summaryrefslogtreecommitdiffstats
path: root/keystore/java/android/security
diff options
context:
space:
mode:
Diffstat (limited to 'keystore/java/android/security')
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java135
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java100
-rw-r--r--keystore/java/android/security/CryptoOperationException.java16
-rw-r--r--keystore/java/android/security/EcIesParameterSpec.java287
-rw-r--r--keystore/java/android/security/KeyExpiredException.java47
-rw-r--r--keystore/java/android/security/KeyGeneratorSpec.java306
-rw-r--r--keystore/java/android/security/KeyNotYetValidException.java48
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java452
-rw-r--r--keystore/java/android/security/KeyStore.java57
-rw-r--r--keystore/java/android/security/KeyStoreCipherSpi.java603
-rw-r--r--keystore/java/android/security/KeyStoreConnectException.java16
-rw-r--r--keystore/java/android/security/KeyStoreCryptoOperation.java31
-rw-r--r--keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java201
-rw-r--r--keystore/java/android/security/KeyStoreException.java37
-rw-r--r--keystore/java/android/security/KeyStoreHmacSpi.java117
-rw-r--r--keystore/java/android/security/KeyStoreKeyCharacteristics.java63
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java571
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java151
-rw-r--r--keystore/java/android/security/KeyStoreKeySpec.java214
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java339
-rw-r--r--keystore/java/android/security/KeyStoreSecretKey.java16
-rw-r--r--keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java186
-rw-r--r--keystore/java/android/security/KeymasterException.java20
-rw-r--r--keystore/java/android/security/KeymasterUtils.java62
-rw-r--r--keystore/java/android/security/NewFingerprintEnrolledException.java41
-rw-r--r--keystore/java/android/security/UserNotAuthenticatedException.java49
26 files changed, 3555 insertions, 610 deletions
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index 1d16ca1..c5b6a68 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
String keyAlgorithmString = key.getAlgorithm();
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
- @KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
+ @KeyStoreKeyConstraints.DigestEnum Integer digest;
try {
keyAlgorithm =
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
@@ -466,100 +466,109 @@ public class AndroidKeyStore extends KeyStoreSpi {
throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
}
- if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
- throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
- + ", parameter spec: "
- + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
- }
-
KeymasterArguments args = new KeymasterArguments();
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));
- if (digest != null) {
- // Digest available from JCA key algorithm
- if (params.getDigest() != null) {
- // Digest also specified in parameters -- check that these two match
- if (digest != params.getDigest()) {
- throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
+ @KeyStoreKeyConstraints.DigestEnum int digests;
+ if (params.isDigestsSpecified()) {
+ // Digest(s) specified in parameters
+ if (digest != null) {
+ // Digest also specified in the JCA key algorithm name.
+ if ((params.getDigests() & digest) != digest) {
+ throw new KeyStoreException("Key digest mismatch"
+ + ". Key: " + keyAlgorithmString
+ ", parameter spec: "
- + KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
+ + KeyStoreKeyConstraints.Digest.allToString(params.getDigests()));
}
}
+ digests = params.getDigests();
} else {
- // Digest not available from JCA key algorithm
- digest = params.getDigest();
+ // No digest specified in parameters
+ if (digest != null) {
+ // Digest specified in the JCA key algorithm name.
+ digests = digest;
+ } else {
+ digests = 0;
+ }
}
- if (digest != null) {
- args.addInt(KeymasterDefs.KM_TAG_DIGEST,
- KeyStoreKeyConstraints.Digest.toKeymaster(digest));
+ for (int keymasterDigest : KeyStoreKeyConstraints.Digest.allToKeymaster(digests)) {
+ args.addInt(KeymasterDefs.KM_TAG_DIGEST, keymasterDigest);
}
- if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
- if (digest == null) {
- throw new IllegalStateException("Digest algorithm must be specified for key"
- + " algorithm " + keyAlgorithmString);
- }
+ if (digests != 0) {
+ // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+ // This code will blow up if mode than one digest is specified.
Integer digestOutputSizeBytes =
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
if (digestOutputSizeBytes != null) {
- // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
// TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
}
}
-
- @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
- ? params.getPurposes()
- : (KeyStoreKeyConstraints.Purpose.ENCRYPT
- | KeyStoreKeyConstraints.Purpose.DECRYPT
- | KeyStoreKeyConstraints.Purpose.SIGN
- | KeyStoreKeyConstraints.Purpose.VERIFY);
- for (int keymasterPurpose :
- KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
+ if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+ if (digests == 0) {
+ throw new KeyStoreException("At least one digest algorithm must be specified"
+ + " for key algorithm " + keyAlgorithmString);
+ }
}
- if (params.getBlockMode() != null) {
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
- KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
+
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = params.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (params.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new KeyStoreException("Randomized encryption (IND-CPA) required but may be"
+ + " violated by block mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyStoreParameter documentation.");
+ }
}
- if (params.getPadding() != null) {
- args.addInt(KeymasterDefs.KM_TAG_PADDING,
- KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
+ for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
+ args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- if (params.getMaxUsesPerBoot() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
- if (params.getMinSecondsBetweenOperations() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
- params.getMinSecondsBetweenOperations());
+ for (int keymasterPadding :
+ KeyStoreKeyConstraints.Padding.allToKeymaster(params.getPaddings())) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
}
- if (params.getUserAuthenticators().isEmpty()) {
+ if (params.getUserAuthenticators() == 0) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
} else {
- // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized
-// for (int userAuthenticatorId : params.getUserAuthenticators()) {
-// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId);
-// }
+ args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
+ KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster(
+ params.getUserAuthenticators()));
}
- if (params.getUserAuthenticationValidityDurationSeconds() != null) {
+ if (params.isInvalidatedOnNewFingerprintEnrolled()) {
+ // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports
+ // that.
+ }
+ if (params.getUserAuthenticationValidityDurationSeconds() != -1) {
args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
params.getUserAuthenticationValidityDurationSeconds());
}
- if (params.getKeyValidityStart() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart());
- }
- if (params.getKeyValidityForOriginationEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
- params.getKeyValidityForOriginationEnd());
- }
- if (params.getKeyValidityForConsumptionEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
- params.getKeyValidityForConsumptionEnd());
- }
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (params.getKeyValidityStart() != null)
+ ? params.getKeyValidityStart() : new Date(0));
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (params.getKeyValidityForOriginationEnd() != null)
+ ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (params.getKeyValidityForConsumptionEnd() != null)
+ ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
// TODO: Remove this once keymaster does not require us to specify the size of imported key.
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (!params.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
+ args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
+ }
+
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
int errorCode = mKeyStore.importKey(
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 7313c48..43f3b30 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -18,6 +18,9 @@ package android.security;
import java.security.Provider;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
/**
* A provider focused on providing JCA interfaces for the Android KeyStore.
*
@@ -26,22 +29,105 @@ import java.security.Provider;
public class AndroidKeyStoreProvider extends Provider {
public static final String PROVIDER_NAME = "AndroidKeyStore";
+ // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
+ // classes when this provider is instantiated and installed early on during each app's
+ // initialization process.
+
+ private static final String PACKAGE_NAME = "android.security";
+ private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".KeyStoreSecretKey";
+
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
// java.security.KeyStore
- put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName());
+ put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore");
// java.security.KeyPairGenerator
- put("KeyPairGenerator.EC", AndroidKeyPairGenerator.EC.class.getName());
- put("KeyPairGenerator.RSA", AndroidKeyPairGenerator.RSA.class.getName());
+ put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC");
+ put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA");
// javax.crypto.KeyGenerator
- put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName());
- put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
+ put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES");
+ put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1");
+ put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224");
+ put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256");
+ put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384");
+ put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512");
+
+ // java.security.SecretKeyFactory
+ putSecretKeyFactoryImpl("AES");
+ putSecretKeyFactoryImpl("HmacSHA1");
+ putSecretKeyFactoryImpl("HmacSHA224");
+ putSecretKeyFactoryImpl("HmacSHA256");
+ putSecretKeyFactoryImpl("HmacSHA384");
+ putSecretKeyFactoryImpl("HmacSHA512");
// javax.crypto.Mac
- put("Mac.HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
- put("Mac.HmacSHA256 SupportedKeyClasses", KeyStoreSecretKey.class.getName());
+ putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1");
+ putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
+ putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
+ putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
+ putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512");
+
+ // javax.crypto.Cipher
+ putSymmetricCipherImpl("AES/ECB/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding");
+ putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CBC/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding");
+ putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CTR/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding");
+ }
+
+ private void putSecretKeyFactoryImpl(String algorithm) {
+ put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
+ }
+
+ private void putMacImpl(String algorithm, String implClass) {
+ put("Mac." + algorithm, implClass);
+ put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ private void putSymmetricCipherImpl(String transformation, String implClass) {
+ put("Cipher." + transformation, implClass);
+ put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ /**
+ * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+ * primitive.
+ *
+ * <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
+ *
+ * @return KeyStore operation handle or {@code null} if the provided primitive's KeyStore
+ * operation is not in progress.
+ *
+ * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
+ * by AndroidKeyStore provider.
+ */
+ public static Long getKeyStoreOperationHandle(Object cryptoPrimitive) {
+ if (cryptoPrimitive == null) {
+ throw new NullPointerException();
+ }
+ Object spi;
+ if (cryptoPrimitive instanceof Mac) {
+ spi = ((Mac) cryptoPrimitive).getSpi();
+ } else if (cryptoPrimitive instanceof Cipher) {
+ spi = ((Cipher) cryptoPrimitive).getSpi();
+ } else {
+ throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive);
+ }
+ if (!(spi instanceof KeyStoreCryptoOperation)) {
+ throw new IllegalArgumentException(
+ "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive
+ + ", spi: " + spi);
+ }
+ return ((KeyStoreCryptoOperation) spi).getOperationHandle();
}
}
diff --git a/keystore/java/android/security/CryptoOperationException.java b/keystore/java/android/security/CryptoOperationException.java
index ce64455..00c142f 100644
--- a/keystore/java/android/security/CryptoOperationException.java
+++ b/keystore/java/android/security/CryptoOperationException.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
/**
diff --git a/keystore/java/android/security/EcIesParameterSpec.java b/keystore/java/android/security/EcIesParameterSpec.java
new file mode 100644
index 0000000..0f19812
--- /dev/null
+++ b/keystore/java/android/security/EcIesParameterSpec.java
@@ -0,0 +1,287 @@
+package android.security;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * {@link AlgorithmParameterSpec} for ECIES (Integrated Encryption Scheme using Elliptic Curve
+ * cryptography) based on {@code ISO/IEC 18033-2}.
+ *
+ * <p>ECIES is a hybrid authenticated encryption scheme. Encryption is performed using an Elliptic
+ * Curve (EC) public key. The resulting ciphertext can be decrypted only using the corresponding EC
+ * private key. The scheme is called hybrid because the EC key is only used to securely encapsulate
+ * symmetric key material. Encryption of plaintext and authentication of the corresponding
+ * ciphertext is performed using symmetric cryptography.
+ *
+ * <p>Encryption using ECIES consists of two stages:
+ * <ol>
+ * <li>Key Encapsulation Mechanism (KEM) randomly generates symmetric key material and securely
+ * encapsulates it in the output so that it can be extracted by the KEM when decrypting.
+ * Encapsulated key material is represented in the output as an EC point.</li>
+ * <li>The above symmetric key material is used by Data Encapsulation Mechanism (DEM) to encrypt the
+ * provided plaintext and authenticate the ciphertext. The resulting authenticated ciphertext is
+ * then output. When decrypting, the DEM first authenticates the ciphertext and, only if it
+ * authenticates, decrypts the ciphertext and outputs the plaintext.</li>
+ * </ol>
+ *
+ * <p>Details of KEM:
+ * <ul>
+ * <li>Only curves with cofactor of {@code 1} are supported.</li>
+ * <li>{@code CheckMode}, {@code OldCofactorMode}, {@code CofactorMode}, and {@code SingleHashMode}
+ * are {@code 0}.
+ * <li>Point format is specified by {@link #getKemPointFormat()}.</li>
+ * <li>KDF algorithm is specified by {@link #getKemKdfAlgorithm()}.</li>
+ * </ul>
+ *
+ * <p>Details of DEM:
+ * <ul>
+ * <li>Only DEM1-like mechanism is supported, with its symmetric cipher (SC) specified by
+ * {@link #getDemCipherTransformation()} (e.g., {@code AES/CBC/NoPadding} for standard DEM1) and
+ * MAC algorithm specified by {@link #getDemMacAlgorithm()} (e.g., {@code HmacSHA1} for standard
+ * DEM1).</li>
+ * </ul>
+ *
+ * @hide
+ */
+public class EcIesParameterSpec implements AlgorithmParameterSpec {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {PointFormat.UNCOMPRESSED, PointFormat.COMPRESSED})
+ public @interface PointFormatEnum {}
+
+ /**
+ * Wire format of the EC point.
+ */
+ public static abstract class PointFormat {
+
+ private PointFormat() {}
+
+ /** Unspecified point format. */
+ public static final int UNSPECIFIED = -1;
+
+ /**
+ * Uncompressed point format: both coordinates are stored separately.
+ *
+ * <p>The wire format is byte {@code 0x04} followed by binary representation of the
+ * {@code x} coordinate followed by binary representation of the {@code y} coordinate. See
+ * {@code ISO 18033-2} section {@code 5.4.3}.
+ */
+ public static final int UNCOMPRESSED = 0;
+
+ /**
+ * Compressed point format: only one coordinate is stored.
+ *
+ * <p>The wire format is byte {@code 0x02} or {@code 0x03} (depending on the value of the
+ * stored coordinate) followed by the binary representation of the {@code x} coordinate.
+ * See {@code ISO 18033-2} section {@code 5.4.3}.
+ */
+ public static final int COMPRESSED = 1;
+ }
+
+ /**
+ * Default parameter spec: NIST P-256 curve (aka secp256r1 aka prime256v1), compressed point
+ * format, {@code HKDFwithSHA256}, DEM uses 128-bit AES GCM.
+ */
+ public static final EcIesParameterSpec DEFAULT = new EcIesParameterSpec(
+ "P-256",
+ PointFormat.COMPRESSED,
+ "HKDFwithSHA256",
+ "AES/GCM/NoPadding",
+ 128,
+ null,
+ 0);
+
+ private final String mKemCurveName;
+ private final @PointFormatEnum int mKemPointFormat;
+ private final String mKemKdfAlgorithm;
+ private final String mDemCipherTransformation;
+ private final int mDemCipherKeySize;
+ private final String mDemMacAlgorithm;
+ private final int mDemMacKeySize;
+
+ private EcIesParameterSpec(
+ String kemCurveName,
+ @PointFormatEnum int kemPointFormat,
+ String kemKdfAlgorithm,
+ String demCipherTransformation,
+ int demCipherKeySize,
+ String demMacAlgorithm,
+ int demMacKeySize) {
+ mKemCurveName = kemCurveName;
+ mKemPointFormat = kemPointFormat;
+ mKemKdfAlgorithm = kemKdfAlgorithm;
+ mDemCipherTransformation = demCipherTransformation;
+ mDemCipherKeySize = demCipherKeySize;
+ mDemMacAlgorithm = demMacAlgorithm;
+ mDemMacKeySize = demMacKeySize;
+ }
+
+ /**
+ * Returns KEM EC curve name (e.g., {@code secp256r1}) or {@code null} if not specified.
+ */
+ public String getKemCurveName() {
+ return mKemCurveName;
+ }
+
+ /**
+ * Returns KEM EC point wire format or {@link PointFormat#UNSPECIFIED} if not specified.
+ */
+ public @PointFormatEnum int getKemPointFormat() {
+ return mKemPointFormat;
+ }
+
+ /**
+ * Returns KEM KDF algorithm (e.g., {@code HKDFwithSHA256} or {@code KDF1withSHA1}) or
+ * {@code null} if not specified.
+ */
+ public String getKemKdfAlgorithm() {
+ return mKemKdfAlgorithm;
+ }
+
+ /**
+ * Returns DEM {@link Cipher} transformation (e.g., {@code AES/GCM/NoPadding} or
+ * {@code AES/CBC/PKCS7Padding}) or {@code null} if not specified.
+ *
+ * @see Cipher#getInstance(String)
+ * @see #getDemCipherKeySize()
+ */
+ public String getDemCipherTransformation() {
+ return mDemCipherTransformation;
+ }
+
+ /**
+ * Returns DEM {@link Cipher} key size in bits.
+ *
+ * @see #getDemCipherTransformation()
+ */
+ public int getDemCipherKeySize() {
+ return mDemCipherKeySize;
+ }
+
+ /**
+ * Returns DEM {@link Mac} algorithm (e.g., {@code HmacSHA256} or {@code HmacSHA1}) or
+ * {@code null} if not specified.
+ *
+ * @see Mac#getInstance(String)
+ * @see #getDemMacKeySize()
+ */
+ public String getDemMacAlgorithm() {
+ return mDemMacAlgorithm;
+ }
+
+ /**
+ * Returns DEM {@link Mac} key size in bits.
+ *
+ * @see #getDemCipherTransformation()
+ */
+ public int getDemMacKeySize() {
+ return mDemMacKeySize;
+ }
+
+ /**
+ * Builder of {@link EcIesParameterSpec}.
+ */
+ public static class Builder {
+ private String mKemCurveName;
+ private @PointFormatEnum int mKemPointFormat = PointFormat.UNSPECIFIED;
+ private String mKemKdfAlgorithm;
+ private String mDemCipherTransformation;
+ private int mDemCipherKeySize = 128;
+ private String mDemMacAlgorithm;
+ private int mDemMacKeySize = -1;
+
+ /**
+ * Sets KEM EC curve name. For example, {@code P-256} or {@code secp256r1}.
+ *
+ * <p>NOTE: Only curves with cofactor of {@code 1} are supported.
+ */
+ public Builder setKemCurveName(String name) {
+ mKemCurveName = name;
+ return this;
+ }
+
+ /**
+ * Sets KEM EC point wire format.
+ */
+ public Builder setKemPointFormat(@PointFormatEnum int pointFormat) {
+ mKemPointFormat = pointFormat;
+ return this;
+ }
+
+ /**
+ * Sets KEM KDF algorithm. For example, {@code HKDFwithSHA256}, {@code KDF2withSHA256}, or
+ * {@code KDF1withSHA1}.
+ */
+ public Builder setKemKdfAlgorithm(String algorithm) {
+ mKemKdfAlgorithm = algorithm;
+ return this;
+ }
+
+ /**
+ * Sets DEM {@link Cipher} transformation. For example, {@code AES/GCM/NoPadding},
+ * {@code AES/CBC/PKCS7Padding} or {@code AES/CTR/NoPadding}.
+ *
+ * @see Cipher#getInstance(String)
+ */
+ public Builder setDemCipherTransformation(String transformation) {
+ mDemCipherTransformation = transformation;
+ return this;
+ }
+
+ /**
+ * Returns DEM {@link Cipher} key size in bits.
+ *
+ * <p>The default value is {@code 128} bits.
+ *
+ * @see #setDemCipherTransformation(String)
+ */
+ public Builder setDemCipherKeySize(int sizeBits) {
+ mDemCipherKeySize = sizeBits;
+ return this;
+ }
+
+ /**
+ * Sets DEM {@link Mac} algorithm. For example, {@code HmacSHA256} or {@code HmacSHA1}.
+ *
+ * @see Mac#getInstance(String)
+ */
+ public Builder setDemMacAlgorithm(String algorithm) {
+ mDemMacAlgorithm = algorithm;
+ return this;
+ }
+
+ /**
+ * Sets DEM {@link Mac} key size in bits.
+ *
+ * <p>By default, {@code Mac} key size is the same as the {@code Cipher} key size.
+ *
+ * @see #setDemCipherKeySize(int)
+ */
+ public Builder setDemMacKeySize(int sizeBits) {
+ mDemMacKeySize = sizeBits;
+ return this;
+ }
+
+ /**
+ * Returns a new {@link EcIesParameterSpec} based on the current state of this builder.
+ */
+ public EcIesParameterSpec build() {
+ int demMacKeySize = (mDemMacKeySize != -1) ? mDemMacKeySize : mDemCipherKeySize;
+ return new EcIesParameterSpec(
+ mKemCurveName,
+ mKemPointFormat,
+ mKemKdfAlgorithm,
+ mDemCipherTransformation,
+ mDemCipherKeySize,
+ mDemMacAlgorithm,
+ demMacKeySize
+ );
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyExpiredException.java b/keystore/java/android/security/KeyExpiredException.java
new file mode 100644
index 0000000..35a5acc
--- /dev/null
+++ b/keystore/java/android/security/KeyExpiredException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Indicates that a cryptographic operation failed because the employed key's validity end date
+ * is in the past.
+ *
+ * @hide
+ */
+public class KeyExpiredException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code KeyExpiredException} without detail message and cause.
+ */
+ public KeyExpiredException() {
+ super("Key expired");
+ }
+
+ /**
+ * Constructs a new {@code KeyExpiredException} with the provided detail message and no cause.
+ */
+ public KeyExpiredException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code KeyExpiredException} with the provided detail message and cause.
+ */
+ public KeyExpiredException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
index 6274b70..4eedb24 100644
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -1,15 +1,28 @@
+/*
+ * Copyright (C) 2015 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;
import android.content.Context;
import android.text.TextUtils;
-import java.security.cert.Certificate;
import java.security.spec.AlgorithmParameterSpec;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -17,13 +30,13 @@ import javax.crypto.SecretKey;
* {@link AlgorithmParameterSpec} for initializing a {@code KeyGenerator} that works with
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>.
*
- * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API
- * using the {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up
- * some UI to ask the user to unlock or initialize the Android KeyStore facility.
+ * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API using the
+ * {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up some UI to
+ * ask the user to unlock or initialize the Android KeyStore facility.
*
* <p>After generation, the {@code keyStoreAlias} is used with the
* {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)}
- * interface to retrieve the {@link SecretKey} and its associated {@link Certificate} chain.
+ * interface to retrieve the {@link SecretKey}.
*
* @hide
*/
@@ -36,13 +49,13 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private final Date mKeyValidityStart;
private final Date mKeyValidityForOriginationEnd;
private final Date mKeyValidityForConsumptionEnd;
- private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private final Integer mMinSecondsBetweenOperations;
- private final Integer mMaxUsesPerBoot;
- private final Set<Integer> mUserAuthenticators;
- private final Integer mUserAuthenticationValidityDurationSeconds;
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mInvalidatedOnNewFingerprintEnrolled;
private KeyGeneratorSpec(
Context context,
@@ -52,19 +65,19 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
Date keyValidityStart,
Date keyValidityForOriginationEnd,
Date keyValidityForConsumptionEnd,
- @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
- @KeyStoreKeyConstraints.PaddingEnum Integer padding,
- @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
- Integer minSecondsBetweenOperations,
- Integer maxUsesPerBoot,
- Set<Integer> userAuthenticators,
- Integer userAuthenticationValidityDurationSeconds) {
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds,
+ boolean invalidatedOnNewFingerprintEnrolled) {
if (context == null) {
throw new IllegalArgumentException("context == null");
} else if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
- } else if ((userAuthenticationValidityDurationSeconds != null)
- && (userAuthenticationValidityDurationSeconds < 0)) {
+ } else if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
throw new IllegalArgumentException(
"userAuthenticationValidityDurationSeconds must not be negative");
}
@@ -77,14 +90,12 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
mPurposes = purposes;
- mPadding = padding;
- mBlockMode = blockMode;
- mMinSecondsBetweenOperations = minSecondsBetweenOperations;
- mMaxUsesPerBoot = maxUsesPerBoot;
- mUserAuthenticators = (userAuthenticators != null)
- ? new HashSet<Integer>(userAuthenticators)
- : Collections.<Integer>emptySet();
+ mPaddings = paddings;
+ mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
+ mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
}
/**
@@ -126,18 +137,16 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Gets the time instant after which the key is no long valid for decryption and verification.
+ * Gets the time instant after which the key is no longer valid for decryption and verification.
*
* @return instant or {@code null} if not restricted.
- *
- * @hide
*/
public Date getKeyValidityForConsumptionEnd() {
return mKeyValidityForConsumptionEnd;
}
/**
- * Gets the time instant after which the key is no long valid for encryption and signing.
+ * Gets the time instant after which the key is no longer valid for encryption and signing.
*
* @return instant or {@code null} if not restricted.
*/
@@ -146,80 +155,71 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Gets the set of purposes for which the key can be used to the provided set of purposes.
- *
- * @return set of purposes or {@code null} if the key can be used for any purpose.
+ * Gets the set of purposes for which the key can be used.
*/
- public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
return mPurposes;
}
/**
- * Gets the padding scheme to which the key is restricted.
- *
- * @return padding scheme or {@code null} if the padding scheme is not restricted.
+ * Gets the set of padding schemes to which the key is restricted.
*/
- public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
- return mPadding;
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
}
/**
- * Gets the block mode to which the key is restricted when used for encryption or decryption.
- *
- * @return block more or {@code null} if block mode is not restricted.
- *
- * @hide
+ * Gets the set of block modes to which the key is restricted.
*/
- public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
- return mBlockMode;
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
}
/**
- * Gets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
- *
- * @return number of seconds or {@code null} if there is no restriction on how frequently a key
- * can be used.
- *
- * @hide
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
*/
- public Integer getMinSecondsBetweenOperations() {
- return mMinSecondsBetweenOperations;
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
}
/**
- * Gets the number of times the key can be used without rebooting the device.
+ * Gets the set of user authenticators which protect access to this key. The key can only be
+ * used iff the user has authenticated to at least one of these user authenticators.
*
- * @return maximum number of times or {@code null} if there is no restriction.
- * @hide
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
*/
- public Integer getMaxUsesPerBoot() {
- return mMaxUsesPerBoot;
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
}
/**
- * Gets the user authenticators which protect access to this key. The key can only be used iff
- * the user has authenticated to at least one of these user authenticators.
- *
- * @return user authenticators or empty set if the key can be used without user authentication.
+ * Gets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
*
- * @hide
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
*/
- public Set<Integer> getUserAuthenticators() {
- return new HashSet<Integer>(mUserAuthenticators);
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
}
/**
- * Gets the duration of time (seconds) for which this key can be used after the user
- * successfully authenticates to one of the associated user authenticators.
+ * Returns {@code true} if this key must be permanently invalidated once a new fingerprint is
+ * enrolled. This constraint only has effect if fingerprint reader is one of the user
+ * authenticators protecting access to this key.
*
- * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
+ * @see #getUserAuthenticators()
*
* @hide
*/
- public Integer getUserAuthenticationValidityDurationSeconds() {
- return mUserAuthenticationValidityDurationSeconds;
+ public boolean isInvalidatedOnNewFingerprintEnrolled() {
+ return mInvalidatedOnNewFingerprintEnrolled;
}
/**
@@ -237,13 +237,13 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private Date mKeyValidityStart;
private Date mKeyValidityForOriginationEnd;
private Date mKeyValidityForConsumptionEnd;
- private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private Integer mMinSecondsBetweenOperations;
- private Integer mMaxUsesPerBoot;
- private Set<Integer> mUserAuthenticators;
- private Integer mUserAuthenticationValidityDurationSeconds;
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+ private boolean mInvalidatedOnNewFingerprintEnrolled;
/**
* Creates a new instance of the {@code Builder} with the given {@code context}. The
@@ -299,11 +299,9 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
- *
- * @hide
*/
public Builder setKeyValidityStart(Date startDate) {
mKeyValidityStart = startDate;
@@ -313,13 +311,11 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
* @see #setKeyValidityForOriginationEnd(Date)
- *
- * @hide
*/
public Builder setKeyValidityEnd(Date endDate) {
setKeyValidityForOriginationEnd(endDate);
@@ -330,11 +326,9 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
- *
- * @hide
*/
public Builder setKeyValidityForOriginationEnd(Date endDate) {
mKeyValidityForOriginationEnd = endDate;
@@ -345,11 +339,9 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
- *
- * @hide
*/
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
mKeyValidityForConsumptionEnd = endDate;
@@ -357,11 +349,9 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Restricts the purposes for which the key can be used to the provided set of purposes.
+ * Restricts the key to being used only for the provided set of purposes.
*
- * <p>By default, the key can be used for encryption, decryption, signing, and verification.
- *
- * @hide
+ * <p>This restriction must be specified. There is no default.
*/
public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
mPurposes = purposes;
@@ -369,53 +359,61 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Restricts the key to being used only with the provided padding scheme. Attempts to use
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
* the key with any other padding will be rejected.
*
* <p>This restriction must be specified for keys which are used for encryption/decryption.
- *
- * @hide
*/
- public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
- mPadding = padding;
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
return this;
}
/**
- * Restricts the key to being used only with the provided block mode when encrypting or
- * decrypting. Attempts to use the key with any other block modes will be rejected.
+ * Restricts the key to being used only with the provided block modes. Attempts to use the
+ * key with any other block modes will be rejected.
*
* <p>This restriction must be specified for keys which are used for encryption/decryption.
- *
- * @hide
- */
- public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
- mBlockMode = blockMode;
- return this;
- }
-
- /**
- * Sets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
- *
- * <p>By default, there is no restriction on how frequently a key can be used.
- *
- * @hide
*/
- public Builder setMinSecondsBetweenOperations(int seconds) {
- mMinSecondsBetweenOperations = seconds;
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
return this;
}
/**
- * Sets the maximum number of times a key can be used without rebooting the device.
- *
- * <p>By default, the key can be used for an unlimited number of times.
- *
- * @hide
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited;
+ * </li>
+ * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
+ * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
+ * used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * </ul>
*/
- public Builder setMaxUsesPerBoot(int count) {
- mMaxUsesPerBoot = count;
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
return this;
}
@@ -429,12 +427,10 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* without user authentication.
*
* @see #setUserAuthenticationValidityDurationSeconds(int)
- *
- * @hide
*/
- public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
- mUserAuthenticators =
- (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
return this;
}
@@ -447,12 +443,26 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
* every use of the key.
*
+ * @see #setUserAuthenticators(int)
+ */
+ public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
+ * Sets whether this key must be invalidated (permanently) once a new fingerprint is
+ * enrolled. This only has effect if fingerprint reader is one of the user authenticators
+ * protecting access to the key.
+ *
+ * <p>By default, enrolling a new fingerprint does not invalidate the key.
+ *
* @see #setUserAuthenticators(Set)
*
* @hide
*/
- public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
- mUserAuthenticationValidityDurationSeconds = seconds;
+ public Builder setInvalidatedOnNewFingerprintEnrolled(boolean invalidated) {
+ mInvalidatedOnNewFingerprintEnrolled = invalidated;
return this;
}
@@ -462,10 +472,20 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* @throws IllegalArgumentException if a required field is missing or violates a constraint.
*/
public KeyGeneratorSpec build() {
- return new KeyGeneratorSpec(mContext, mKeystoreAlias, mFlags, mKeySize,
- mKeyValidityStart, mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd,
- mPurposes, mPadding, mBlockMode, mMinSecondsBetweenOperations, mMaxUsesPerBoot,
- mUserAuthenticators, mUserAuthenticationValidityDurationSeconds);
+ return new KeyGeneratorSpec(mContext,
+ mKeystoreAlias,
+ mFlags,
+ mKeySize,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mPaddings,
+ mBlockModes,
+ mRandomizedEncryptionRequired,
+ mUserAuthenticators,
+ mUserAuthenticationValidityDurationSeconds,
+ mInvalidatedOnNewFingerprintEnrolled);
}
}
}
diff --git a/keystore/java/android/security/KeyNotYetValidException.java b/keystore/java/android/security/KeyNotYetValidException.java
new file mode 100644
index 0000000..f1c2cac
--- /dev/null
+++ b/keystore/java/android/security/KeyNotYetValidException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Indicates that a cryptographic operation failed because the employed key's validity start date
+ * is in the future.
+ *
+ * @hide
+ */
+public class KeyNotYetValidException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} without detail message and cause.
+ */
+ public KeyNotYetValidException() {
+ super("Key not yet valid");
+ }
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} with the provided detail message and no
+ * cause.
+ */
+ public KeyNotYetValidException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} with the provided detail message and cause.
+ */
+ public KeyNotYetValidException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index cc097aa..4ca220d 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -72,6 +72,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private final int mFlags;
+ private final Date mKeyValidityStart;
+
+ private final Date mKeyValidityForOriginationEnd;
+
+ private final Date mKeyValidityForConsumptionEnd;
+
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+
+ private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
+
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+
+ private final boolean mRandomizedEncryptionRequired;
+
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+
+ private final int mUserAuthenticationValidityDurationSeconds;
+
+ private final boolean mInvalidatedOnNewFingerprintEnrolled;
+
/**
* Parameter specification for the "{@code AndroidKeyPairGenerator}"
* instance of the {@link java.security.KeyPairGenerator} API. The
@@ -106,7 +128,18 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
*/
public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
- Date startDate, Date endDate, int flags) {
+ Date startDate, Date endDate, int flags,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.DigestEnum int digests,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds,
+ boolean invalidatedOnNewFingerprintEnrolled) {
if (context == null) {
throw new IllegalArgumentException("context == null");
} else if (TextUtils.isEmpty(keyStoreAlias)) {
@@ -121,6 +154,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
throw new IllegalArgumentException("endDate == null");
} else if (endDate.before(startDate)) {
throw new IllegalArgumentException("endDate < startDate");
+ } else if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
+ throw new IllegalArgumentException(
+ "userAuthenticationValidityDurationSeconds must not be negative");
}
mContext = context;
@@ -133,6 +170,48 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mStartDate = startDate;
mEndDate = endDate;
mFlags = flags;
+ mKeyValidityStart = keyValidityStart;
+ mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
+ mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mPurposes = purposes;
+ mDigests = digests;
+ mPaddings = paddings;
+ mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
+ mUserAuthenticators = userAuthenticators;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
+ }
+
+ /**
+ * TODO: Remove this constructor once tests are switched over to the new one above.
+ * @hide
+ */
+ public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
+ AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
+ Date startDate, Date endDate, int flags) {
+
+ this(context,
+ keyStoreAlias,
+ keyType,
+ keySize,
+ spec,
+ subjectDN,
+ serialNumber,
+ startDate,
+ endDate,
+ flags,
+ startDate,
+ endDate,
+ endDate,
+ 0,
+ 0,
+ 0,
+ 0,
+ true,
+ 0,
+ -1,
+ false);
}
/**
@@ -222,6 +301,135 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Gets the time instant before which the key pair is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityStart() {
+ return mKeyValidityStart;
+ }
+
+ /**
+ * Gets the time instant after which the key pair is no longer valid for decryption and
+ * verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key pair is no longer valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the set of digests to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ return mDigests;
+ }
+
+ /**
+ * Gets the set of padding schemes to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
+ }
+
+ /**
+ * Gets the set of block modes to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
+ }
+
+ /**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
+ * Gets the set of user authenticators which protect access to the private key. The key can only
+ * be used iff the user has authenticated to at least one of these user authenticators.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which the private key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
+ *
+ * @hide
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Returns {@code true} if this key must be permanently invalidated once a new fingerprint is
+ * enrolled. This constraint only has effect if fingerprint reader is one of the user
+ * authenticators protecting access to this key.
+ *
+ * @see #getUserAuthenticators()
+ *
+ * @hide
+ */
+ public boolean isInvalidatedOnNewFingerprintEnrolled() {
+ return mInvalidatedOnNewFingerprintEnrolled;
+ }
+
+ /**
* Builder class for {@link KeyPairGeneratorSpec} objects.
* <p>
* This will build a parameter spec for use with the <a href="{@docRoot}
@@ -263,6 +471,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private int mFlags;
+ private Date mKeyValidityStart;
+
+ private Date mKeyValidityForOriginationEnd;
+
+ private Date mKeyValidityForConsumptionEnd;
+
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+
+ private @KeyStoreKeyConstraints.DigestEnum int mDigests;
+
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+
+ private boolean mRandomizedEncryptionRequired = true;
+
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+
+ private boolean mInvalidatedOnNewFingerprintEnrolled;
+
/**
* Creates a new instance of the {@code Builder} with the given
* {@code context}. The {@code context} passed in may be used to pop up
@@ -389,14 +619,230 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Sets the time instant before which the key is not yet valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityStart(Date startDate) {
+ mKeyValidityStart = startDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityStart(Date)
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ * @see #setKeyValidityForOriginationEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityEnd(Date endDate) {
+ setKeyValidityForOriginationEnd(endDate);
+ setKeyValidityForConsumptionEnd(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for encryption and signing.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityForOriginationEnd(Date endDate) {
+ mKeyValidityForOriginationEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for decryption and
+ * verification.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForOriginationEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityForConsumptionEnd(Date endDate) {
+ mKeyValidityForConsumptionEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only for the provided set of purposes.
+ *
+ * <p>This restriction must be specified. There is no default.
+ *
+ * @hide
+ */
+ public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
+ mPurposes = purposes;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided digests. Attempts to use the key
+ * with any other digests be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for signing/verification.
+ *
+ * @hide
+ */
+ public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+ mDigests = digests;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
+ * the key with any other padding will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ *
+ * @hide
+ */
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided block mode when encrypting or
+ * decrypting. Attempts to use the key with any other block modes will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ *
+ * @hide
+ */
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
+ return this;
+ }
+
+ /**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not
+ * offer {@code IND-CPA}, such as RSA without padding, are prohibited.
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets the user authenticators which protect access to this key. The key can only be used
+ * iff the user has authenticated to at least one of these user authenticators.
+ *
+ * <p>By default, the key can be used without user authentication.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
+ * without user authentication.
+ *
+ * @see #setUserAuthenticationValidityDurationSeconds(int)
+ *
+ * @hide
+ */
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
+ return this;
+ }
+
+ /**
+ * Sets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * <p>By default, the user needs to authenticate for every use of the key.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
+ * every use of the key.
+ *
+ * @see #setUserAuthenticators(int)
+ *
+ * @hide
+ */
+ public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
+ * Sets whether this key must be invalidated (permanently) once a new fingerprint is
+ * enrolled. This only has effect if fingerprint reader is one of the user authenticators
+ * protecting access to the key.
+ *
+ * <p>By default, enrolling a new fingerprint does not invalidate the key.
+ *
+ * @see #setUserAuthenticators(Set)
+ *
+ * @hide
+ */
+ public Builder setInvalidatedOnNewFingerprintEnrolled(boolean invalidated) {
+ mInvalidatedOnNewFingerprintEnrolled = invalidated;
+ return this;
+ }
+
+ /**
* Builds the instance of the {@code KeyPairGeneratorSpec}.
*
* @throws IllegalArgumentException if a required field is missing
* @return built instance of {@code KeyPairGeneratorSpec}
*/
public KeyPairGeneratorSpec build() {
- return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec,
- mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags);
+ return new KeyPairGeneratorSpec(mContext,
+ mKeystoreAlias,
+ mKeyType,
+ mKeySize,
+ mSpec,
+ mSubjectDN,
+ mSerialNumber,
+ mStartDate,
+ mEndDate,
+ mFlags,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mDigests,
+ mPaddings,
+ mBlockModes,
+ mRandomizedEncryptionRequired,
+ mUserAuthenticators,
+ mUserAuthenticationValidityDurationSeconds,
+ mInvalidatedOnNewFingerprintEnrolled);
}
}
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 94a479b..5af0527 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -26,6 +26,7 @@ import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
+import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.util.Log;
@@ -506,4 +507,60 @@ public class KeyStore {
return SYSTEM_ERROR;
}
}
+
+ public static KeyStoreException getKeyStoreException(int errorCode) {
+ if (errorCode > 0) {
+ // KeyStore layer error
+ switch (errorCode) {
+ case NO_ERROR:
+ return new KeyStoreException(errorCode, "OK");
+ case LOCKED:
+ return new KeyStoreException(errorCode, "Keystore locked");
+ case UNINITIALIZED:
+ return new KeyStoreException(errorCode, "Keystore not initialized");
+ case SYSTEM_ERROR:
+ return new KeyStoreException(errorCode, "System error");
+ case PERMISSION_DENIED:
+ return new KeyStoreException(errorCode, "Permission denied");
+ case KEY_NOT_FOUND:
+ return new KeyStoreException(errorCode, "Key not found");
+ case VALUE_CORRUPTED:
+ return new KeyStoreException(errorCode, "Key blob corrupted");
+ default:
+ return new KeyStoreException(errorCode, String.valueOf(errorCode));
+ }
+ } else {
+ // Keymaster layer error
+ switch (errorCode) {
+ case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
+ // The name of this parameter significantly differs between Keymaster and
+ // framework APIs. Use the framework wording to make life easier for developers.
+ return new KeyStoreException(errorCode,
+ "Invalid user authentication validity duration");
+ default:
+ return new KeyStoreException(errorCode,
+ KeymasterDefs.getErrorMessage(errorCode));
+ }
+ }
+ }
+
+ public static CryptoOperationException getCryptoOperationException(KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
+ return new KeyExpiredException();
+ case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
+ return new KeyNotYetValidException();
+ case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED:
+ return new UserNotAuthenticatedException();
+ // TODO: Handle TBD Keymaster error code "invalid key: new fingerprint enrolled"
+ // case KeymasterDefs.KM_ERROR_TBD
+ // return new NewFingerprintEnrolledException();
+ default:
+ return new CryptoOperationException("Crypto operation failed", e);
+ }
+ }
+
+ public static CryptoOperationException getCryptoOperationException(int errorCode) {
+ return getCryptoOperationException(getKeyStoreException(errorCode));
+ }
}
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
new file mode 100644
index 0000000..487eac0
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.os.IBinder;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation {
+
+ public abstract static class AES extends KeyStoreCipherSpi {
+ protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode,
+ @KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) {
+ super(KeyStoreKeyConstraints.Algorithm.AES,
+ blockMode,
+ padding,
+ 16,
+ ivUsed);
+ }
+
+ public abstract static class ECB extends AES {
+ protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false);
+ }
+
+ public static class NoPadding extends ECB {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends ECB {
+ public PKCS7Padding() {
+ super(KeyStoreKeyConstraints.Padding.PKCS7);
+ }
+ }
+ }
+
+ public abstract static class CBC extends AES {
+ protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true);
+ }
+
+ public static class NoPadding extends CBC {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends CBC {
+ public PKCS7Padding() {
+ super(KeyStoreKeyConstraints.Padding.PKCS7);
+ }
+ }
+ }
+
+ public abstract static class CTR extends AES {
+ protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true);
+ }
+
+ public static class NoPadding extends CTR {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+ }
+ }
+
+ private final KeyStore mKeyStore;
+ private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPadding;
+ private final int mBlockSizeBytes;
+
+ /** Whether this transformation requires an IV. */
+ private final boolean mIvRequired;
+
+ // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
+ // doFinal finishes.
+ protected boolean mEncrypting;
+ private KeyStoreSecretKey mKey;
+ private SecureRandom mRng;
+ private boolean mFirstOperationInitiated;
+ private byte[] mIv;
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ // Fields below must be reset after doFinal
+ private byte[] mAdditionalEntropyForBegin;
+
+ /**
+ * Token referencing this operation inside keystore service. It is initialized by
+ * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some
+ * error conditions in between.
+ */
+ private IBinder mOperationToken;
+ private Long mOperationHandle;
+ private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
+
+ protected KeyStoreCipherSpi(
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockMode,
+ @KeyStoreKeyConstraints.PaddingEnum int padding,
+ int blockSizeBytes,
+ boolean ivUsed) {
+ mKeyStore = KeyStore.getInstance();
+ mAlgorithm = algorithm;
+ mBlockMode = blockMode;
+ mPadding = padding;
+ mBlockSizeBytes = blockSizeBytes;
+ mIvRequired = ivUsed;
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters();
+ ensureKeystoreOperationInitialized();
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
+ throws InvalidKeyException, InvalidAlgorithmParameterException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ }
+
+ private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ resetAll();
+ if (!(key instanceof KeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ mKey = (KeyStoreSecretKey) key;
+ mRng = random;
+ mIv = null;
+ mFirstOperationInitiated = false;
+
+ if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
+ throw new UnsupportedOperationException(
+ "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
+ }
+ mEncrypting = opmode == Cipher.ENCRYPT_MODE;
+ }
+
+ private void resetAll() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mEncrypting = false;
+ mKey = null;
+ mRng = null;
+ mFirstOperationInitiated = false;
+ mIv = null;
+ mIvHasBeenUsed = false;
+ mAdditionalEntropyForBegin = null;
+ mOperationToken = null;
+ mOperationHandle = null;
+ mMainDataStreamer = null;
+ }
+
+ private void resetWhilePreservingInitState() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mOperationHandle = null;
+ mMainDataStreamer = null;
+ mAdditionalEntropyForBegin = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() {
+ if (mMainDataStreamer != null) {
+ return;
+ }
+ if (mKey == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if ((mEncrypting) && (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.");
+ }
+
+ KeymasterArguments keymasterInputArgs = new KeymasterArguments();
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm);
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode);
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding);
+ addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
+
+ KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
+ OperationResult opResult = mKeyStore.begin(
+ mKey.getAlias(),
+ mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
+ true, // permit aborting this operation if keystore runs out of resources
+ keymasterInputArgs,
+ mAdditionalEntropyForBegin,
+ keymasterOutputArgs);
+ mAdditionalEntropyForBegin = null;
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getCryptoOperationException(opResult.resultCode);
+ }
+
+ if (opResult.token == null) {
+ throw new CryptoOperationException("Keystore returned null operation token");
+ }
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
+ loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
+ mFirstOperationInitiated = true;
+ mIvHasBeenUsed = true;
+ mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mKeyStore, opResult.token));
+ }
+
+ @Override
+ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+ ensureKeystoreOperationInitialized();
+
+ if (inputLen == 0) {
+ return null;
+ }
+
+ byte[] output;
+ try {
+ output = mMainDataStreamer.update(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
+ }
+
+ if (output.length == 0) {
+ return null;
+ }
+
+ return output;
+ }
+
+ @Override
+ protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
+ int outputOffset) throws ShortBufferException {
+ byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
+ if (outputCopy == null) {
+ return 0;
+ }
+ int outputAvailable = output.length - outputOffset;
+ if (outputCopy.length > outputAvailable) {
+ throw new ShortBufferException("Output buffer too short. Produced: "
+ + outputCopy.length + ", available: " + outputAvailable);
+ }
+ System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+ return outputCopy.length;
+ }
+
+ @Override
+ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
+ throws IllegalBlockSizeException, BadPaddingException {
+ ensureKeystoreOperationInitialized();
+
+ byte[] output;
+ try {
+ output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
+ throw new IllegalBlockSizeException();
+ case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
+ throw new BadPaddingException();
+ case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
+ throw new AEADBadTagException();
+ default:
+ throw KeyStore.getCryptoOperationException(e);
+ }
+ }
+
+ resetWhilePreservingInitState();
+ return output;
+ }
+
+ @Override
+ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+ int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
+ BadPaddingException {
+ byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
+ if (outputCopy == null) {
+ return 0;
+ }
+ int outputAvailable = output.length - outputOffset;
+ if (outputCopy.length > outputAvailable) {
+ throw new ShortBufferException("Output buffer too short. Produced: "
+ + outputCopy.length + ", available: " + outputAvailable);
+ }
+ System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+ return outputCopy.length;
+ }
+
+ @Override
+ protected int engineGetBlockSize() {
+ return mBlockSizeBytes;
+ }
+
+ @Override
+ protected byte[] engineGetIV() {
+ return (mIv != null) ? mIv.clone() : null;
+ }
+
+ @Override
+ protected int engineGetOutputSize(int inputLen) {
+ return inputLen + 3 * engineGetBlockSize();
+ }
+
+ @Override
+ protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
+ // This should never be invoked because all algorithms registered with the AndroidKeyStore
+ // provide explicitly specify block mode.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void engineSetPadding(String arg0) throws NoSuchPaddingException {
+ // This should never be invoked because all algorithms registered with the AndroidKeyStore
+ // provide explicitly specify padding mode.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public Long getOperationHandle() {
+ return mOperationHandle;
+ }
+
+ // The methods below may need to be overridden by subclasses that use algorithm-specific
+ // parameters.
+
+ /**
+ * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null}
+ * if no algorithm-specific parameters are used.
+ *
+ * <p>This implementation only handles the IV parameter.
+ */
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ if (!mIvRequired) {
+ return null;
+ }
+ if ((mIv != null) && (mIv.length > 0)) {
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+ params.init(new IvParameterSpec(mIv));
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new RuntimeException(
+ "Failed to initialize AES AlgorithmParameters with an IV", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameters.
+ *
+ * @param params algorithm parameters.
+ *
+ * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
+ * automatically configured and thus {@code Cipher.init} needs to be invoked with
+ * explicitly provided parameters.
+ */
+ 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 (!mEncrypting) {
+ // 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");
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameters.
+ *
+ * @param params algorithm parameters.
+ *
+ * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
+ * automatically configured and thus {@code Cipher.init} needs to be invoked with
+ * explicitly provided parameters.
+ */
+ 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 (!mEncrypting) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ return;
+ }
+
+ IvParameterSpec ivSpec;
+ try {
+ ivSpec = params.getParameterSpec(IvParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ if (!mEncrypting) {
+ // 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");
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameter.
+ *
+ * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured
+ * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters.
+ */
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!mIvRequired) {
+ return;
+ }
+
+ // IV is used
+ if (!mEncrypting) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ /**
+ * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
+ *
+ * <p>The default implementation takes care of the IV.
+ *
+ * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
+ if (!mFirstOperationInitiated) {
+ // First begin operation -- see if we need to provide additional entropy for IV
+ // generation.
+ if (mIvRequired) {
+ // IV is needed
+ if ((mIv == null) && (mEncrypting)) {
+ // TODO: Switch to keymaster-generated IV code below once keymaster supports
+ // that.
+ // IV is needed but was not provided by the caller -- generate an IV.
+ mIv = new byte[mBlockSizeBytes];
+ SecureRandom rng = (mRng != null) ? mRng : new SecureRandom();
+ rng.nextBytes(mIv);
+// // IV was not provided by the caller and thus will be generated by keymaster.
+// // Mix in some additional entropy from the provided SecureRandom.
+// if (mRng != null) {
+// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes];
+// mRng.nextBytes(mAdditionalEntropyForBegin);
+// }
+ }
+ }
+ }
+
+ if ((mIvRequired) && (mIv != null)) {
+ keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the
+ * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after
+ * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}.
+ *
+ * <p>The default implementation only takes care of the IV.
+ *
+ * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
+ * operation.
+ */
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ KeymasterArguments keymasterArgs) {
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = keymasterArgs.getBlob(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 CryptoOperationException("IV in use differs from provided IV");
+ }
+ } else {
+ if (returnedIv != null) {
+ throw new CryptoOperationException(
+ "IV in use despite IV not being used by this transformation");
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java
index 4c465a4..8ed6e04 100644
--- a/keystore/java/android/security/KeyStoreConnectException.java
+++ b/keystore/java/android/security/KeyStoreConnectException.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
/**
diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/KeyStoreCryptoOperation.java
new file mode 100644
index 0000000..19abd05
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCryptoOperation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Cryptographic operation backed by {@link KeyStore}.
+ *
+ * @hide
+ */
+public interface KeyStoreCryptoOperation {
+ /**
+ * Gets the KeyStore operation handle of this crypto operation.
+ *
+ * @return handle or {@code null} if the KeyStore operation is not in progress.
+ */
+ Long getOperationHandle();
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
index a37ddce..1f8b7e4 100644
--- a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
@@ -1,5 +1,22 @@
+/*
+ * Copyright (C) 2015 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;
+import android.os.IBinder;
import android.security.keymaster.OperationResult;
import java.io.ByteArrayOutputStream;
@@ -10,32 +27,36 @@ import java.io.IOException;
* {@code update} and {@code finish} operations.
*
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
- * update and finish operations. Firstly, KeyStore's update and finish operations can consume only a
- * limited amount of data in one go because the operations are marshalled via Binder. Secondly, the
- * update operation may consume less data than provided, in which case the caller has to buffer
- * the remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
+ * update and finish operations. Firstly, KeyStore's update operation can consume only a limited
+ * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
+ * operation may consume less data than provided, in which case the caller has to buffer the
+ * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
* various JCA crypto primitives.
*
- * <p>KeyStore operation through which data is streamed is abstracted away as
- * {@link KeyStoreOperation} to avoid having this class deal with operation tokens and occasional
- * additional parameters to update and final operations.
+ * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
+ * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
+ * parameters to {@code update} and {@code final} operations.
*
* @hide
*/
public class KeyStoreCryptoOperationChunkedStreamer {
- public interface KeyStoreOperation {
+
+ /**
+ * Bidirectional chunked data stream over a KeyStore crypto operation.
+ */
+ public interface Stream {
/**
- * Returns the result of the KeyStore update operation or null if keystore couldn't be
- * reached.
+ * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
+ * be reached.
*/
OperationResult update(byte[] input);
/**
- * Returns the result of the KeyStore finish operation or null if keystore couldn't be
- * reached.
+ * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
+ * be reached.
*/
- OperationResult finish(byte[] input);
+ OperationResult finish();
}
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
@@ -43,23 +64,23 @@ public class KeyStoreCryptoOperationChunkedStreamer {
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
- private final KeyStoreOperation mKeyStoreOperation;
+ private final Stream mKeyStoreStream;
private final int mMaxChunkSize;
private byte[] mBuffered = EMPTY_BYTE_ARRAY;
private int mBufferedOffset;
private int mBufferedLength;
- public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation) {
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_MAX_CHUNK_SIZE);
}
- public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation, int maxChunkSize) {
- mKeyStoreOperation = operation;
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) {
+ mKeyStoreStream = operation;
mMaxChunkSize = maxChunkSize;
}
- public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException {
+ public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
if (inputLength == 0) {
// No input provided
return EMPTY_BYTE_ARRAY;
@@ -73,10 +94,9 @@ public class KeyStoreCryptoOperationChunkedStreamer {
if ((mBufferedLength + inputLength) > mMaxChunkSize) {
// Too much input for one chunk -- extract one max-sized chunk and feed it into the
// update operation.
- chunk = new byte[mMaxChunkSize];
- System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
- inputBytesInChunk = chunk.length - mBufferedLength;
- System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputBytesInChunk);
+ inputBytesInChunk = mMaxChunkSize - mBufferedLength;
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
} else {
// All of available input fits into one chunk.
if ((mBufferedLength == 0) && (inputOffset == 0)
@@ -87,21 +107,20 @@ public class KeyStoreCryptoOperationChunkedStreamer {
inputBytesInChunk = input.length;
} else {
// Need to combine buffered data with input data into one array.
- chunk = new byte[mBufferedLength + inputLength];
inputBytesInChunk = inputLength;
- System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
- System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputLength);
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
}
}
// Update input array references to reflect that some of its bytes are now in mBuffered.
inputOffset += inputBytesInChunk;
inputLength -= inputBytesInChunk;
- OperationResult opResult = mKeyStoreOperation.update(chunk);
+ OperationResult opResult = mKeyStoreStream.update(chunk);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
- throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
}
if (opResult.inputConsumed == chunk.length) {
@@ -169,60 +188,122 @@ public class KeyStoreCryptoOperationChunkedStreamer {
}
public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
- throws KeymasterException {
+ throws KeyStoreException {
if (inputLength == 0) {
// No input provided -- simplify the rest of the code
input = EMPTY_BYTE_ARRAY;
inputOffset = 0;
}
- byte[] updateOutput = null;
- if ((mBufferedLength + inputLength) > mMaxChunkSize) {
- updateOutput = update(input, inputOffset, inputLength);
- inputOffset += inputLength;
- inputLength = 0;
+ // Flush all buffered input and provided input into keystore/keymaster.
+ byte[] output = update(input, inputOffset, inputLength);
+ output = concat(output, flush());
+
+ OperationResult opResult = mKeyStoreStream.finish();
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
}
- // All of available input fits into one chunk.
- byte[] finalChunk;
- if ((mBufferedLength == 0) && (inputOffset == 0)
- && (inputLength == input.length)) {
- // Nothing buffered and all of input array needs to be fed into the finish operation.
- finalChunk = input;
- } else {
- // Need to combine buffered data with input data into one array.
- finalChunk = new byte[mBufferedLength + inputLength];
- System.arraycopy(mBuffered, mBufferedOffset, finalChunk, 0, mBufferedLength);
- System.arraycopy(input, inputOffset, finalChunk, mBufferedLength, inputLength);
+ return concat(output, opResult.output);
+ }
+
+ /**
+ * Passes all of buffered input into the the KeyStore operation (via the {@code update}
+ * operation) and returns output.
+ */
+ public byte[] flush() throws KeyStoreException {
+ if (mBufferedLength <= 0) {
+ return EMPTY_BYTE_ARRAY;
}
+
+ byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength);
mBuffered = EMPTY_BYTE_ARRAY;
mBufferedLength = 0;
mBufferedOffset = 0;
- OperationResult opResult = mKeyStoreOperation.finish(finalChunk);
+ OperationResult opResult = mKeyStoreStream.update(chunk);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
- throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
}
- if (opResult.inputConsumed != finalChunk.length) {
- throw new CryptoOperationException("Unexpected number of bytes consumed by finish: "
- + opResult.inputConsumed + " instead of " + finalChunk.length);
+ if (opResult.inputConsumed < chunk.length) {
+ throw new CryptoOperationException("Keystore failed to consume all input. Provided: "
+ + chunk.length + ", consumed: " + opResult.inputConsumed);
+ } else if (opResult.inputConsumed > chunk.length) {
+ throw new CryptoOperationException("Keystore consumed more input than provided"
+ + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed);
}
- // Return the concatenation of the output of update and finish.
- byte[] result;
- byte[] finishOutput = opResult.output;
- if ((updateOutput == null) || (updateOutput.length == 0)) {
- result = finishOutput;
- } else if ((finishOutput == null) || (finishOutput.length == 0)) {
- result = updateOutput;
+ return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY;
+ }
+
+ private static byte[] concat(byte[] arr1, byte[] arr2) {
+ if ((arr1 == null) || (arr1.length == 0)) {
+ return arr2;
+ } else if ((arr2 == null) || (arr2.length == 0)) {
+ return arr1;
} else {
- result = new byte[updateOutput.length + finishOutput.length];
- System.arraycopy(updateOutput, 0, result, 0, updateOutput.length);
- System.arraycopy(finishOutput, 0, result, updateOutput.length, finishOutput.length);
+ byte[] result = new byte[arr1.length + arr2.length];
+ System.arraycopy(arr1, 0, result, 0, arr1.length);
+ System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
+ return result;
+ }
+ }
+
+ private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2,
+ int len2) {
+ if (len1 == 0) {
+ return subarray(arr2, offset2, len2);
+ } else if (len2 == 0) {
+ return subarray(arr1, offset1, len1);
+ } else {
+ byte[] result = new byte[len1 + len2];
+ System.arraycopy(arr1, offset1, result, 0, len1);
+ System.arraycopy(arr2, offset2, result, len1, len2);
+ return result;
+ }
+ }
+
+ private static byte[] subarray(byte[] arr, int offset, int len) {
+ if (len == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ if ((offset == 0) && (len == arr.length)) {
+ return arr;
+ }
+ byte[] result = new byte[len];
+ System.arraycopy(arr, offset, result, 0, len);
+ return result;
+ }
+
+ /**
+ * Main data stream via a KeyStore streaming operation.
+ *
+ * <p>For example, for an encryption operation, this is the stream through which plaintext is
+ * provided and ciphertext is obtained.
+ */
+ public static class MainDataStream implements Stream {
+
+ private final KeyStore mKeyStore;
+ private final IBinder mOperationToken;
+
+ public MainDataStream(KeyStore keyStore, IBinder operationToken) {
+ mKeyStore = keyStore;
+ mOperationToken = operationToken;
+ }
+
+ @Override
+ public OperationResult update(byte[] input) {
+ return mKeyStore.update(mOperationToken, null, input);
+ }
+
+ @Override
+ public OperationResult finish() {
+ return mKeyStore.finish(mOperationToken, null, null);
}
- return (result != null) ? result : EMPTY_BYTE_ARRAY;
}
}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
new file mode 100644
index 0000000..88e768c
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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/keymaster exception with positive error codes coming from the KeyStore and negative
+ * ones from keymaster.
+ *
+ * @hide
+ */
+public class KeyStoreException extends Exception {
+
+ private final int mErrorCode;
+
+ public KeyStoreException(int errorCode, String message) {
+ super(message);
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index 3080d7b..f69e7d1 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
import android.os.IBinder;
@@ -17,11 +33,35 @@ import javax.crypto.MacSpi;
*
* @hide
*/
-public abstract class KeyStoreHmacSpi extends MacSpi {
+public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
+
+ public static class HmacSHA1 extends KeyStoreHmacSpi {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends KeyStoreHmacSpi {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
public static class HmacSHA256 extends KeyStoreHmacSpi {
public HmacSHA256() {
- super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8);
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends KeyStoreHmacSpi {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends KeyStoreHmacSpi {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
}
}
@@ -34,10 +74,11 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
// The fields below are reset by the engineReset operation.
private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
private IBinder mOperationToken;
+ private Long mOperationHandle;
- protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) {
+ protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest) {
mDigest = digest;
- mMacSizeBytes = macSizeBytes;
+ mMacSizeBytes = KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
}
@Override
@@ -61,7 +102,11 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
}
mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
+ if (mKeyAliasInKeyStore == null) {
+ throw new InvalidKeyException("Key's KeyStore alias not known");
+ }
engineReset();
+ ensureKeystoreOperationInitialized();
}
@Override
@@ -71,9 +116,20 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
+ mOperationHandle = null;
mChunkedStreamer = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() {
+ if (mChunkedStreamer != null) {
+ return;
+ }
+ if (mKeyAliasInKeyStore == null) {
+ throw new IllegalStateException("Not initialized");
+ }
KeymasterArguments keymasterArgs = new KeymasterArguments();
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeyStoreKeyConstraints.Algorithm.HMAC);
keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest);
OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
@@ -85,15 +141,16 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
- throw new CryptoOperationException("Failed to start keystore operation",
- KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode));
+ throw KeyStore.getCryptoOperationException(opResult.resultCode);
}
- mOperationToken = opResult.token;
- if (mOperationToken == null) {
+ if (opResult.token == null) {
throw new CryptoOperationException("Keystore returned null operation token");
}
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
- new KeyStoreStreamingConsumer(mKeyStore, mOperationToken));
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mKeyStore, mOperationToken));
}
@Override
@@ -103,15 +160,13 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
@Override
protected void engineUpdate(byte[] input, int offset, int len) {
- if (mChunkedStreamer == null) {
- throw new IllegalStateException("Not initialized");
- }
+ ensureKeystoreOperationInitialized();
byte[] output;
try {
output = mChunkedStreamer.update(input, offset, len);
- } catch (KeymasterException e) {
- throw new CryptoOperationException("Keystore operation failed", e);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
}
if ((output != null) && (output.length != 0)) {
throw new CryptoOperationException("Update operation unexpectedly produced output");
@@ -120,15 +175,13 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
@Override
protected byte[] engineDoFinal() {
- if (mChunkedStreamer == null) {
- throw new IllegalStateException("Not initialized");
- }
+ ensureKeystoreOperationInitialized();
byte[] result;
try {
result = mChunkedStreamer.doFinal(null, 0, 0);
- } catch (KeymasterException e) {
- throw new CryptoOperationException("Keystore operation failed", e);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
}
engineReset();
@@ -140,7 +193,6 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
- mOperationToken = null;
mKeyStore.abort(operationToken);
}
} finally {
@@ -148,27 +200,8 @@ public abstract class KeyStoreHmacSpi extends MacSpi {
}
}
- /**
- * KeyStore-backed consumer of {@code MacSpi}'s chunked stream.
- */
- private static class KeyStoreStreamingConsumer
- implements KeyStoreCryptoOperationChunkedStreamer.KeyStoreOperation {
- private final KeyStore mKeyStore;
- private final IBinder mOperationToken;
-
- private KeyStoreStreamingConsumer(KeyStore keyStore, IBinder operationToken) {
- mKeyStore = keyStore;
- mOperationToken = operationToken;
- }
-
- @Override
- public OperationResult update(byte[] input) {
- return mKeyStore.update(mOperationToken, null, input);
- }
-
- @Override
- public OperationResult finish(byte[] input) {
- return mKeyStore.finish(mOperationToken, null, input);
- }
+ @Override
+ public Long getOperationHandle() {
+ return mOperationHandle;
}
}
diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
new file mode 100644
index 0000000..1f5d400
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.annotation.IntDef;
+import android.security.keymaster.KeymasterDefs;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Characteristics of {@code AndroidKeyStore} keys.
+ *
+ * @hide
+ */
+public abstract class KeyStoreKeyCharacteristics {
+ private KeyStoreKeyCharacteristics() {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Origin.GENERATED, Origin.IMPORTED})
+ public @interface OriginEnum {}
+
+ /**
+ * Origin of the key.
+ */
+ public static abstract class Origin {
+ private Origin() {}
+
+ /** Key was generated inside AndroidKeyStore. */
+ public static final int GENERATED = 1 << 0;
+
+ /** Key was imported into AndroidKeyStore. */
+ public static final int IMPORTED = 1 << 1;
+
+ /**
+ * @hide
+ */
+ public static @OriginEnum int fromKeymaster(int origin) {
+ switch (origin) {
+ case KeymasterDefs.KM_ORIGIN_HARDWARE:
+ return GENERATED;
+ case KeymasterDefs.KM_ORIGIN_IMPORTED:
+ return IMPORTED;
+ default:
+ throw new IllegalArgumentException("Unknown origin: " + origin);
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index b5e2436..98ac3e7 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
import android.annotation.IntDef;
@@ -5,7 +21,6 @@ import android.security.keymaster.KeymasterDefs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
@@ -18,7 +33,8 @@ public abstract class KeyStoreKeyConstraints {
private KeyStoreKeyConstraints() {}
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
+ @IntDef(flag = true,
+ value = {Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
public @interface PurposeEnum {}
/**
@@ -48,11 +64,6 @@ public abstract class KeyStoreKeyConstraints {
public static final int VERIFY = 1 << 3;
/**
- * Number of flags defined above. Needs to be kept in sync with the flags above.
- */
- private static final int VALUE_COUNT = 4;
-
- /**
* @hide
*/
public static int toKeymaster(@PurposeEnum int purpose) {
@@ -91,22 +102,12 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
- public static int[] allToKeymaster(int purposes) {
- int[] result = new int[VALUE_COUNT];
- int resultCount = 0;
- int purpose = 1;
- for (int i = 0; i < 32; i++) {
- if ((purposes & 1) != 0) {
- result[resultCount] = toKeymaster(purpose);
- resultCount++;
- }
- purposes >>>= 1;
- purpose <<= 1;
- if (purposes == 0) {
- break;
- }
+ public static int[] allToKeymaster(@PurposeEnum int purposes) {
+ int[] result = getSetFlags(purposes);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
}
- return Arrays.copyOf(result, resultCount);
+ return result;
}
/**
@@ -122,7 +123,7 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Algorithm.AES, Algorithm.HMAC})
+ @IntDef({Algorithm.AES, Algorithm.HMAC, Algorithm.RSA, Algorithm.EC})
public @interface AlgorithmEnum {}
/**
@@ -134,12 +135,22 @@ public abstract class KeyStoreKeyConstraints {
/**
* Key algorithm: AES.
*/
- public static final int AES = 0;
+ public static final int AES = 1 << 0;
/**
* Key algorithm: HMAC.
*/
- public static final int HMAC = 1;
+ public static final int HMAC = 1 << 1;
+
+ /**
+ * Key algorithm: RSA.
+ */
+ public static final int RSA = 1 << 2;
+
+ /**
+ * Key algorithm: EC.
+ */
+ public static final int EC = 1 << 3;
/**
* @hide
@@ -150,6 +161,10 @@ public abstract class KeyStoreKeyConstraints {
return KeymasterDefs.KM_ALGORITHM_AES;
case HMAC:
return KeymasterDefs.KM_ALGORITHM_HMAC;
+ case RSA:
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ case EC:
+ return KeymasterDefs.KM_ALGORITHM_EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -164,6 +179,10 @@ public abstract class KeyStoreKeyConstraints {
return AES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
return HMAC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -178,6 +197,10 @@ public abstract class KeyStoreKeyConstraints {
return "AES";
case HMAC:
return "HMAC";
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -212,8 +235,18 @@ public abstract class KeyStoreKeyConstraints {
throw new IllegalArgumentException("HMAC digest not specified");
}
switch (digest) {
+ case Digest.MD5:
+ return "HmacMD5";
+ case Digest.SHA1:
+ return "HmacSHA1";
+ case Digest.SHA224:
+ return "HmacSHA224";
case Digest.SHA256:
return "HmacSHA256";
+ case Digest.SHA384:
+ return "HmacSHA384";
+ case Digest.SHA512:
+ return "HmacSHA512";
default:
throw new IllegalArgumentException(
"Unsupported HMAC digest: " + digest);
@@ -228,6 +261,10 @@ public abstract class KeyStoreKeyConstraints {
*/
public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
switch (algorithm) {
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
default:
throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
}
@@ -235,7 +272,15 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7})
+ @IntDef(flag = true,
+ value = {
+ Padding.NONE,
+ Padding.PKCS7,
+ Padding.RSA_PKCS1_ENCRYPTION,
+ Padding.RSA_PKCS1_SIGNATURE,
+ Padding.RSA_OAEP,
+ Padding.RSA_PSS,
+ })
public @interface PaddingEnum {}
/**
@@ -247,17 +292,32 @@ public abstract class KeyStoreKeyConstraints {
/**
* No padding.
*/
- public static final int NONE = 0;
+ public static final int NONE = 1 << 0;
/**
- * Pad with zeros.
+ * PKCS#7 padding.
*/
- public static final int ZERO = 1;
+ public static final int PKCS7 = 1 << 1;
/**
- * PKCS#7 padding.
+ * RSA PKCS#1 v1.5 padding for encryption/decryption.
*/
- public static final int PKCS7 = 2;
+ public static final int RSA_PKCS1_ENCRYPTION = 1 << 2;
+
+ /**
+ * RSA PKCS#1 v1.5 padding for signatures.
+ */
+ public static final int RSA_PKCS1_SIGNATURE = 1 << 3;
+
+ /**
+ * RSA Optimal Asymmetric Encryption Padding (OAEP).
+ */
+ public static final int RSA_OAEP = 1 << 4;
+
+ /**
+ * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
+ */
+ public static final int RSA_PSS = 1 << 5;
/**
* @hide
@@ -266,10 +326,16 @@ public abstract class KeyStoreKeyConstraints {
switch (padding) {
case NONE:
return KeymasterDefs.KM_PAD_NONE;
- case ZERO:
- return KeymasterDefs.KM_PAD_ZERO;
case PKCS7:
return KeymasterDefs.KM_PAD_PKCS7;
+ case RSA_PKCS1_ENCRYPTION:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ case RSA_PKCS1_SIGNATURE:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
+ case RSA_OAEP:
+ return KeymasterDefs.KM_PAD_RSA_OAEP;
+ case RSA_PSS:
+ return KeymasterDefs.KM_PAD_RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -282,10 +348,16 @@ public abstract class KeyStoreKeyConstraints {
switch (padding) {
case KeymasterDefs.KM_PAD_NONE:
return NONE;
- case KeymasterDefs.KM_PAD_ZERO:
- return ZERO;
case KeymasterDefs.KM_PAD_PKCS7:
return PKCS7;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return RSA_PKCS1_ENCRYPTION;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
+ return RSA_PKCS1_SIGNATURE;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ return RSA_OAEP;
+ case KeymasterDefs.KM_PAD_RSA_PSS:
+ return RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -298,18 +370,75 @@ public abstract class KeyStoreKeyConstraints {
switch (padding) {
case NONE:
return "NONE";
- case ZERO:
- return "ZERO";
case PKCS7:
return "PKCS#7";
+ case RSA_PKCS1_ENCRYPTION:
+ return "RSA PKCS#1 (encryption)";
+ case RSA_PKCS1_SIGNATURE:
+ return "RSA PKCS#1 (signature)";
+ case RSA_OAEP:
+ return "RSA OAEP";
+ case RSA_PSS:
+ return "RSA PSS";
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
}
+
+ /**
+ * @hide
+ */
+ public static @PaddingEnum int fromJCACipherPadding(String padding) {
+ String paddingLower = padding.toLowerCase(Locale.US);
+ if ("nopadding".equals(paddingLower)) {
+ return NONE;
+ } else if ("pkcs7padding".equals(paddingLower)) {
+ return PKCS7;
+ } else if ("pkcs1padding".equals(paddingLower)) {
+ return RSA_PKCS1_ENCRYPTION;
+ } else if (("oaeppadding".equals(paddingLower))
+ || ((paddingLower.startsWith("oaepwith"))
+ && (paddingLower.endsWith("padding")))) {
+ return RSA_OAEP;
+ } else {
+ throw new IllegalArgumentException("Unknown padding: " + padding);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int[] allToKeymaster(@PaddingEnum int paddings) {
+ int[] result = getSetFlags(paddings);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @PaddingEnum int allFromKeymaster(Collection<Integer> paddings) {
+ @PaddingEnum int result = 0;
+ for (int keymasterPadding : paddings) {
+ result |= fromKeymaster(keymasterPadding);
+ }
+ return result;
+ }
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Digest.NONE, Digest.SHA256})
+ @IntDef(flag = true,
+ value = {
+ Digest.NONE,
+ Digest.MD5,
+ Digest.SHA1,
+ Digest.SHA224,
+ Digest.SHA256,
+ Digest.SHA384,
+ Digest.SHA512,
+ })
public @interface DigestEnum {}
/**
@@ -322,12 +451,37 @@ public abstract class KeyStoreKeyConstraints {
/**
* No digest: sign/authenticate the raw message.
*/
- public static final int NONE = 0;
+ public static final int NONE = 1 << 0;
+
+ /**
+ * MD5 digest.
+ */
+ public static final int MD5 = 1 << 1;
+
+ /**
+ * SHA-1 digest.
+ */
+ public static final int SHA1 = 1 << 2;
+
+ /**
+ * SHA-2 224 (aka SHA-224) digest.
+ */
+ public static final int SHA224 = 1 << 3;
+
+ /**
+ * SHA-2 256 (aka SHA-256) digest.
+ */
+ public static final int SHA256 = 1 << 4;
/**
- * SHA-256 digest.
+ * SHA-2 384 (aka SHA-384) digest.
*/
- public static final int SHA256 = 1;
+ public static final int SHA384 = 1 << 5;
+
+ /**
+ * SHA-2 512 (aka SHA-512) digest.
+ */
+ public static final int SHA512 = 1 << 6;
/**
* @hide
@@ -336,8 +490,18 @@ public abstract class KeyStoreKeyConstraints {
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA-1";
+ case SHA224:
+ return "SHA-224";
case SHA256:
- return "SHA256";
+ return "SHA-256";
+ case SHA384:
+ return "SHA-384";
+ case SHA512:
+ return "SHA-512";
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -346,12 +510,40 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
+ public static String allToString(@DigestEnum int digests) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@DigestEnum int digest : getSetFlags(digests)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(digest));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
public static int toKeymaster(@DigestEnum int digest) {
switch (digest) {
case NONE:
return KeymasterDefs.KM_DIGEST_NONE;
+ case MD5:
+ return KeymasterDefs.KM_DIGEST_MD5;
+ case SHA1:
+ return KeymasterDefs.KM_DIGEST_SHA1;
+ case SHA224:
+ return KeymasterDefs.KM_DIGEST_SHA_2_224;
case SHA256:
return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ case SHA384:
+ return KeymasterDefs.KM_DIGEST_SHA_2_384;
+ case SHA512:
+ return KeymasterDefs.KM_DIGEST_SHA_2_512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -364,8 +556,18 @@ public abstract class KeyStoreKeyConstraints {
switch (digest) {
case KeymasterDefs.KM_DIGEST_NONE:
return NONE;
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return MD5;
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return SHA1;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return SHA224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return SHA256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return SHA384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return SHA512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -374,14 +576,46 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
+ public static int[] allToKeymaster(@DigestEnum int digests) {
+ int[] result = getSetFlags(digests);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @DigestEnum int allFromKeymaster(Collection<Integer> digests) {
+ @DigestEnum int result = 0;
+ for (int keymasterDigest : digests) {
+ result |= fromKeymaster(keymasterDigest);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
String algorithmLower = algorithm.toLowerCase(Locale.US);
if (algorithmLower.startsWith("hmac")) {
- if ("hmacsha256".equals(algorithmLower)) {
+ String digestLower = algorithmLower.substring("hmac".length());
+ if ("md5".equals(digestLower)) {
+ return MD5;
+ } else if ("sha1".equals(digestLower)) {
+ return SHA1;
+ } else if ("sha224".equals(digestLower)) {
+ return SHA224;
+ } else if ("sha256".equals(digestLower)) {
return SHA256;
+ } else if ("sha384".equals(digestLower)) {
+ return SHA384;
+ } else if ("sha512".equals(digestLower)) {
+ return SHA512;
} else {
- throw new IllegalArgumentException("Unsupported digest: "
- + algorithmLower.substring("hmac".length()));
+ throw new IllegalArgumentException("Unsupported digest: " + digestLower);
}
} else {
return null;
@@ -395,8 +629,18 @@ public abstract class KeyStoreKeyConstraints {
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA1";
+ case SHA224:
+ return "SHA224";
case SHA256:
return "SHA256";
+ case SHA384:
+ return "SHA384";
+ case SHA512:
+ return "SHA512";
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -409,8 +653,18 @@ public abstract class KeyStoreKeyConstraints {
switch (digest) {
case NONE:
return null;
+ case MD5:
+ return 128 / 8;
+ case SHA1:
+ return 160 / 8;
+ case SHA224:
+ return 224 / 8;
case SHA256:
return 256 / 8;
+ case SHA384:
+ return 384 / 8;
+ case SHA512:
+ return 512 / 8;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -418,7 +672,8 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({BlockMode.ECB})
+ @IntDef(flag = true,
+ value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR, BlockMode.GCM})
public @interface BlockModeEnum {}
/**
@@ -427,10 +682,25 @@ public abstract class KeyStoreKeyConstraints {
public static abstract class BlockMode {
private BlockMode() {}
+ /** Electronic Codebook (ECB) block mode. */
+ public static final int ECB = 1 << 0;
+
+ /** Cipher Block Chaining (CBC) block mode. */
+ public static final int CBC = 1 << 1;
+
+ /** Counter (CTR) block mode. */
+ public static final int CTR = 1 << 2;
+
+ /** Galois/Counter Mode (GCM) block mode. */
+ public static final int GCM = 1 << 3;
+
/**
- * Electronic Codebook (ECB) block mode.
+ * Set of block modes compatible with IND-CPA if used correctly.
+ *
+ * @hide
*/
- public static final int ECB = 0;
+ public static final @BlockModeEnum int IND_CPA_COMPATIBLE_MODES =
+ CBC | CTR | GCM;
/**
* @hide
@@ -439,6 +709,12 @@ public abstract class KeyStoreKeyConstraints {
switch (mode) {
case ECB:
return KeymasterDefs.KM_MODE_ECB;
+ case CBC:
+ return KeymasterDefs.KM_MODE_CBC;
+ case CTR:
+ return KeymasterDefs.KM_MODE_CTR;
+ case GCM:
+ return KeymasterDefs.KM_MODE_GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -451,6 +727,12 @@ public abstract class KeyStoreKeyConstraints {
switch (mode) {
case KeymasterDefs.KM_MODE_ECB:
return ECB;
+ case KeymasterDefs.KM_MODE_CBC:
+ return CBC;
+ case KeymasterDefs.KM_MODE_CTR:
+ return CTR;
+ case KeymasterDefs.KM_MODE_GCM:
+ return GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -459,13 +741,206 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
+ public static int[] allToKeymaster(@BlockModeEnum int modes) {
+ int[] result = getSetFlags(modes);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @BlockModeEnum int allFromKeymaster(Collection<Integer> modes) {
+ @BlockModeEnum int result = 0;
+ for (int keymasterMode : modes) {
+ result |= fromKeymaster(keymasterMode);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
public static String toString(@BlockModeEnum int mode) {
switch (mode) {
case ECB:
return "ECB";
+ case CBC:
+ return "CBC";
+ case CTR:
+ return "CTR";
+ case GCM:
+ return "GCM";
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
}
+
+ /**
+ * @hide
+ */
+ public static String allToString(@BlockModeEnum int modes) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@BlockModeEnum int mode : getSetFlags(modes)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(mode));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public static @BlockModeEnum int fromJCAMode(String mode) {
+ String modeLower = mode.toLowerCase(Locale.US);
+ if ("ecb".equals(modeLower)) {
+ return ECB;
+ } else if ("cbc".equals(modeLower)) {
+ return CBC;
+ } else if ("ctr".equals(modeLower)) {
+ return CTR;
+ } else if ("gcm".equals(modeLower)) {
+ return GCM;
+ } else {
+ throw new IllegalArgumentException("Unknown block mode: " + mode);
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {UserAuthenticator.LOCK_SCREEN})
+ public @interface UserAuthenticatorEnum {}
+
+ /**
+ * User authenticators which can be used to restrict/protect access to keys.
+ */
+ public static abstract class UserAuthenticator {
+ private UserAuthenticator() {}
+
+ /** Lock screen. */
+ public static final int LOCK_SCREEN = 1 << 0;
+
+ /** Fingerprint reader/sensor. */
+ public static final int FINGERPRINT_READER = 1 << 1;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) {
+ switch (userAuthenticator) {
+ case LOCK_SCREEN:
+ return KeymasterDefs.HW_AUTH_PASSWORD;
+ case FINGERPRINT_READER:
+ return KeymasterDefs.HW_AUTH_FINGERPRINT;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) {
+ switch (userAuthenticator) {
+ case KeymasterDefs.HW_AUTH_PASSWORD:
+ return LOCK_SCREEN;
+ case FINGERPRINT_READER:
+ return FINGERPRINT_READER;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int allToKeymaster(@UserAuthenticatorEnum int userAuthenticators) {
+ int result = 0;
+ int userAuthenticator = 1;
+ while (userAuthenticators != 0) {
+ if ((userAuthenticators & 1) != 0) {
+ result |= toKeymaster(userAuthenticator);
+ }
+ userAuthenticators >>>= 1;
+ userAuthenticator <<= 1;
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @UserAuthenticatorEnum int allFromKeymaster(int userAuthenticators) {
+ @UserAuthenticatorEnum int result = 0;
+ int userAuthenticator = 1;
+ while (userAuthenticators != 0) {
+ if ((userAuthenticators & 1) != 0) {
+ result |= fromKeymaster(userAuthenticator);
+ }
+ userAuthenticators >>>= 1;
+ userAuthenticator <<= 1;
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static String toString(@UserAuthenticatorEnum int userAuthenticator) {
+ switch (userAuthenticator) {
+ case LOCK_SCREEN:
+ return "LOCK_SCREEN";
+ case FINGERPRINT_READER:
+ return "FINGERPRINT_READER";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+ }
+
+ private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+ private static int[] getSetFlags(int flags) {
+ if (flags == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ int result[] = new int[getSetBitCount(flags)];
+ int resultOffset = 0;
+ int flag = 1;
+ while (flags != 0) {
+ if ((flags & 1) != 0) {
+ result[resultOffset] = flag;
+ resultOffset++;
+ }
+ flags >>>= 1;
+ flag <<= 1;
+ }
+ return result;
+ }
+
+ private static int getSetBitCount(int value) {
+ if (value == 0) {
+ return 0;
+ }
+ int result = 0;
+ while (value != 0) {
+ if ((value & 1) != 0) {
+ result++;
+ }
+ value >>>= 1;
+ }
+ return result;
}
}
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index f1f9436..b39d16d 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
import android.security.keymaster.KeyCharacteristics;
@@ -7,6 +23,7 @@ import android.security.keymaster.KeymasterDefs;
import java.security.InvalidAlgorithmParameterException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.SecretKey;
@@ -24,18 +41,47 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
}
}
- public static class HmacSHA256 extends KeyStoreKeyGeneratorSpi {
- public HmacSHA256() {
+ protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi {
+ protected HmacBase(@KeyStoreKeyConstraints.DigestEnum int digest) {
super(KeyStoreKeyConstraints.Algorithm.HMAC,
- KeyStoreKeyConstraints.Digest.SHA256,
- KeyStoreKeyConstraints.Digest.getOutputSizeBytes(
- KeyStoreKeyConstraints.Digest.SHA256) * 8);
+ digest,
+ KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest) * 8);
+ }
+ }
+
+ public static class HmacSHA1 extends HmacBase {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends HmacBase {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
+
+ public static class HmacSHA256 extends HmacBase {
+ public HmacSHA256() {
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends HmacBase {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends HmacBase {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
}
}
private final KeyStore mKeyStore = KeyStore.getInstance();
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
- private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mDigest;
+ private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
private final int mDefaultKeySizeBits;
private KeyGeneratorSpec mSpec;
@@ -76,12 +122,6 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
if (mDigest != null) {
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
- }
- if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
- if (mDigest == null) {
- throw new IllegalStateException("Digest algorithm must be specified for key"
- + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
- }
Integer digestOutputSizeBytes =
KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
if (digestOutputSizeBytes != null) {
@@ -90,60 +130,68 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
}
}
+ if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+ if (mDigest == null) {
+ throw new IllegalStateException("Digest algorithm must be specified for key"
+ + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
+ }
+ }
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
- @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
- ? spec.getPurposes()
- : (KeyStoreKeyConstraints.Purpose.ENCRYPT
- | KeyStoreKeyConstraints.Purpose.DECRYPT
- | KeyStoreKeyConstraints.Purpose.SIGN
- | KeyStoreKeyConstraints.Purpose.VERIFY);
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = spec.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new IllegalStateException(
+ "Randomized encryption (IND-CPA) required but may be violated by block"
+ + " mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyGeneratorSpec documentation.");
+ }
+ }
+
for (int keymasterPurpose :
KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- if (spec.getBlockMode() != null) {
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
- KeyStoreKeyConstraints.BlockMode.toKeymaster(spec.getBlockMode()));
- }
- if (spec.getPadding() != null) {
- args.addInt(KeymasterDefs.KM_TAG_PADDING,
- KeyStoreKeyConstraints.Padding.toKeymaster(spec.getPadding()));
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
- if (spec.getMaxUsesPerBoot() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, spec.getMaxUsesPerBoot());
+ for (int keymasterPadding :
+ KeyStoreKeyConstraints.Padding.allToKeymaster(spec.getPaddings())) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
}
- if (spec.getMinSecondsBetweenOperations() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
- spec.getMinSecondsBetweenOperations());
- }
- if (spec.getUserAuthenticators().isEmpty()) {
+ if (spec.getUserAuthenticators() == 0) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
} else {
- // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized
-// for (int userAuthenticatorId : spec.getUserAuthenticators()) {
-// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId);
-// }
+ args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
+ KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster(
+ spec.getUserAuthenticators()));
+ }
+ if (spec.isInvalidatedOnNewFingerprintEnrolled()) {
+ // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports
+ // that.
}
- if (spec.getUserAuthenticationValidityDurationSeconds() != null) {
+ if (spec.getUserAuthenticationValidityDurationSeconds() != -1) {
args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
}
- if (spec.getKeyValidityStart() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart());
- }
- if (spec.getKeyValidityForOriginationEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
- spec.getKeyValidityForOriginationEnd());
- }
- if (spec.getKeyValidityForConsumptionEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
- spec.getKeyValidityForConsumptionEnd());
- }
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (spec.getKeyValidityStart() != null)
+ ? spec.getKeyValidityStart() : new Date(0));
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (spec.getKeyValidityForOriginationEnd() != null)
+ ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (spec.getKeyValidityForConsumptionEnd() != null)
+ ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed due to the Cipher abstraction.
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
@@ -159,8 +207,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
int errorCode = mKeyStore.generateKey(
keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics());
if (errorCode != KeyStore.NO_ERROR) {
- throw new CryptoOperationException("Failed to generate key",
- KeymasterUtils.getExceptionForKeymasterError(errorCode));
+ throw KeyStore.getCryptoOperationException(errorCode);
}
String keyAlgorithmJCA =
KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(mAlgorithm, mDigest);
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
new file mode 100644
index 0000000..65bb236
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 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;
+
+import java.security.spec.KeySpec;
+import java.util.Date;
+
+/**
+ * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android
+ * KeyStore</a>.
+ *
+ * @hide
+ */
+public class KeyStoreKeySpec implements KeySpec {
+ private final String mKeystoreAlias;
+ private final int mKeySize;
+ private final boolean mTeeBacked;
+ private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mTeeEnforcedUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mInvalidatedOnNewFingerprintEnrolled;
+
+ /**
+ * @hide
+ */
+ KeyStoreKeySpec(String keystoreKeyAlias,
+ boolean teeBacked,
+ @KeyStoreKeyCharacteristics.OriginEnum int origin,
+ int keySize,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.DigestEnum int digests,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators,
+ int userAuthenticationValidityDurationSeconds,
+ boolean invalidatedOnNewFingerprintEnrolled) {
+ mKeystoreAlias = keystoreKeyAlias;
+ mTeeBacked = teeBacked;
+ mOrigin = origin;
+ mKeySize = keySize;
+ mKeyValidityStart = keyValidityStart;
+ mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
+ mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mPurposes = purposes;
+ mAlgorithm = algorithm;
+ mPaddings = paddings;
+ mDigests = digests;
+ mBlockModes = blockModes;
+ mUserAuthenticators = userAuthenticators;
+ mTeeEnforcedUserAuthenticators = teeEnforcedUserAuthenticators;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
+ }
+
+ /**
+ * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}.
+ */
+ public String getKeystoreAlias() {
+ return mKeystoreAlias;
+ }
+
+ /**
+ * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available
+ * in plaintext only inside the TEE.
+ */
+ public boolean isTeeBacked() {
+ return mTeeBacked;
+ }
+
+ /**
+ * Gets the origin of the key.
+ */
+ public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
+ return mOrigin;
+ }
+
+ /**
+ * Gets the size of the key in bits.
+ */
+ public int getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Gets the time instant before which the key is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityStart() {
+ return mKeyValidityStart;
+ }
+
+ /**
+ * Gets the time instant after which the key is no long valid for decryption and verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key is no long valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the algorithm of the key.
+ */
+ public @KeyStoreKeyConstraints.AlgorithmEnum int getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ /**
+ * Gets the set of block modes with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
+ }
+
+ /**
+ * Gets the set of padding modes with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
+ }
+
+ /**
+ * Gets the set of digest algorithms with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ return mDigests;
+ }
+
+ /**
+ * Gets the set of user authenticators which protect access to the key. The key can only be used
+ * iff the user has authenticated to at least one of these user authenticators.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
+ }
+
+ /**
+ * Gets the set of user authenticators for which the TEE enforces access restrictions for this
+ * key. This is a subset of the user authentications returned by
+ * {@link #getUserAuthenticators()}.
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getTeeEnforcedUserAuthenticators() {
+ return mTeeEnforcedUserAuthenticators;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which the key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Returns {@code true} if this key will be permanently invalidated once a new fingerprint is
+ * enrolled. This constraint only has effect if fingerprint reader is one of the user
+ * authenticators protecting access to this key.
+ *
+ * @see #getUserAuthenticators()
+ */
+ public boolean isInvalidatedOnNewFingerprintEnrolled() {
+ return mInvalidatedOnNewFingerprintEnrolled;
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 2428c2a..751eef5 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -18,16 +18,15 @@ package android.security;
import android.content.Context;
-import java.security.KeyPairGenerator;
+import java.security.Key;
import java.security.KeyStore.ProtectionParameter;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
+
+import javax.crypto.Cipher;
/**
- * This provides the optional parameters that can be specified for
- * {@code KeyStore} entries that work with
+ * Parameters specifying how to secure and restrict the use of a key being
+ * imported into the
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
* facility</a>. The Android KeyStore facility is accessed through a
* {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
@@ -38,41 +37,35 @@ import java.util.Set;
* there is only one logical instance of the {@code KeyStore} per application
* UID so apps using the {@code sharedUid} facility will also share a
* {@code KeyStore}.
- * <p>
- * Keys may be generated using the {@link KeyPairGenerator} facility with a
- * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A
- * self-signed X.509 certificate will be attached to generated entries, but that
- * may be replaced at a later time by a certificate signed by a real Certificate
- * Authority.
*/
public final class KeyStoreParameter implements ProtectionParameter {
private int mFlags;
private final Date mKeyValidityStart;
private final Date mKeyValidityForOriginationEnd;
private final Date mKeyValidityForConsumptionEnd;
- private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
- private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
- private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private final Integer mMinSecondsBetweenOperations;
- private final Integer mMaxUsesPerBoot;
- private final Set<Integer> mUserAuthenticators;
- private final Integer mUserAuthenticationValidityDurationSeconds;
-
- private KeyStoreParameter(int flags, Date keyValidityStart,
- Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd,
- @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
- @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm,
- @KeyStoreKeyConstraints.PaddingEnum Integer padding,
- @KeyStoreKeyConstraints.DigestEnum Integer digest,
- @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
- Integer minSecondsBetweenOperations,
- Integer maxUsesPerBoot,
- Set<Integer> userAuthenticators,
- Integer userAuthenticationValidityDurationSeconds) {
- if ((userAuthenticationValidityDurationSeconds != null)
- && (userAuthenticationValidityDurationSeconds < 0)) {
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mInvalidatedOnNewFingerprintEnrolled;
+
+ private KeyStoreParameter(int flags,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.DigestEnum Integer digests,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds,
+ boolean invalidatedOnNewFingerprintEnrolled) {
+ if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
throw new IllegalArgumentException(
"userAuthenticationValidityDurationSeconds must not be negative");
}
@@ -82,16 +75,13 @@ public final class KeyStoreParameter implements ProtectionParameter {
mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
mPurposes = purposes;
- mAlgorithm = algorithm;
- mPadding = padding;
- mDigest = digest;
- mBlockMode = blockMode;
- mMinSecondsBetweenOperations = minSecondsBetweenOperations;
- mMaxUsesPerBoot = maxUsesPerBoot;
- mUserAuthenticators = (userAuthenticators != null)
- ? new HashSet<Integer>(userAuthenticators)
- : Collections.<Integer>emptySet();
+ mPaddings = paddings;
+ mDigests = digests;
+ mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
+ mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
}
/**
@@ -142,106 +132,110 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Gets the set of purposes for which the key can be used to the provided set of purposes.
- *
- * @return set of purposes or {@code null} if the key can be used for any purpose.
+ * Gets the set of purposes for which the key can be used.
*
* @hide
*/
- public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
return mPurposes;
}
/**
- * Gets the algorithm to which the key is restricted.
+ * Gets the set of padding schemes to which the key is restricted.
*
- * @return algorithm or {@code null} if it's not restricted.
* @hide
*/
- public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() {
- return mAlgorithm;
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
}
/**
- * Gets the padding scheme to which the key is restricted.
+ * Gets the set of digests to which the key is restricted.
*
- * @return padding scheme or {@code null} if the padding scheme is not restricted.
+ * @throws IllegalStateException if this restriction has not been specified.
+ *
+ * @see #isDigestsSpecified()
*
* @hide
*/
- public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
- return mPadding;
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ if (mDigests == null) {
+ throw new IllegalStateException("Digests not specified");
+ }
+ return mDigests;
}
/**
- * Gets the digest to which the key is restricted when generating Message Authentication Codes
- * (MACs).
+ * Returns {@code true} if digest restrictions have been specified.
*
- * @return digest or {@code null} if the digest is not restricted.
+ * @see #getDigests()
*
* @hide
*/
- public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
- return mDigest;
+ public boolean isDigestsSpecified() {
+ return mDigests != null;
}
/**
- * Gets the block mode to which the key is restricted when used for encryption or decryption.
- *
- * @return block more or {@code null} if block mode is not restricted.
+ * Gets the set of block modes to which the key is restricted.
*
* @hide
*/
- public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
- return mBlockMode;
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
}
/**
- * Gets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
- *
- * @return number of seconds or {@code null} if there is no restriction on how frequently a key
- * can be used.
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
*
* @hide
*/
- public Integer getMinSecondsBetweenOperations() {
- return mMinSecondsBetweenOperations;
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
}
/**
- * Gets the number of times the key can be used without rebooting the device.
+ * Gets the set of user authenticators which protect access to this key. The key can only be
+ * used iff the user has authenticated to at least one of these user authenticators.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
*
- * @return maximum number of times or {@code null} if there is no restriction.
* @hide
*/
- public Integer getMaxUsesPerBoot() {
- return mMaxUsesPerBoot;
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
}
/**
- * Gets the user authenticators which protect access to this key. The key can only be used iff
- * the user has authenticated to at least one of these user authenticators.
+ * Gets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
*
- * @return user authenticators or empty set if the key can be used without user authentication.
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
*
* @hide
*/
- public Set<Integer> getUserAuthenticators() {
- return new HashSet<Integer>(mUserAuthenticators);
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
}
/**
- * Gets the duration of time (seconds) for which this key can be used after the user
- * successfully authenticates to one of the associated user authenticators.
+ * Returns {@code true} if this key must be permanently invalidated once a new fingerprint is
+ * enrolled. This constraint only has effect if fingerprint reader is one of the user
+ * authenticators protecting access to this key.
*
- * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
+ * @see #getUserAuthenticators()
*
* @hide
*/
- public Integer getUserAuthenticationValidityDurationSeconds() {
- return mUserAuthenticationValidityDurationSeconds;
+ public boolean isInvalidatedOnNewFingerprintEnrolled() {
+ return mInvalidatedOnNewFingerprintEnrolled;
}
/**
@@ -266,15 +260,14 @@ public final class KeyStoreParameter implements ProtectionParameter {
private Date mKeyValidityStart;
private Date mKeyValidityForOriginationEnd;
private Date mKeyValidityForConsumptionEnd;
- private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
- private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
- private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private Integer mMinSecondsBetweenOperations;
- private Integer mMaxUsesPerBoot;
- private Set<Integer> mUserAuthenticators;
- private Integer mUserAuthenticationValidityDurationSeconds;
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+ private boolean mInvalidatedOnNewFingerprintEnrolled;
/**
* Creates a new instance of the {@code Builder} with the given
@@ -308,7 +301,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -322,7 +315,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -339,7 +332,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -354,7 +347,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -366,9 +359,9 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Restricts the purposes for which the key can be used to the provided set of purposes.
+ * Restricts the key to being used only for the provided set of purposes.
*
- * <p>By default, the key can be used for encryption, decryption, signing, and verification.
+ * <p>This restriction must be specified. There is no default.
*
* @hide
*/
@@ -378,83 +371,84 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Sets the algorithm of the key.
- *
- * <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly
- * specifying the algorithm of symmetric keys using this method is not necessary.
- *
- * @hide
- */
- public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) {
- mAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Restricts the key to being used only with the provided padding scheme. Attempts to use
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
* the key with any other padding will be rejected.
*
* <p>This restriction must be specified for keys which are used for encryption/decryption.
*
* @hide
*/
- public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
- mPadding = padding;
- return this;
- }
-
- /**
- * Restricts the key to being used only with the provided digest when generating Message
- * Authentication Codes (MACs). Attempts to use the key with any other digest will be
- * rejected.
- *
- * <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm
- * name.
- *
- * @see java.security.Key#getAlgorithm()
- *
- * @hide
- */
- public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
- mDigest = digest;
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
return this;
}
/**
- * Restricts the key to being used only with the provided block mode when encrypting or
- * decrypting. Attempts to use the key with any other block modes will be rejected.
+ * Restricts the key to being used only with the provided digests when generating signatures
+ * or HMACs. Attempts to use the key with any other digest will be rejected.
*
- * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ * <p>For HMAC keys, the default is to restrict to the digest specified in
+ * {@link Key#getAlgorithm()}. For asymmetric signing keys this constraint must be specified
+ * because there is no default.
*
* @hide
*/
- public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
- mBlockMode = blockMode;
+ public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+ mDigests = digests;
return this;
}
/**
- * Sets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
+ * Restricts the key to being used only with the provided block modes. Attempts to use the
+ * key with any other block modes will be rejected.
*
- * <p>By default, there is no restriction on how frequently a key can be used.
+ * <p>This restriction must be specified for symmetric encryption/decryption keys.
*
* @hide
*/
- public Builder setMinSecondsBetweenOperations(int seconds) {
- mMinSecondsBetweenOperations = seconds;
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
return this;
}
/**
- * Sets the maximum number of times a key can be used without rebooting the device.
- *
- * <p>By default, the key can be used for an unlimited number of times.
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
+ * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
+ * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
+ * encrypting, to ensure that only random IVs are used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
*
* @hide
*/
- public Builder setMaxUsesPerBoot(int count) {
- mMaxUsesPerBoot = count;
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
return this;
}
@@ -464,16 +458,16 @@ public final class KeyStoreParameter implements ProtectionParameter {
*
* <p>By default, the key can be used without user authentication.
*
- * @param userAuthenticators user authenticators or empty list if this key can be accessed
+ * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
* without user authentication.
*
* @see #setUserAuthenticationValidityDurationSeconds(int)
*
* @hide
*/
- public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
- mUserAuthenticators =
- (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
return this;
}
@@ -486,7 +480,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
* @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
* every use of the key.
*
- * @see #setUserAuthenticators(Set)
+ * @see #setUserAuthenticators(int)
*
* @hide
*/
@@ -496,17 +490,40 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Sets whether this key must be invalidated (permanently) whenever a new fingerprint is
+ * enrolled. This only has effect if fingerprint reader is one of the user authenticators
+ * protecting access to the key.
+ *
+ * <p>By default, enrolling a new fingerprint does not invalidate the key.
+ *
+ * @see #setUserAuthenticators(Set)
+ *
+ * @hide
+ */
+ public Builder setInvalidatedOnNewFingerprintEnrolled(boolean invalidated) {
+ mInvalidatedOnNewFingerprintEnrolled = invalidated;
+ return this;
+ }
+
+ /**
* Builds the instance of the {@code KeyStoreParameter}.
*
* @throws IllegalArgumentException if a required field is missing
* @return built instance of {@code KeyStoreParameter}
*/
public KeyStoreParameter build() {
- return new KeyStoreParameter(mFlags, mKeyValidityStart,
- mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, mPurposes,
- mAlgorithm, mPadding, mDigest, mBlockMode, mMinSecondsBetweenOperations,
- mMaxUsesPerBoot, mUserAuthenticators,
- mUserAuthenticationValidityDurationSeconds);
+ return new KeyStoreParameter(mFlags,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mPaddings,
+ mDigests,
+ mBlockModes,
+ mRandomizedEncryptionRequired,
+ mUserAuthenticators,
+ mUserAuthenticationValidityDurationSeconds,
+ mInvalidatedOnNewFingerprintEnrolled);
}
}
}
diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java
index 9410127..7f0e3d3 100644
--- a/keystore/java/android/security/KeyStoreSecretKey.java
+++ b/keystore/java/android/security/KeyStoreSecretKey.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 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;
import javax.crypto.SecretKey;
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
new file mode 100644
index 0000000..a5e87d1
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Date;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactorySpi;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link SecretKeyFactorySpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ @Override
+ protected KeySpec engineGetKeySpec(SecretKey key,
+ @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
+ if (keySpecClass == null) {
+ throw new InvalidKeySpecException("keySpecClass == null");
+ }
+ if (!(key instanceof KeyStoreSecretKey)) {
+ throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " +
+ ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) {
+ throw new InvalidKeySpecException(
+ "Key material export of Android KeyStore keys is not supported");
+ }
+ if (!KeyStoreKeySpec.class.equals(keySpecClass)) {
+ throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
+ }
+ String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
+ String entryAlias;
+ if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
+ entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
+ } else {
+ throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
+ }
+
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode =
+ mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new InvalidKeySpecException("Failed to obtain information about key."
+ + " Keystore error: " + errorCode);
+ }
+
+ boolean teeBacked;
+ @KeyStoreKeyCharacteristics.OriginEnum int origin;
+ int keySize;
+ @KeyStoreKeyConstraints.PurposeEnum int purposes;
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
+ @KeyStoreKeyConstraints.PaddingEnum int paddings;
+ @KeyStoreKeyConstraints.DigestEnum int digests;
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes;
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
+ try {
+ if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = true;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = false;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else {
+ throw new InvalidKeySpecException("Key origin not available");
+ }
+ Integer keySizeInteger =
+ KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
+ if (keySizeInteger == null) {
+ throw new InvalidKeySpecException("Key size not available");
+ }
+ keySize = keySizeInteger;
+ purposes = KeyStoreKeyConstraints.Purpose.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PURPOSE));
+ Integer alg = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ALGORITHM);
+ if (alg == null) {
+ throw new InvalidKeySpecException("Key algorithm not available");
+ }
+ algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg);
+ paddings = KeyStoreKeyConstraints.Padding.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING));
+ digests = KeyStoreKeyConstraints.Digest.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST));
+ blockModes = KeyStoreKeyConstraints.BlockMode.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE));
+
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int swEnforcedKeymasterUserAuthenticators =
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int hwEnforcedKeymasterUserAuthenticators =
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int keymasterUserAuthenticators =
+ swEnforcedKeymasterUserAuthenticators | hwEnforcedKeymasterUserAuthenticators;
+ userAuthenticators = KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+ keymasterUserAuthenticators);
+ teeEnforcedUserAuthenticators =
+ KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+ hwEnforcedKeymasterUserAuthenticators);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidKeySpecException("Unsupported key characteristic", e);
+ }
+
+ Date keyValidityStart =
+ KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
+ if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) {
+ keyValidityStart = null;
+ }
+ Date keyValidityForOriginationEnd = KeymasterUtils.getDate(keyCharacteristics,
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME);
+ if ((keyValidityForOriginationEnd != null)
+ && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) {
+ keyValidityForOriginationEnd = null;
+ }
+ Date keyValidityForConsumptionEnd = KeymasterUtils.getDate(keyCharacteristics,
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME);
+ if ((keyValidityForConsumptionEnd != null)
+ && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) {
+ keyValidityForConsumptionEnd = null;
+ }
+ Integer userAuthenticationValidityDurationSeconds =
+ KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT);
+
+ // TODO: Populate the value below from key characteristics once Keymaster is ready.
+ boolean invalidatedOnNewFingerprintEnrolled = false;
+
+ return new KeyStoreKeySpec(entryAlias,
+ teeBacked,
+ origin,
+ keySize,
+ keyValidityStart,
+ keyValidityForOriginationEnd,
+ keyValidityForConsumptionEnd,
+ purposes,
+ algorithm,
+ paddings,
+ digests,
+ blockModes,
+ userAuthenticators,
+ teeEnforcedUserAuthenticators,
+ ((userAuthenticationValidityDurationSeconds != null)
+ ? userAuthenticationValidityDurationSeconds : -1),
+ invalidatedOnNewFingerprintEnrolled);
+ }
+
+ @Override
+ protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
+ throw new UnsupportedOperationException(
+ "Key import into Android KeyStore is not supported");
+ }
+
+ @Override
+ protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
+ throw new UnsupportedOperationException(
+ "Key import into Android KeyStore is not supported");
+ }
+}
diff --git a/keystore/java/android/security/KeymasterException.java b/keystore/java/android/security/KeymasterException.java
deleted file mode 100644
index bc8198f..0000000
--- a/keystore/java/android/security/KeymasterException.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package android.security;
-
-/**
- * Keymaster exception.
- *
- * @hide
- */
-public class KeymasterException extends Exception {
-
- private final int mErrorCode;
-
- public KeymasterException(int errorCode, String message) {
- super(message);
- mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-}
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
index 4f17586..3143d4d 100644
--- a/keystore/java/android/security/KeymasterUtils.java
+++ b/keystore/java/android/security/KeymasterUtils.java
@@ -1,6 +1,26 @@
+/*
+ * Copyright (C) 2015 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;
-import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.KeyCharacteristics;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
/**
* @hide
@@ -8,16 +28,36 @@ import android.security.keymaster.KeymasterDefs;
public abstract class KeymasterUtils {
private KeymasterUtils() {}
- public static KeymasterException getExceptionForKeymasterError(int keymasterErrorCode) {
- switch (keymasterErrorCode) {
- case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
- // The name of this parameter significantly differs between Keymaster and framework
- // APIs. Use the framework wording to make life easier for developers.
- return new KeymasterException(keymasterErrorCode,
- "Invalid user authentication validity duration");
- default:
- return new KeymasterException(keymasterErrorCode,
- KeymasterDefs.getErrorMessage(keymasterErrorCode));
+ public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) {
+ if (keyCharacteristics.hwEnforced.containsTag(tag)) {
+ return keyCharacteristics.hwEnforced.getInt(tag, -1);
+ } else if (keyCharacteristics.swEnforced.containsTag(tag)) {
+ return keyCharacteristics.swEnforced.getInt(tag, -1);
+ } else {
+ return null;
+ }
+ }
+
+ public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) {
+ List<Integer> result = new ArrayList<Integer>();
+ result.addAll(keyCharacteristics.hwEnforced.getInts(tag));
+ result.addAll(keyCharacteristics.swEnforced.getInts(tag));
+ return result;
+ }
+
+ public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) {
+ Date result = keyCharacteristics.hwEnforced.getDate(tag, null);
+ if (result == null) {
+ result = keyCharacteristics.swEnforced.getDate(tag, null);
+ }
+ return result;
+ }
+
+ public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) {
+ if (keyCharacteristics.hwEnforced.containsTag(tag)) {
+ return keyCharacteristics.hwEnforced.getBoolean(tag, false);
+ } else {
+ return keyCharacteristics.swEnforced.getBoolean(tag, false);
}
}
}
diff --git a/keystore/java/android/security/NewFingerprintEnrolledException.java b/keystore/java/android/security/NewFingerprintEnrolledException.java
new file mode 100644
index 0000000..6da4a2a
--- /dev/null
+++ b/keystore/java/android/security/NewFingerprintEnrolledException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Indicates that a cryptographic operation could not be performed because the key used by the
+ * operation is permanently invalid because a new fingerprint was enrolled.
+ *
+ * @hide
+ */
+public class NewFingerprintEnrolledException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code NewFingerprintEnrolledException} without detail message and cause.
+ */
+ public NewFingerprintEnrolledException() {
+ super("Invalid key: new fingerprint enrolled");
+ }
+
+ /**
+ * Constructs a new {@code NewFingerprintEnrolledException} with the provided detail message and
+ * no cause.
+ */
+ public NewFingerprintEnrolledException(String message) {
+ super(message);
+ }
+}
diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/UserNotAuthenticatedException.java
new file mode 100644
index 0000000..e6342ef
--- /dev/null
+++ b/keystore/java/android/security/UserNotAuthenticatedException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Indicates that a cryptographic operation could not be performed because the user has not been
+ * authenticated recently enough.
+ *
+ * @hide
+ */
+public class UserNotAuthenticatedException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} without detail message and cause.
+ */
+ public UserNotAuthenticatedException() {
+ super("User not authenticated");
+ }
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and
+ * no cause.
+ */
+ public UserNotAuthenticatedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and
+ * cause.
+ */
+ public UserNotAuthenticatedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}