diff options
Diffstat (limited to 'keystore/java/android/security')
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); + } +} |
