Keystore APIs for Import Wrapped Key, Strongbox, 3DES
Import Wrapped Key:
Applications can import keys in a wrapped, encrypted format. Wrapped keys are
unwrapped inside of a Keymaster device.
Strongbox:
Applications can import and generate keys in secure hardware.
3DES:
Add KeyProperties and KeymasterDefs
Add AndroidKeyStore3DESCipherSpi and provider registrations
Bug: 63931634
Test: Keystore CTS tests in progress
Change-Id: I80b6db865b517fa108f14aced7402336212c441b
diff --git a/api/current.txt b/api/current.txt
index b90d80e..4f725b5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37691,6 +37691,7 @@
method public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
+ method public boolean isStrongBoxBacked();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
}
@@ -37708,6 +37709,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setIsStrongBoxBacked(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
@@ -37768,6 +37770,7 @@
field public static final java.lang.String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";
field public static final java.lang.String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding";
field public static final java.lang.String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";
+ field public static final deprecated java.lang.String KEY_ALGORITHM_3DES = "DESede";
field public static final java.lang.String KEY_ALGORITHM_AES = "AES";
field public static final java.lang.String KEY_ALGORITHM_EC = "EC";
field public static final java.lang.String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";
@@ -37778,11 +37781,13 @@
field public static final java.lang.String KEY_ALGORITHM_RSA = "RSA";
field public static final int ORIGIN_GENERATED = 1; // 0x1
field public static final int ORIGIN_IMPORTED = 2; // 0x2
+ field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
field public static final int ORIGIN_UNKNOWN = 4; // 0x4
field public static final int PURPOSE_DECRYPT = 2; // 0x2
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
+ field public static final int PURPOSE_WRAP_KEY = 32; // 0x20
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -37822,12 +37827,24 @@
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
}
+ public class StrongBoxUnavailableException extends java.security.ProviderException {
+ ctor public StrongBoxUnavailableException();
+ }
+
public class UserNotAuthenticatedException extends java.security.InvalidKeyException {
ctor public UserNotAuthenticatedException();
ctor public UserNotAuthenticatedException(java.lang.String);
ctor public UserNotAuthenticatedException(java.lang.String, java.lang.Throwable);
}
+ public class WrappedKeyEntry implements java.security.KeyStore.Entry {
+ ctor public WrappedKeyEntry(byte[], java.lang.String, java.lang.String, java.security.spec.AlgorithmParameterSpec);
+ method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public java.lang.String getTransformation();
+ method public byte[] getWrappedKeyBytes();
+ method public java.lang.String getWrappingKeyAlias();
+ }
+
}
package android.service.autofill {
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f409e5b..2870dc2 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -101,6 +101,7 @@
public static final int KM_ALGORITHM_RSA = 1;
public static final int KM_ALGORITHM_EC = 3;
public static final int KM_ALGORITHM_AES = 32;
+ public static final int KM_ALGORITHM_3DES = 33;
public static final int KM_ALGORITHM_HMAC = 128;
// Block modes.
@@ -130,6 +131,7 @@
public static final int KM_ORIGIN_GENERATED = 0;
public static final int KM_ORIGIN_IMPORTED = 2;
public static final int KM_ORIGIN_UNKNOWN = 3;
+ public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;
// Key usability requirements.
public static final int KM_BLOB_STANDALONE = 0;
@@ -140,6 +142,7 @@
public static final int KM_PURPOSE_DECRYPT = 1;
public static final int KM_PURPOSE_SIGN = 2;
public static final int KM_PURPOSE_VERIFY = 3;
+ public static final int KM_PURPOSE_WRAP = 5;
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 1690e8c..e25386b 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -510,6 +510,19 @@
return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
}
+ public int importWrappedKey(String wrappedKeyAlias, byte[] wrappedKey,
+ String wrappingKeyAlias,
+ byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid, int uid,
+ KeyCharacteristics outCharacteristics) {
+ try {
+ return mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+ maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
try {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStore3DESCipherSpi.java
new file mode 100644
index 0000000..01fd062
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStore3DESCipherSpi.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Base class for Android Keystore 3DES {@link CipherSpi} implementations.
+ *
+ * @hide
+ */
+public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ private static final int BLOCK_SIZE_BYTES = 8;
+
+ private final int mKeymasterBlockMode;
+ private final int mKeymasterPadding;
+ /** Whether this transformation requires an IV. */
+ private final boolean mIvRequired;
+
+ private byte[] mIv;
+
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ AndroidKeyStore3DESCipherSpi(
+ int keymasterBlockMode,
+ int keymasterPadding,
+ boolean ivRequired) {
+ mKeymasterBlockMode = keymasterBlockMode;
+ mKeymasterPadding = keymasterPadding;
+ mIvRequired = ivRequired;
+ }
+
+ abstract static class ECB extends AndroidKeyStore3DESCipherSpi {
+ protected ECB(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
+ }
+
+ public static class NoPadding extends ECB {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends ECB {
+ public PKCS7Padding() {
+ super(KeymasterDefs.KM_PAD_PKCS7);
+ }
+ }
+ }
+
+ abstract static class CBC extends AndroidKeyStore3DESCipherSpi {
+ protected CBC(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
+ }
+
+ public static class NoPadding extends CBC {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends CBC {
+ public PKCS7Padding() {
+ super(KeymasterDefs.KM_PAD_PKCS7);
+ }
+ }
+ }
+
+ @Override
+ protected void initKey(int i, Key key) throws InvalidKeyException {
+ if (!(key instanceof AndroidKeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException(
+ "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
+ KeyProperties.KEY_ALGORITHM_3DES + " supported");
+ }
+ setKey((AndroidKeyStoreSecretKey) key);
+ }
+
+ @Override
+ protected int engineGetBlockSize() {
+ return BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected int engineGetOutputSize(int inputLen) {
+ return inputLen + 3 * BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ return ArrayUtils.cloneIfNotEmpty(mIv);
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ if (!mIvRequired) {
+ return null;
+ }
+ if ((mIv != null) && (mIv.length > 0)) {
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("DESede");
+ params.init(new IvParameterSpec(mIv));
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain 3DES AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize 3DES AlgorithmParameters with an IV",
+ e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!mIvRequired) {
+ return;
+ }
+
+ // IV is used
+ if (!isEncrypting()) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException(
+ "IvParameterSpec must be provided when decrypting");
+ }
+ return;
+ }
+ if (!(params instanceof IvParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
+ }
+ mIv = ((IvParameterSpec) params).getIV();
+ if (mIv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ return;
+ }
+
+ if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ + ". Supported: DESede");
+ }
+
+ IvParameterSpec ivSpec;
+ try {
+ ivSpec = params.getParameterSpec(IvParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ", but not found in parameters: " + params, e);
+ }
+ mIv = null;
+ return;
+ }
+ mIv = ivSpec.getIV();
+ if (mIv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
+ }
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
+ // IV will need to be generated
+ return BLOCK_SIZE_BYTES;
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
+ protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
+ if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
+ // IV is being reused for encryption: this violates security best practices.
+ throw new IllegalStateException(
+ "IV has already been used. Reusing IV in encryption mode violates security best"
+ + " practices.");
+ }
+
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_3DES);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ if ((mIvRequired) && (mIv != null)) {
+ keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ @Override
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ KeymasterArguments keymasterArgs) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
+ if ((returnedIv != null) && (returnedIv.length == 0)) {
+ returnedIv = null;
+ }
+
+ if (mIvRequired) {
+ if (mIv == null) {
+ mIv = returnedIv;
+ } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+ throw new ProviderException("IV in use differs from provided IV");
+ }
+ } else {
+ if (returnedIv != null) {
+ throw new ProviderException(
+ "IV in use despite IV not being used by this transformation");
+ }
+ }
+ }
+
+ @Override
+ protected final void resetAll() {
+ mIv = null;
+ mIvHasBeenUsed = false;
+ super.resetAll();
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index be390ff..e4cf84a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -93,6 +93,16 @@
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
+ putSymmetricCipherImpl("DESede/CBC/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding");
+ putSymmetricCipherImpl("DESede/CBC/PKCS7Padding",
+ PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding");
+
+ putSymmetricCipherImpl("DESede/ECB/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding");
+ putSymmetricCipherImpl("DESede/ECB/PKCS7Padding",
+ PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding");
+
putSymmetricCipherImpl("AES/GCM/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index fdebf37..5bcb34a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -307,7 +307,7 @@
*
* <p>This implementation returns {@code null}.
*
- * @returns stream or {@code null} if AAD is not supported by this cipher.
+ * @return stream or {@code null} if AAD is not supported by this cipher.
*/
@Nullable
protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index f1d1e16..379e177 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -60,6 +60,12 @@
}
}
+ public static class DESede extends AndroidKeyStoreKeyGeneratorSpi {
+ public DESede() {
+ super(KeymasterDefs.KM_ALGORITHM_3DES, 168);
+ }
+ }
+
protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
protected HmacBase(int keymasterDigest) {
super(KeymasterDefs.KM_ALGORITHM_HMAC,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 55e6519..1018926 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -80,6 +80,7 @@
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
+ put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede");
put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224");
put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256");
@@ -88,6 +89,7 @@
// java.security.SecretKeyFactory
putSecretKeyFactoryImpl("AES");
+ putSecretKeyFactoryImpl("DESede");
putSecretKeyFactoryImpl("HmacSHA1");
putSecretKeyFactoryImpl("HmacSHA224");
putSecretKeyFactoryImpl("HmacSHA256");
@@ -348,7 +350,8 @@
}
if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC ||
- keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
+ keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES ||
+ keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid,
keyCharacteristics);
} else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index d73a9e2..440e086 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -18,6 +18,7 @@
import libcore.util.EmptyArray;
import android.security.Credentials;
+import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.KeyStoreParameter;
import android.security.keymaster.KeyCharacteristics;
@@ -25,6 +26,7 @@
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
+import android.security.keystore.WrappedKeyEntry;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -744,6 +746,31 @@
}
}
+ private void setWrappedKeyEntry(String alias, byte[] wrappedKeyBytes, String wrappingKeyAlias,
+ java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
+ if (param != null) {
+ throw new KeyStoreException("Protection parameters are specified inside wrapped keys");
+ }
+
+ byte[] maskingKey = new byte[32];
+ KeymasterArguments args = new KeymasterArguments(); // TODO: populate wrapping key args.
+
+ int errorCode = mKeyStore.importWrappedKey(
+ Credentials.USER_SECRET_KEY + alias,
+ wrappedKeyBytes,
+ Credentials.USER_PRIVATE_KEY + wrappingKeyAlias,
+ maskingKey,
+ args,
+ GateKeeper.getSecureUserId(),
+ 0, // FIXME fingerprint id?
+ mUid,
+ new KeyCharacteristics());
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
+ + errorCode);
+ }
+ }
+
@Override
public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
throws KeyStoreException {
@@ -974,6 +1001,9 @@
} else if (entry instanceof SecretKeyEntry) {
SecretKeyEntry secE = (SecretKeyEntry) entry;
setSecretKeyEntry(alias, secE.getSecretKey(), param);
+ } else if (entry instanceof WrappedKeyEntry) {
+ WrappedKeyEntry wke = (WrappedKeyEntry) entry;
+ setWrappedKeyEntry(alias, wke.getWrappedKeyBytes(), wke.getWrappingKeyAlias(), param);
} else {
throw new KeyStoreException(
"Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 1238d87..1e2b873 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -262,6 +262,7 @@
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mInvalidatedByBiometricEnrollment;
+ private final boolean mIsStrongBoxBacked;
/**
* @hide should be built with Builder
@@ -289,7 +290,8 @@
byte[] attestationChallenge,
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
- boolean invalidatedByBiometricEnrollment) {
+ boolean invalidatedByBiometricEnrollment,
+ boolean isStrongBoxBacked) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -335,6 +337,7 @@
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
+ mIsStrongBoxBacked = isStrongBoxBacked;
}
/**
@@ -625,6 +628,13 @@
}
/**
+ * Returns {@code true} if the key is protected by a Strongbox security chip.
+ */
+ public boolean isStrongBoxBacked() {
+ return mIsStrongBoxBacked;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -652,6 +662,7 @@
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
+ private boolean mIsStrongBoxBacked = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -1177,6 +1188,15 @@
}
/**
+ * Sets whether this key should be protected by a StrongBox security chip.
+ */
+ @NonNull
+ public Builder setIsStrongBoxBacked(boolean isStrongBoxBacked) {
+ mIsStrongBoxBacked = isStrongBoxBacked;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1204,7 +1224,8 @@
mAttestationChallenge,
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
- mInvalidatedByBiometricEnrollment);
+ mInvalidatedByBiometricEnrollment,
+ mIsStrongBoxBacked);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index a250d1f0..f54b6de 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -44,6 +44,7 @@
PURPOSE_DECRYPT,
PURPOSE_SIGN,
PURPOSE_VERIFY,
+ PURPOSE_WRAP_KEY,
})
public @interface PurposeEnum {}
@@ -68,6 +69,11 @@
public static final int PURPOSE_VERIFY = 1 << 3;
/**
+ * Purpose of key: wrapping and unwrapping wrapped keys for secure import.
+ */
+ public static final int PURPOSE_WRAP_KEY = 1 << 5;
+
+ /**
* @hide
*/
public static abstract class Purpose {
@@ -83,6 +89,8 @@
return KeymasterDefs.KM_PURPOSE_SIGN;
case PURPOSE_VERIFY:
return KeymasterDefs.KM_PURPOSE_VERIFY;
+ case PURPOSE_WRAP_KEY:
+ return KeymasterDefs.KM_PURPOSE_WRAP;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
@@ -98,6 +106,8 @@
return PURPOSE_SIGN;
case KeymasterDefs.KM_PURPOSE_VERIFY:
return PURPOSE_VERIFY;
+ case KeymasterDefs.KM_PURPOSE_WRAP:
+ return PURPOSE_WRAP_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
@@ -146,6 +156,15 @@
/** Advanced Encryption Standard (AES) key. */
public static final String KEY_ALGORITHM_AES = "AES";
+ /**
+ * Triple Data Encryption Algorithm (3DES) key.
+ *
+ * @deprecated Included for interoperability with legacy systems. Prefer {@link
+ * KeyProperties#KEY_ALGORITHM_AES} for new development.
+ */
+ @Deprecated
+ public static final String KEY_ALGORITHM_3DES = "DESede";
+
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";
@@ -196,6 +215,8 @@
@NonNull @KeyAlgorithmEnum String algorithm) {
if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
return KeymasterDefs.KM_ALGORITHM_AES;
+ } else if (KEY_ALGORITHM_3DES.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_3DES;
} else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) {
return KeymasterDefs.KM_ALGORITHM_HMAC;
} else {
@@ -210,6 +231,8 @@
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_AES:
return KEY_ALGORITHM_AES;
+ case KeymasterDefs.KM_ALGORITHM_3DES:
+ return KEY_ALGORITHM_3DES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_SHA1:
@@ -666,6 +689,10 @@
*/
public static final int ORIGIN_UNKNOWN = 1 << 2;
+ /** Key was imported into the AndroidKeyStore in an encrypted wrapper */
+ public static final int ORIGIN_SECURELY_IMPORTED = 1 << 3;
+
+
/**
* @hide
*/
@@ -680,6 +707,8 @@
return ORIGIN_IMPORTED;
case KeymasterDefs.KM_ORIGIN_UNKNOWN:
return ORIGIN_UNKNOWN;
+ case KeymasterDefs.KM_ORIGIN_SECURELY_IMPORTED:
+ return ORIGIN_SECURELY_IMPORTED;
default:
throw new IllegalArgumentException("Unknown origin: " + origin);
}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 2eb0663..dbacb9c 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -488,9 +488,9 @@
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
-
private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
private boolean mCriticalToDeviceEncryption = false;
+
/**
* Creates a new instance of the {@code Builder}.
*
diff --git a/keystore/java/android/security/keystore/StrongBoxUnavailableException.java b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
new file mode 100644
index 0000000..ad41a58
--- /dev/null
+++ b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import java.security.ProviderException;
+
+/**
+ * Indicates that an operation could not be performed because the requested security hardware
+ * is not available.
+ */
+public class StrongBoxUnavailableException extends ProviderException {
+
+}
+
diff --git a/keystore/java/android/security/keystore/WrappedKeyEntry.java b/keystore/java/android/security/keystore/WrappedKeyEntry.java
new file mode 100644
index 0000000..a8f4afe
--- /dev/null
+++ b/keystore/java/android/security/keystore/WrappedKeyEntry.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.security.keystore;
+
+import java.security.KeyStore.Entry;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * An {@link Entry} that holds a wrapped key.
+ */
+public class WrappedKeyEntry implements Entry {
+
+ private final byte[] mWrappedKeyBytes;
+ private final String mWrappingKeyAlias;
+ private final String mTransformation;
+ private final AlgorithmParameterSpec mAlgorithmParameterSpec;
+
+ public WrappedKeyEntry(byte[] wrappedKeyBytes, String wrappingKeyAlias, String transformation,
+ AlgorithmParameterSpec algorithmParameterSpec) {
+ mWrappedKeyBytes = wrappedKeyBytes;
+ mWrappingKeyAlias = wrappingKeyAlias;
+ mTransformation = transformation;
+ mAlgorithmParameterSpec = algorithmParameterSpec;
+ }
+
+ public byte[] getWrappedKeyBytes() {
+ return mWrappedKeyBytes;
+ }
+
+ public String getWrappingKeyAlias() {
+ return mWrappingKeyAlias;
+ }
+
+ public String getTransformation() {
+ return mTransformation;
+ }
+
+ public AlgorithmParameterSpec getAlgorithmParameterSpec() {
+ return mAlgorithmParameterSpec;
+ }
+}