diff options
Diffstat (limited to 'keystore/java/android')
16 files changed, 1230 insertions, 597 deletions
diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index 3b25ba6..3f29c6a 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -54,13 +54,13 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { public static class RSA extends AndroidKeyPairGenerator { public RSA() { - super("RSA"); + super(KeyStoreKeyProperties.Algorithm.RSA); } } public static class EC extends AndroidKeyPairGenerator { public EC() { - super("EC"); + super(KeyStoreKeyProperties.Algorithm.EC); } } @@ -83,15 +83,15 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { private android.security.KeyStore mKeyStore; private KeyPairGeneratorSpec mSpec; - private String mKeyAlgorithm; + private @KeyStoreKeyProperties.AlgorithmEnum String mKeyAlgorithm; private int mKeyType; private int mKeySize; - protected AndroidKeyPairGenerator(String algorithm) { + protected AndroidKeyPairGenerator(@KeyStoreKeyProperties.AlgorithmEnum String algorithm) { mAlgorithm = algorithm; } - public String getAlgorithm() { + public @KeyStoreKeyProperties.AlgorithmEnum String getAlgorithm() { return mAlgorithm; } @@ -197,7 +197,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { return certGen.generate(privateKey); } - private String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + private @KeyStoreKeyProperties.AlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { String result = spec.getKeyType(); if (result != null) { return result; @@ -248,10 +248,11 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } } - private static String getDefaultSignatureAlgorithmForKeyAlgorithm(String algorithm) { - if ("RSA".equalsIgnoreCase(algorithm)) { + private static String getDefaultSignatureAlgorithmForKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(algorithm)) { return "sha256WithRSA"; - } else if ("EC".equalsIgnoreCase(algorithm)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(algorithm)) { return "sha256WithECDSA"; } else { throw new IllegalArgumentException("Unsupported key type " + algorithm); @@ -287,7 +288,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params; - String keyAlgorithm = getKeyAlgorithm(spec); + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithm = getKeyAlgorithm(spec); int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); if (keyType == -1) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 72cb062..69d80e6 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -103,8 +103,9 @@ public class AndroidKeyStore extends KeyStoreSpi { keyAliasInKeystore, null, null, keyCharacteristics); if ((errorCode != KeymasterDefs.KM_ERROR_OK) && (errorCode != android.security.KeyStore.NO_ERROR)) { - throw new UnrecoverableKeyException("Failed to load information about key." - + " Error code: " + errorCode); + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to load information about key") + .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); } int keymasterAlgorithm = @@ -128,10 +129,11 @@ public class AndroidKeyStore extends KeyStoreSpi { keymasterDigest = keymasterDigests.get(0); } - String keyAlgorithmString; + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmString; try { - keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); + keyAlgorithmString = + KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); @@ -451,10 +453,10 @@ public class AndroidKeyStore extends KeyStoreSpi { int keymasterAlgorithm; int keymasterDigest; try { - keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm( + keymasterAlgorithm = KeyStoreKeyProperties.Algorithm.toKeymasterSecretKeyAlgorithm( keyAlgorithmString); keymasterDigest = - KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString); + KeyStoreKeyProperties.Algorithm.toKeymasterDigest(keyAlgorithmString); } catch (IllegalArgumentException e) { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } @@ -465,8 +467,7 @@ public class AndroidKeyStore extends KeyStoreSpi { int[] keymasterDigests; if (params.isDigestsSpecified()) { // Digest(s) specified in parameters - keymasterDigests = - KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests()); + keymasterDigests = KeyStoreKeyProperties.Digest.allToKeymaster(params.getDigests()); if (keymasterDigest != -1) { // Digest also specified in the JCA key algorithm name. if (!com.android.internal.util.ArrayUtils.contains( @@ -494,8 +495,8 @@ public class AndroidKeyStore extends KeyStoreSpi { } @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - params.getBlockModes()); + int[] keymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { @@ -503,8 +504,7 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) + + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + ". See KeyStoreParameter documentation."); } } @@ -513,11 +513,11 @@ public class AndroidKeyStore extends KeyStoreSpi { args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - int[] keymasterPaddings = ArrayUtils.concat( - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - params.getEncryptionPaddings()), - KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings( - params.getSignaturePaddings())); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); KeymasterUtils.addUserAuthArgs(args, params.getContext(), diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index e9c24dd..8e27dc3 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -262,7 +262,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, String host, int port, String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String alias) { choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); } @@ -306,9 +307,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, - String host, int port, String url, - String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String url, String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant * to follow the semantics and purpose of X509KeyManager @@ -431,9 +431,11 @@ public final class KeyChain { * specific {@code PrivateKey} type indicated by {@code algorithm} (e.g., * "RSA"). */ - public static boolean isKeyAlgorithmSupported(String algorithm) { + public static boolean isKeyAlgorithmSupported( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { final String algUpper = algorithm.toUpperCase(Locale.US); - return "EC".equals(algUpper) || "RSA".equals(algUpper); + return KeyStoreKeyProperties.Algorithm.EC.equals(algUpper) + || KeyStoreKeyProperties.Algorithm.RSA.equals(algUpper); } /** @@ -443,7 +445,8 @@ public final class KeyChain { * hardware support that can be used to bind keys to the device in a way * that makes it non-exportable. */ - public static boolean isBoundKeyAlgorithm(String algorithm) { + public static boolean isBoundKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { return false; } diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java index 8f135a6..97e3a67 100644 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -16,6 +16,7 @@ package android.security; +import android.app.KeyguardManager; import android.content.Context; import android.text.TextUtils; @@ -24,19 +25,53 @@ import java.util.Date; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; -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>. + * {@link AlgorithmParameterSpec} for initializing a {@link KeyGenerator} of the + * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class + * specifies whether user authentication is required for using the key, what uses the key is + * authorized for (e.g., only in {@code CBC} mode), whether the key should be encrypted at rest, the + * key's and validity start and end dates. * - * <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>To generate a key, create an instance of this class using the {@link Builder}, initialize a + * {@code KeyGenerator} of the desired key type (e.g., {@code AES} or {@code HmacSHA256}) from the + * {@code AndroidKeyStore} provider with the {@code KeyGeneratorSpec} instance, and then generate a + * key using {@link KeyGenerator#generateKey()}. * - * <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}. + * <p>The generated key will be returned by the {@code KeyGenerator} and also stored in the Android + * KeyStore under the alias specified in this {@code KeyGeneratorSpec}. To obtain the key from the + * Android KeyStore use + * {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}. + * + * <p>NOTE: The key material of the keys generating using the {@code KeyGeneratorSpec} is not + * accessible. + * + * <p><h3>Example</h3> + * The following example illustrates how to generate an HMAC key in the Android KeyStore under alias + * {@code key1} authorized to be used only for HMAC with SHA-256 digest and only if the user has + * been authenticated within the last five minutes. + * <pre> {@code + * KeyGenerator keyGenerator = KeyGenerator.getInstance( + * KeyStoreKeyProperties.Algorithm.HMAC_SHA256, + * "AndroidKeyStore"); + * keyGenerator.initialize( + * new KeyGeneratorSpec.Builder(context) + * .setAlias("key1") + * .setPurposes(KeyStoreKeyProperties.Purpose.SIGN + * | KeyStoreKeyProperties.Purpose.VERIFY) + * // Only permit this key to be used if the user authenticated + * // within the last five minutes. + * .setUserAuthenticationRequired(true) + * .setUserAuthenticationValidityDurationSeconds(5 * 60) + * .build()); + * SecretKey key = keyGenerator.generateKey(); + * + * // The key can also be obtained from the Android KeyStore any time as follows: + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * SecretKey key = (SecretKey) keyStore.getKey("key1", null); + * }</pre> */ public class KeyGeneratorSpec implements AlgorithmParameterSpec { @@ -48,8 +83,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -63,8 +98,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -160,14 +195,14 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -195,17 +230,20 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. + * + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; } /** - * Returns {@code true} if the key must be encrypted in the {@link java.security.KeyStore}. + * Returns {@code true} if the key must be encrypted at rest. This will protect the key with the + * secure lock screen credential (e.g., password, PIN, or pattern). */ public boolean isEncryptionRequired() { return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; @@ -220,8 +258,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -264,16 +302,19 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { } /** - * Indicates that this key must be encrypted at rest on storage. Note that enabling this - * will require that the user enable a strong lock screen (e.g., PIN, password) before - * creating or using the generated key is successful. + * Indicates that this key must be encrypted at rest. This will protect the key with the + * secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that this feature requires that the secure lock screen (e.g., password, PIN, + * pattern) is set up, otherwise key generation will fail. Moreover, this key will be + * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device + * Administrator). Finally, this key cannot be used until the user unlocks the secure lock + * screen after boot. + * + * @see KeyguardManager#isDeviceSecure() */ - public Builder setEncryptionRequired(boolean required) { - if (required) { - mFlags |= KeyStore.FLAG_ENCRYPTED; - } else { - mFlags &= ~KeyStore.FLAG_ENCRYPTED; - } + public Builder setEncryptionRequired() { + mFlags |= KeyStore.FLAG_ENCRYPTED; return this; } @@ -346,7 +387,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -357,7 +399,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -425,7 +467,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index d6d3789..7fd5cb5 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -16,10 +16,12 @@ package android.security; +import android.app.KeyguardManager; import android.content.Context; import android.text.TextUtils; import java.math.BigInteger; +import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -29,26 +31,64 @@ import java.util.Date; import javax.security.auth.x500.X500Principal; /** - * This provides the required parameters needed for initializing the - * {@code KeyPairGenerator} that works with - * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore - * facility</a>. The Android KeyStore facility is accessed through a - * {@link java.security.KeyPairGenerator} 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 PrivateKey} and its associated - * {@link Certificate} chain. - * <p> - * The KeyPair generator will create a self-signed certificate with the subject - * as its X.509v3 Subject Distinguished Name and as its X.509v3 Issuer - * Distinguished Name along with the other parameters specified with the - * {@link Builder}. - * <p> - * The self-signed X.509 certificate may be replaced at a later time by a - * certificate signed by a real Certificate Authority. + * {@link AlgorithmParameterSpec} for initializing a {@link KeyPairGenerator} of the + * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class + * specifies whether user authentication is required for using the private key, what uses the + * private key is authorized for (e.g., only for signing -- decryption not permitted), whether the + * private key should be encrypted at rest, the private key's and validity start and end dates. + * + * <p>To generate a key pair, create an instance of this class using the {@link Builder}, initialize + * a {@code KeyPairGenerator} of the desired key type (e.g., {@code EC} or {@code RSA}) from the + * {@code AndroidKeyStore} provider with the {@code KeyPairGeneratorSpec} instance, and then + * generate a key pair using {@link KeyPairGenerator#generateKeyPair()}. + * + * <p>The generated key pair will be returned by the {@code KeyPairGenerator} and also stored in the + * Android KeyStore under the alias specified in this {@code KeyPairGeneratorSpec}. To obtain the + * private key from the Android KeyStore use + * {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}. + * To obtain the public key from the Android KeyStore use + * {@link java.security.KeyStore#getCertificate(String)} and then + * {@link Certificate#getPublicKey()}. + * + * <p>A self-signed X.509 certificate will be also generated and stored in the Android KeyStore. + * This is because the {@link java.security.KeyStore} abstraction does not support storing key pairs + * without a certificate. The subject, serial number, and validity dates of the certificate can be + * specified in this {@code KeyPairGeneratorSpec}. The self-signed certificate may be replaced at a + * later time by a certificate signed by a Certificate Authority (CA). + * + * <p>NOTE: The key material of the private keys generating using the {@code KeyPairGeneratorSpec} + * is not accessible. The key material of the public keys is accessible. + * + * <p><h3>Example</h3> + * The following example illustrates how to generate an EC key pair in the Android KeyStore under + * alias {@code key2} authorized to be used only for signing using SHA-256, SHA-384, or SHA-512 + * digest and only if the user has been authenticated within the last five minutes. + * <pre> {@code + * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + * KeyStoreKeyProperties.Algorithm.EC, + * "AndroidKeyStore"); + * keyPairGenerator.initialize( + * new KeyGeneratorSpec.Builder(context) + * .setAlias("key2") + * .setPurposes(KeyStoreKeyProperties.Purpose.SIGN + * | KeyStoreKeyProperties.Purpose.VERIFY) + * .setDigests(KeyStoreKeyProperties.Digest.SHA256 + * | KeyStoreKeyProperties.Digest.SHA384 + * | KeyStoreKeyProperties.Digest.SHA512) + * // Only permit this key to be used if the user authenticated + * // within the last five minutes. + * .setUserAuthenticationRequired(true) + * .setUserAuthenticationValidityDurationSeconds(5 * 60) + * .build()); + * KeyPair keyPair = keyPairGenerator.generateKey(); + * + * // The key pair can also be obtained from the Android KeyStore any time as follows: + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * PrivateKey privateKey = (PrivateKey) keyStore.getKey("key2", null); + * PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey(); + * }</pre> */ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { @@ -85,13 +125,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mDigests; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; @@ -138,10 +178,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] digests, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] blockModes, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -246,7 +286,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Returns the key type (e.g., "EC", "RSA") specified by this parameter. */ - public String getKeyType() { + public @KeyStoreKeyProperties.AlgorithmEnum String getKeyType() { return mKeyType; } @@ -307,8 +347,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Returns {@code true} if this parameter will require generated keys to be - * encrypted in the {@link java.security.KeyStore}. + * Returns {@code true} if the key must be encrypted at rest. This will protect the key pair + * with the secure lock screen credential (e.g., password, PIN, or pattern). */ public boolean isEncryptionRequired() { return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; @@ -352,28 +392,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding schemes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -403,14 +443,14 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the duration of time (seconds) for which the private key can be used after the user - * is successfully authenticated. + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. * * <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. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ @@ -468,13 +508,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mDigests; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private String[] mSignaturePaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; @@ -511,7 +551,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the key type (e.g., EC, RSA) of the keypair to be created. */ - public Builder setKeyType(String keyType) throws NoSuchAlgorithmException { + public Builder setKeyType(@KeyStoreKeyProperties.AlgorithmEnum String keyType) + throws NoSuchAlgorithmException { if (keyType == null) { throw new NullPointerException("keyType == null"); } else { @@ -613,10 +654,16 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Indicates that this key must be encrypted at rest on storage. Note - * that enabling this will require that the user enable a strong lock - * screen (e.g., PIN, password) before creating or using the generated - * key is successful. + * Indicates that this key pair must be encrypted at rest. This will protect the key pair + * with the secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that this feature requires that the secure lock screen (e.g., password, PIN, + * pattern) is set up, otherwise key pair generation will fail. Moreover, this key pair will + * be deleted when the secure lock screen is disabled or reset (e.g., by the user or a + * Device Administrator). Finally, this key pair cannot be used until the user unlocks the + * secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() */ public Builder setEncryptionRequired() { mFlags |= KeyStore.FLAG_ENCRYPTED; @@ -628,6 +675,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -640,6 +689,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -655,6 +706,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -668,6 +721,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -679,6 +734,14 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p>If the set of purposes for which the key can be used does not contain + * {@link KeyStoreKeyProperties.Purpose#SIGN}, the self-signed certificate generated by + * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an invalid + * signature. This is OK if the certificate is only used for obtaining the public key from + * Android KeyStore. + * + * <p><b>NOTE: This has currently no effect. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -690,8 +753,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * to use the key with any other digest will be rejected. * * <p>This must be specified for keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -702,8 +767,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -714,8 +782,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -725,8 +796,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -750,6 +823,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <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> + * + * <p><b>NOTE: This has currently no effect. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -772,6 +847,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * + * <p><b>NOTE: This has currently no effect. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -788,7 +865,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <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 + * <p><b>NOTE: This has currently no effect. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 82d328b..3ed8899 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -18,14 +18,17 @@ package android.security; import android.app.ActivityThread; import android.app.Application; +import android.app.KeyguardManager; import com.android.org.conscrypt.NativeConstants; import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.os.Binder; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; @@ -71,6 +74,19 @@ public class KeyStore { // Flags for "put" "import" and "generate" public static final int FLAG_NONE = 0; + + /** + * Indicates that this key (or key pair) must be encrypted at rest. This will protect the key + * (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set + * up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key + * pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or + * a Device Administrator). Finally, this key (or key pair) cannot be used until the user + * unlocks the secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() + */ public static final int FLAG_ENCRYPTED = 1; // States @@ -115,10 +131,10 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(String keyType) { - if ("RSA".equalsIgnoreCase(keyType)) { + static int getKeyTypeForAlgorithm(@KeyStoreKeyProperties.AlgorithmEnum String keyType) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; - } else if ("EC".equalsIgnoreCase(keyType)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_EC; } else { return -1; @@ -212,15 +228,6 @@ public class KeyStore { } } - public boolean password(String password) { - try { - return mBinder.password(password) == NO_ERROR; - } catch (RemoteException e) { - Log.w(TAG, "Cannot connect to keystore", e); - return false; - } - } - public boolean lock() { try { return mBinder.lock() == NO_ERROR; @@ -230,9 +237,20 @@ public class KeyStore { } } - public boolean unlock(String password) { + /** + * Attempt to unlock the keystore for {@code user} with the password {@code password}. + * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or + * created. + * + * @param user Android user ID to operate on + * @param password user's keystore password. Should be the most recent value passed to + * {@link #onUserPasswordChanged} for the user. + * + * @return whether the keystore was unlocked. + */ + public boolean unlock(int userId, String password) { try { - mError = mBinder.unlock(password); + mError = mBinder.unlock(userId, password); return mError == NO_ERROR; } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); @@ -240,6 +258,10 @@ public class KeyStore { } } + public boolean unlock(String password) { + return unlock(UserHandle.getUserId(Process.myUid()), password); + } + public boolean isEmpty() { try { return mBinder.zero() == KEY_NOT_FOUND; @@ -540,6 +562,30 @@ public class KeyStore { } /** + * Notify keystore that a user's password has changed. + * + * @param userId the user whose password changed. + * @param newPassword the new password or "" if the password was removed. + */ + public boolean onUserPasswordChanged(int userId, String newPassword) { + // Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that + // explicit here. + if (newPassword == null) { + newPassword = ""; + } + try { + return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + public boolean onUserPasswordChanged(String newPassword) { + return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword); + } + + /** * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error * code. */ @@ -550,7 +596,7 @@ public class KeyStore { case NO_ERROR: return new KeyStoreException(errorCode, "OK"); case LOCKED: - return new KeyStoreException(errorCode, "Keystore locked"); + return new KeyStoreException(errorCode, "User authentication required"); case UNINITIALIZED: return new KeyStoreException(errorCode, "Keystore not initialized"); case SYSTEM_ERROR: @@ -587,6 +633,8 @@ public class KeyStore { */ InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, KeyStoreException e) { switch (e.getErrorCode()) { + case LOCKED: + return new UserNotAuthenticatedException(); case KeymasterDefs.KM_ERROR_KEY_EXPIRED: return new KeyExpiredException(); case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java index 094aa75..bd601bc 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -27,6 +27,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; @@ -315,15 +316,15 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } else if (e instanceof InvalidAlgorithmParameterException) { throw (InvalidAlgorithmParameterException) e; } else { - throw new RuntimeException("Unexpected exception type", e); + throw new ProviderException("Unexpected exception type", e); } } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); @@ -494,13 +495,14 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } if ((mIv != null) && (mIv.length > 0)) { try { - AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + AlgorithmParameters params = + AlgorithmParameters.getInstance(KeyStoreKeyProperties.Algorithm.AES); params.init(new IvParameterSpec(mIv)); return params; } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e); + throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); } catch (InvalidParameterSpecException e) { - throw new RuntimeException( + throw new ProviderException( "Failed to initialize AES AlgorithmParameters with an IV", e); } } @@ -633,10 +635,9 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if ((mIv == null) && (mEncrypting)) { // 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); - } + mAdditionalEntropyForBegin = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, mBlockSizeBytes); } } } @@ -668,11 +669,11 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if (mIv == null) { mIv = returnedIv; } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { - throw new IllegalStateException("IV in use differs from provided IV"); + throw new ProviderException("IV in use differs from provided IV"); } } else { if (returnedIv != null) { - throw new IllegalStateException( + throw new ProviderException( "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 1aa3aec..885f1f7 100644 --- a/keystore/java/android/security/KeyStoreConnectException.java +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -16,12 +16,14 @@ package android.security; +import java.security.ProviderException; + /** * Indicates a communications error with keystore service. * * @hide */ -public class KeyStoreConnectException extends IllegalStateException { +public class KeyStoreConnectException extends ProviderException { public KeyStoreConnectException() { super("Failed to communicate with keystore service"); } diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java index e5933ad..311278b 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterDefs; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.SecureRandom; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -28,6 +29,9 @@ import java.security.InvalidKeyException; * @hide */ abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + private KeyStoreCryptoOperationUtils() {} /** @@ -81,4 +85,28 @@ abstract class KeyStoreCryptoOperationUtils { // General cases return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } } diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java index 0dbe788..5089a25 100644 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -24,6 +24,7 @@ import android.security.keymaster.OperationResult; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; +import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.MacSpi; @@ -185,10 +186,10 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( @@ -206,17 +207,17 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] output; try { output = mChunkedStreamer.update(input, offset, len); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } if ((output != null) && (output.length != 0)) { - throw new IllegalStateException("Update operation unexpectedly produced output"); + throw new ProviderException("Update operation unexpectedly produced output"); } } @@ -225,14 +226,14 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] result; try { result = mChunkedStreamer.doFinal(null, 0, 0); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } resetWhilePreservingInitState(); diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 68b5751..4b914c2 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.Date; @@ -39,6 +40,17 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { public AES() { super(KeymasterDefs.KM_ALGORITHM_AES, 128); } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } } protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { @@ -87,6 +99,11 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { private KeyGeneratorSpec mSpec; private SecureRandom mRng; + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + protected KeyStoreKeyGeneratorSpi( int keymasterAlgorithm, int defaultKeySizeBits) { @@ -100,6 +117,97 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { mKeymasterAlgorithm = keymasterAlgorithm; mKeymasterDigest = keymasterDigest; mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + KeyGeneratorSpec spec = (KeyGeneratorSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size in must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = + KeyStoreKeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyStoreKeyProperties.BlockMode.fromKeymaster( + keymasterBlockMode) + + ". See " + KeyGeneratorSpec.class.getName() + + " documentation."); + } + } + } + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; } @Override @@ -117,43 +225,14 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); if (mKeymasterDigest != -1) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); } - if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { - if (mKeymasterDigest == -1) { - throw new IllegalStateException("Digest algorithm must be specified for HMAC key"); - } - } - int keySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; - args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); - @KeyStoreKeyProperties.PurposeEnum int purposes = spec.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - spec.getBlockModes()); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) - && (spec.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { - throw new IllegalStateException( - "Randomized encryption (IND-CPA) required but may be violated by block" - + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) - + ". See KeyGeneratorSpec documentation."); - } - } - } - - for (int keymasterPurpose : - KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } - args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - args.addInts( - KeymasterDefs.KM_TAG_PADDING, - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - spec.getEncryptionPaddings())); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); KeymasterUtils.addUserAuthArgs(args, spec.getContext(), spec.isUserAuthenticationRequired(), @@ -168,57 +247,31 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { (spec.getKeyValidityForConsumptionEnd() != null) ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (!spec.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); } - byte[] additionalEntropy = null; - SecureRandom rng = mRng; - if (rng != null) { - additionalEntropy = new byte[(keySizeBits + 7) / 8]; - rng.nextBytes(additionalEntropy); - } - + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); int flags = spec.getFlags(); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics()); + keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw new IllegalStateException( + throw new ProviderException( "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); } - String keyAlgorithmJCA = - KeymasterUtils.getJcaSecretKeyAlgorithm(mKeymasterAlgorithm, mKeymasterDigest); - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); - } - - @Override - protected void engineInit(SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); - } - - @Override - protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { - throw new InvalidAlgorithmParameterException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); - } - KeyGeneratorSpec spec = (KeyGeneratorSpec) params; - if (spec.getKeystoreAlias() == null) { - throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } - - mSpec = spec; - mRng = random; - } - - @Override - protected void engineInit(int keySize, SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGeneratorSpec.class.getName() + " parameter"); + return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } } diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java index b85ec53..1cf6a7a 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/KeyStoreKeyProperties.java @@ -17,13 +17,23 @@ package android.security; import android.annotation.IntDef; +import android.annotation.StringDef; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; import java.util.Collection; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; /** * Properties of {@code AndroidKeyStore} keys. @@ -37,7 +47,7 @@ public abstract class KeyStoreKeyProperties { public @interface PurposeEnum {} /** - * Purpose of key. + * Purposes of key. */ public static abstract class Purpose { private Purpose() {} @@ -122,6 +132,514 @@ public abstract class KeyStoreKeyProperties { } @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Algorithm.RSA, + Algorithm.EC, + Algorithm.AES, + Algorithm.HMAC_SHA1, + Algorithm.HMAC_SHA224, + Algorithm.HMAC_SHA256, + Algorithm.HMAC_SHA384, + Algorithm.HMAC_SHA512, + }) + public @interface AlgorithmEnum {} + + /** + * Key algorithms. + * + * <p>These are standard names which can be used to obtain instances of {@link KeyGenerator}, + * {@link KeyPairGenerator}, {@link Cipher} (as part of the transformation string), {@link Mac}, + * {@link KeyFactory}, {@link SecretKeyFactory}. These are also the names used by + * {@link Key#getAlgorithm()}. + */ + public static abstract class Algorithm { + private Algorithm() {} + + /** Rivest Shamir Adleman (RSA) key. */ + public static final String RSA = "RSA"; + + /** Elliptic Curve (EC) key. */ + public static final String EC = "EC"; + + /** Advanced Encryption Standard (AES) key. */ + public static final String AES = "AES"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ + public static final String HMAC_SHA1 = "HmacSHA1"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ + public static final String HMAC_SHA224 = "HmacSHA224"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ + public static final String HMAC_SHA256 = "HmacSHA256"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ + public static final String HMAC_SHA384 = "HmacSHA384"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ + public static final String HMAC_SHA512 = "HmacSHA512"; + + /** + * @hide + */ + static int toKeymasterSecretKeyAlgorithm(@AlgorithmEnum String algorithm) { + if (AES.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_AES; + } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { + return KeymasterDefs.KM_ALGORITHM_HMAC; + } else { + throw new IllegalArgumentException( + "Unsupported secret key algorithm: " + algorithm); + } + } + + /** + * @hide + */ + static @AlgorithmEnum String fromKeymasterSecretKeyAlgorithm( + int keymasterAlgorithm, int keymasterDigest) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_AES: + if (keymasterDigest != -1) { + throw new IllegalArgumentException("Digest not supported for AES key: " + + Digest.fromKeymaster(keymasterDigest)); + } + return AES; + case KeymasterDefs.KM_ALGORITHM_HMAC: + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + return HMAC_SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return HMAC_SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return HMAC_SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return HMAC_SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return HMAC_SHA512; + default: + throw new IllegalArgumentException("Unsupported HMAC digest: " + + Digest.fromKeymaster(keymasterDigest)); + } + default: + throw new IllegalArgumentException( + "Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * @hide + * + * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. + */ + static int toKeymasterDigest(@AlgorithmEnum String algorithm) { + String algorithmUpper = algorithm.toUpperCase(Locale.US); + if (algorithmUpper.startsWith("HMAC")) { + String digestUpper = algorithmUpper.substring("HMAC".length()); + switch (digestUpper) { + 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( + "Unsupported HMAC digest: " + digestUpper); + } + } else { + return -1; + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + BlockMode.ECB, + BlockMode.CBC, + BlockMode.CTR, + BlockMode.GCM, + }) + public @interface BlockModeEnum {} + + /** + * Block modes that can be used when encrypting/decrypting using a key. + */ + public static abstract class BlockMode { + private BlockMode() {} + + /** Electronic Codebook (ECB) block mode. */ + public static final String ECB = "ECB"; + + /** Cipher Block Chaining (CBC) block mode. */ + public static final String CBC = "CBC"; + + /** Counter (CTR) block mode. */ + public static final String CTR = "CTR"; + + /** Galois/Counter Mode (GCM) block mode. */ + public static final String GCM = "GCM"; + + /** + * @hide + */ + static int toKeymaster(@BlockModeEnum String blockMode) { + if (ECB.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_ECB; + } else if (CBC.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CBC; + } else if (CTR.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CTR; + } else if (GCM.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_GCM; + } else { + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String fromKeymaster(int blockMode) { + switch (blockMode) { + 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("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String[] allFromKeymaster(Collection<Integer> blockModes) { + if ((blockModes == null) || (blockModes.isEmpty())) { + return EmptyArray.STRING; + } + @BlockModeEnum String[] result = new String[blockModes.size()]; + int offset = 0; + for (int blockMode : blockModes) { + result[offset] = fromKeymaster(blockMode); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@BlockModeEnum String[] blockModes) { + if ((blockModes == null) || (blockModes.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[blockModes.length]; + for (int i = 0; i < blockModes.length; i++) { + result[i] = toKeymaster(blockModes[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + EncryptionPadding.NONE, + EncryptionPadding.PKCS7, + EncryptionPadding.RSA_PKCS1, + EncryptionPadding.RSA_OAEP, + }) + public @interface EncryptionPaddingEnum {} + + /** + * Padding schemes for encryption/decryption. + */ + public static abstract class EncryptionPadding { + private EncryptionPadding() {} + + /** + * No padding. + */ + public static final String NONE = "NoPadding"; + + /** + * PKCS#7 padding. + */ + public static final String PKCS7 = "PKCS7Padding"; + + /** + * RSA PKCS#1 v1.5 padding for encryption/decryption. + */ + public static final String RSA_PKCS1 = "PKCS1Padding"; + + /** + * RSA Optimal Asymmetric Encryption Padding (OAEP). + */ + public static final String RSA_OAEP = "OAEPPadding"; + + /** + * @hide + */ + static int toKeymaster(@EncryptionPaddingEnum String padding) { + if (NONE.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_NONE; + } else if (PKCS7.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_PKCS7; + } else if (RSA_PKCS1.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; + } else if (RSA_OAEP.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_OAEP; + } else { + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @EncryptionPaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_NONE: + return NONE; + case KeymasterDefs.KM_PAD_PKCS7: + return PKCS7; + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_OAEP: + return RSA_OAEP; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@EncryptionPaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + SignaturePadding.RSA_PKCS1, + SignaturePadding.RSA_PSS, + }) + public @interface SignaturePaddingEnum {} + + /** + * Padding schemes for signing/verification. + */ + public static abstract class SignaturePadding { + private SignaturePadding() {} + + /** + * RSA PKCS#1 v1.5 padding for signatures. + */ + public static final String RSA_PKCS1 = "PKCS1"; + + /** + * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. + */ + public static final String RSA_PSS = "PSS"; + + /** + * @hide + */ + static int toKeymaster(@SignaturePaddingEnum String padding) { + switch (padding.toUpperCase(Locale.US)) { + case RSA_PKCS1: + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; + case RSA_PSS: + return KeymasterDefs.KM_PAD_RSA_PSS; + default: + throw new IllegalArgumentException( + "Unsupported signature padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @SignaturePaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_PSS: + return RSA_PSS; + default: + throw new IllegalArgumentException("Unsupported signature padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@SignaturePaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Digest.NONE, + Digest.MD5, + Digest.SHA1, + Digest.SHA224, + Digest.SHA256, + Digest.SHA384, + Digest.SHA512, + }) + public @interface DigestEnum {} + + /** + * Digests that can be used with a key when signing or generating Message Authentication + * Codes (MACs). + */ + public static abstract class Digest { + private Digest() {} + + /** + * No digest: sign/authenticate the raw message. + */ + public static final String NONE = "NONE"; + + /** + * MD5 digest. + */ + public static final String MD5 = "MD5"; + + /** + * SHA-1 digest. + */ + public static final String SHA1 = "SHA-1"; + + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final String SHA224 = "SHA-224"; + + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final String SHA256 = "SHA-256"; + + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final String SHA384 = "SHA-384"; + + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final String SHA512 = "SHA-512"; + + /** + * @hide + */ + static int toKeymaster(@DigestEnum String digest) { + switch (digest.toUpperCase(Locale.US)) { + 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; + case NONE: + return KeymasterDefs.KM_DIGEST_NONE; + case MD5: + return KeymasterDefs.KM_DIGEST_MD5; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String fromKeymaster(int digest) { + 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("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String[] allFromKeymaster(Collection<Integer> digests) { + if (digests.isEmpty()) { + return EmptyArray.STRING; + } + String[] result = new String[digests.size()]; + int offset = 0; + for (int digest : digests) { + result[offset] = fromKeymaster(digest); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@DigestEnum String[] digests) { + if ((digests == null) || (digests.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[digests.length]; + int offset = 0; + for (@DigestEnum String digest : digests) { + result[offset] = toKeymaster(digest); + offset++; + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) public @interface OriginEnum {} @@ -138,7 +656,7 @@ public abstract class KeyStoreKeyProperties { public static final int IMPORTED = 1 << 1; /** - * Origin of the key is unknown. This can occur only for keys backed by an old TEE + * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed * implementation which does not record origin information. */ public static final int UNKNOWN = 1 << 2; diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java index 96d58d8..0a9acbb 100644 --- a/keystore/java/android/security/KeyStoreKeySpec.java +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -16,50 +16,87 @@ package android.security; +import java.security.PrivateKey; import java.security.spec.KeySpec; import java.util.Date; +import javax.crypto.SecretKey; + /** * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android - * KeyStore</a>. + * KeyStore</a>. This class describes whether the key material is available in + * plaintext outside of secure hardware, whether user authentication is required for using the key + * and whether this requirement is enforced by secure hardware, the key's origin, what uses the key + * is authorized for (e.g., only in {@code CBC} mode, or signing only), whether the key should be + * encrypted at rest, the key's and validity start and end dates. + * + * <p><h3>Example: Symmetric Key</h3> + * The following example illustrates how to obtain a {@link KeyStoreKeySpec} describing the provided + * Android KeyStore {@link SecretKey}. + * <pre> {@code + * SecretKey key = ...; // Android KeyStore key + * + * SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore"); + * KeyStoreKeySpec spec; + * try { + * spec = (KeyStoreKeySpec) factory.getKeySpec(key, KeyStoreKeySpec.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</pre> + * + * <p><h3>Example: Private Key</h3> + * The following example illustrates how to obtain a {@link KeyStoreKeySpec} describing the provided + * Android KeyStore {@link PrivateKey}. + * <pre> {@code + * PrivateKey key = ...; // Android KeyStore key + * + * KeyFactory factory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore"); + * KeyStoreKeySpec spec; + * try { + * spec = factory.getKeySpec(key, KeyStoreKeySpec.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</pre> */ public class KeyStoreKeySpec implements KeySpec { private final String mKeystoreAlias; private final int mKeySize; - private final boolean mTeeBacked; + private final boolean mInsideSecureHardware; private final @KeyStoreKeyProperties.OriginEnum int mOrigin; private final Date mKeyValidityStart; private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; - private final boolean mUserAuthenticationRequirementTeeEnforced; + private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware; /** * @hide */ KeyStoreKeySpec(String keystoreKeyAlias, - boolean teeBacked, + boolean insideSecureHardware, @KeyStoreKeyProperties.OriginEnum int origin, int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, - boolean userAuthenticationRequirementTeeEnforced) { + boolean userAuthenticationRequirementEnforcedBySecureHardware) { mKeystoreAlias = keystoreKeyAlias; - mTeeBacked = teeBacked; + mInsideSecureHardware = insideSecureHardware; mOrigin = origin; mKeySize = keySize; mKeyValidityStart = keyValidityStart; @@ -74,7 +111,8 @@ public class KeyStoreKeySpec implements KeySpec { mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mUserAuthenticationRequired = userAuthenticationRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; - mUserAuthenticationRequirementTeeEnforced = userAuthenticationRequirementTeeEnforced; + mUserAuthenticationRequirementEnforcedBySecureHardware = + userAuthenticationRequirementEnforcedBySecureHardware; } /** @@ -85,11 +123,12 @@ public class KeyStoreKeySpec implements KeySpec { } /** - * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available - * in plaintext only inside the TEE. + * Returns {@code true} if the key resides inside secure hardware (e.g., Trusted Execution + * Environment (TEE) or Secure Element (SE)). Key material of such keys is available in + * plaintext only inside the secure hardware and is not exposed outside of it. */ - public boolean isTeeBacked() { - return mTeeBacked; + public boolean isInsideSecureHardware() { + return mInsideSecureHardware; } /** @@ -143,28 +182,28 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } /** * Gets the set of padding modes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding modes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } @@ -179,10 +218,10 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ @@ -192,11 +231,12 @@ public class KeyStoreKeySpec implements KeySpec { /** * Returns {@code true} if the requirement that this key can only be used if the user has been - * authenticated if enforced by the TEE. + * authenticated if enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or + * Secure Element (SE)). * * @see #isUserAuthenticationRequired() */ - public boolean isUserAuthenticationRequirementTeeEnforced() { - return mUserAuthenticationRequirementTeeEnforced; + public boolean isUserAuthenticationRequirementEnforcedBySecureHardware() { + return mUserAuthenticationRequirementEnforcedBySecureHardware; } } diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index b4747e9..7332332 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -16,27 +16,89 @@ package android.security; +import android.app.KeyguardManager; import android.content.Context; import java.security.Key; import java.security.KeyStore.ProtectionParameter; +import java.security.cert.Certificate; import java.util.Date; import javax.crypto.Cipher; /** - * 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} - * 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> - * Any entries placed in the {@code KeyStore} may be retrieved later. Note that - * 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}. + * Parameters specifying how to secure and restrict the use of a key or key pair being imported into + * the <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class + * specifies whether user authentication is required for using the key, what uses the key is + * authorized for (e.g., only in {@code CTR} mode, or only for signing -- decryption not permitted), + * whether the key should be encrypted at rest, the key's and validity start and end dates. + * + * <p>To import a key or key pair into the Android KeyStore, create an instance of this class using + * the {@link Builder} and pass the instance into {@link java.security.KeyStore#setEntry(String, java.security.KeyStore.Entry, ProtectionParameter) KeyStore.setEntry} + * with the key or key pair being imported. + * + * <p>To obtain the secret/symmetric or private key from the Android KeyStore use + * {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}. + * To obtain the public key from the Android KeyStore use + * {@link java.security.KeyStore#getCertificate(String)} and then + * {@link Certificate#getPublicKey()}. + * + * <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible. + * + * <p><h3>Example: Symmetric Key</h3> + * The following example illustrates how to import an AES key into the Android KeyStore under alias + * {@code key1} authorized to be used only for encryption/decryption in CBC mode with PKCS#7 + * padding. The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format. + * <pre> {@code + * SecretKey key = ...; // AES key + * + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * keyStore.setEntry( + * "key1", + * new KeyStore.SecretKeyEntry(key), + * new KeyStoreParameter.Builder(context) + * .setPurposes(KeyStoreKeyProperties.Purpose.ENCRYPT + * | KeyStoreKeyProperties.Purpose.DECRYPT) + * .setBlockMode(KeyStoreKeyProperties.BlockMode.CBC) + * .setEncryptionPaddings( + * KeyStoreKeyProperties.EncryptionPaddings.PKCS7) + * .build()); + * // Key imported, obtain a reference to it. + * SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null); + * // The original key can now be thrown away. + * }</pre> + * + * <p><h3>Example: Asymmetric Key Pair</h3> + * The following example illustrates how to import an EC key pair into the Android KeyStore under + * alias {@code key2} authorized to be used only for signing with SHA-256 digest and only if + * the user has been authenticated within the last ten minutes. Both the private and the public key + * must export their key material via {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509} + * format respectively. + * <pre> {@code + * PrivateKey privateKey = ...; // EC private key + * Certificate[] certChain = ...; // Certificate chain with the first certificate + * // containing the corresponding EC public key. + * + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * keyStore.setEntry( + * "key2", + * new KeyStore.PrivateKeyEntry(privateKey, certChain), + * new KeyStoreParameter.Builder(context) + * .setPurposes(KeyStoreKeyProperties.Purpose.SIGN) + * .setDigests(KeyStoreKeyProperties.Digest.SHA256) + * // Only permit this key to be used if the user + * // authenticated within the last ten minutes. + * .setUserAuthenticationRequired(true) + * .setUserAuthenticationValidityDurationSeconds(10 * 60) + * .build()); + * // Key pair imported, obtain a reference to it. + * PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null); + * PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey(); + * // The original private key can now be thrown away. + * }</pre> */ public final class KeyStoreParameter implements ProtectionParameter { private final Context mContext; @@ -45,10 +107,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -60,10 +122,10 @@ public final class KeyStoreParameter implements ProtectionParameter { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -107,8 +169,9 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Returns {@code true} if this parameter requires entries to be encrypted - * on the disk. + * Returns {@code true} if the {@link java.security.KeyStore} entry must be encrypted at rest. + * This will protect the entry with the secure lock screen credential (e.g., password, PIN, or + * pattern). */ public boolean isEncryptionRequired() { return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; @@ -151,7 +214,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } @@ -159,7 +222,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * Gets the set of padding schemes with which the key can be used when signing or verifying * signatures. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } @@ -170,7 +233,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * @see #isDigestsSpecified() */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { if (mDigests == null) { throw new IllegalStateException("Digests not specified"); } @@ -190,7 +253,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -218,10 +281,12 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. + * + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -240,7 +305,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <pre class="prettyprint"> * KeyStoreParameter params = new KeyStoreParameter.Builder(mContext) - * .setEncryptionRequired() + * .setEncryptionRequired(true) * .build(); * </pre> */ @@ -251,10 +316,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mSignaturePaddings; - private String[] mDigests; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -273,10 +338,17 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Indicates that this key must be encrypted at rest on storage. Note - * that enabling this will require that the user enable a strong lock - * screen (e.g., PIN, password) before creating or using the generated - * key is successful. + * Sets whether this {@link java.security.KeyStore} entry must be encrypted at rest. + * Encryption at rest will protect the entry with the secure lock screen credential (e.g., + * password, PIN, or pattern). + * + * <p>Note that enabling this feature requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise setting the {@code KeyStore} entry will fail. + * Moreover, this entry will be deleted when the secure lock screen is disabled or reset + * (e.g., by the user or a Device Administrator). Finally, this entry cannot be used until + * the user unlocks the secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() */ public Builder setEncryptionRequired(boolean required) { if (required) { @@ -292,6 +364,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -304,6 +378,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -319,6 +395,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -332,6 +410,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -343,6 +423,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -355,8 +437,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -367,8 +452,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -380,8 +468,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For * asymmetric signing keys this constraint must be specified. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -391,8 +481,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -430,6 +522,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <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> + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -449,6 +543,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More * information</a>. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -462,7 +558,9 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java index bfe09e3..548296b 100644 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -74,22 +74,22 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { + " Keystore error: " + errorCode); } - boolean teeBacked; + boolean insideSecureHardware; @KeyStoreKeyProperties.OriginEnum int origin; int keySize; @KeyStoreKeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; - String[] digests; - String[] blockModes; + @KeyStoreKeyProperties.DigestEnum String[] digests; + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; try { if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - teeBacked = true; + insideSecureHardware = true; origin = KeyStoreKeyProperties.Origin.fromKeymaster( keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { - teeBacked = false; + insideSecureHardware = false; origin = KeyStoreKeyProperties.Origin.fromKeymaster( keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); } else { @@ -105,10 +105,10 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { List<String> encryptionPaddingsList = new ArrayList<String>(); for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - String jcaPadding; + @KeyStoreKeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = KeymasterUtils.getJcaEncryptionPaddingFromKeymasterPadding( - keymasterPadding); + jcaPadding = + KeyStoreKeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); } catch (IllegalArgumentException e) { throw new InvalidKeySpecException( "Unsupported encryption padding: " + keymasterPadding); @@ -118,9 +118,9 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - digests = KeymasterUtils.getJcaDigestAlgorithmsFromKeymasterDigests( + digests = KeyStoreKeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeymasterUtils.getJcaBlockModesFromKeymasterBlockModes( + blockModes = KeyStoreKeyProperties.BlockMode.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); keymasterSwEnforcedUserAuthenticators = keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); @@ -150,12 +150,12 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); int userAuthenticationValidityDurationSeconds = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, -1); - boolean userAuthenticationRequirementEnforcedInTee = (userAuthenticationRequired) + boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) && (keymasterHwEnforcedUserAuthenticators != 0) && (keymasterSwEnforcedUserAuthenticators == 0); return new KeyStoreKeySpec(entryAlias, - teeBacked, + insideSecureHardware, origin, keySize, keyValidityStart, @@ -168,7 +168,7 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { blockModes, userAuthenticationRequired, userAuthenticationValidityDurationSeconds, - userAuthenticationRequirementEnforcedInTee); + userAuthenticationRequirementEnforcedBySecureHardware); } @Override diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index aa44ecd..df67ae7 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -21,11 +21,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; -import libcore.util.EmptyArray; - -import java.util.Collection; -import java.util.Locale; - /** * @hide */ @@ -33,152 +28,6 @@ public abstract class KeymasterUtils { private KeymasterUtils() {} - public static int getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - if ("AES".equalsIgnoreCase(jcaKeyAlgorithm)) { - return KeymasterDefs.KM_ALGORITHM_AES; - } else if (jcaKeyAlgorithm.toUpperCase(Locale.US).startsWith("HMAC")) { - return KeymasterDefs.KM_ALGORITHM_HMAC; - } else { - throw new IllegalArgumentException( - "Unsupported secret key algorithm: " + jcaKeyAlgorithm); - } - } - - public static String getJcaSecretKeyAlgorithm(int keymasterAlgorithm, int keymasterDigest) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_AES: - if (keymasterDigest != -1) { - throw new IllegalArgumentException( - "Digest not supported for AES key: " + keymasterDigest); - } - return "AES"; - case KeymasterDefs.KM_ALGORITHM_HMAC: - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_SHA1: - return "HmacSHA1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "HmacSHA224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "HmacSHA256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "HmacSHA384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "HmacSHA512"; - default: - throw new IllegalArgumentException( - "Unsupported HMAC digest: " + keymasterDigest); - } - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static String getJcaKeyPairAlgorithmFromKeymasterAlgorithm(int keymasterAlgorithm) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_RSA: - return "RSA"; - case KeymasterDefs.KM_ALGORITHM_EC: - return "EC"; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static int getKeymasterDigestfromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - String algorithmUpper = jcaKeyAlgorithm.toUpperCase(Locale.US); - if (algorithmUpper.startsWith("HMAC")) { - String digestUpper = algorithmUpper.substring("HMAC".length()); - switch (digestUpper) { - 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("Unsupported HMAC digest: " + digestUpper); - } - } else { - return -1; - } - } - - public static int getKeymasterDigestFromJcaDigestAlgorithm(String jcaDigestAlgorithm) { - if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-1")) { - return KeymasterDefs.KM_DIGEST_SHA1; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-224")) { - return KeymasterDefs.KM_DIGEST_SHA_2_224; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-256")) { - return KeymasterDefs.KM_DIGEST_SHA_2_256; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-384")) { - return KeymasterDefs.KM_DIGEST_SHA_2_384; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-512")) { - return KeymasterDefs.KM_DIGEST_SHA_2_512; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("NONE")) { - return KeymasterDefs.KM_DIGEST_NONE; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("MD5")) { - return KeymasterDefs.KM_DIGEST_MD5; - } else { - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + jcaDigestAlgorithm); - } - } - - public static String getJcaDigestAlgorithmFromKeymasterDigest(int keymasterDigest) { - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_NONE: - return "NONE"; - case KeymasterDefs.KM_DIGEST_MD5: - return "MD5"; - case KeymasterDefs.KM_DIGEST_SHA1: - return "SHA-1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "SHA-224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "SHA-256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "SHA-384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + keymasterDigest); - } - } - - public static String[] getJcaDigestAlgorithmsFromKeymasterDigests( - Collection<Integer> keymasterDigests) { - if (keymasterDigests.isEmpty()) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterDigests.size()]; - int offset = 0; - for (int keymasterDigest : keymasterDigests) { - result[offset] = getJcaDigestAlgorithmFromKeymasterDigest(keymasterDigest); - offset++; - } - return result; - } - - public static int[] getKeymasterDigestsFromJcaDigestAlgorithms(String[] jcaDigestAlgorithms) { - if ((jcaDigestAlgorithms == null) || (jcaDigestAlgorithms.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaDigestAlgorithms.length]; - int offset = 0; - for (String jcaDigestAlgorithm : jcaDigestAlgorithms) { - result[offset] = getKeymasterDigestFromJcaDigestAlgorithm(jcaDigestAlgorithm); - offset++; - } - return result; - } - public static int getDigestOutputSizeBits(int keymasterDigest) { switch (keymasterDigest) { case KeymasterDefs.KM_DIGEST_NONE: @@ -200,60 +49,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterBlockModeFromJcaBlockMode(String jcaBlockMode) { - if ("ECB".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_ECB; - } else if ("CBC".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CBC; - } else if ("CTR".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CTR; - } else if ("GCM".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_GCM; - } else { - throw new IllegalArgumentException("Unsupported block mode: " + jcaBlockMode); - } - } - - public static String getJcaBlockModeFromKeymasterBlockMode(int keymasterBlockMode) { - switch (keymasterBlockMode) { - 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("Unsupported block mode: " + keymasterBlockMode); - } - } - - public static String[] getJcaBlockModesFromKeymasterBlockModes( - Collection<Integer> keymasterBlockModes) { - if ((keymasterBlockModes == null) || (keymasterBlockModes.isEmpty())) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterBlockModes.size()]; - int offset = 0; - for (int keymasterBlockMode : keymasterBlockModes) { - result[offset] = getJcaBlockModeFromKeymasterBlockMode(keymasterBlockMode); - offset++; - } - return result; - } - - public static int[] getKeymasterBlockModesFromJcaBlockModes(String[] jcaBlockModes) { - if ((jcaBlockModes == null) || (jcaBlockModes.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaBlockModes.length]; - for (int i = 0; i < jcaBlockModes.length; i++) { - result[i] = getKeymasterBlockModeFromJcaBlockMode(jcaBlockModes[i]); - } - return result; - } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: @@ -267,82 +62,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterPaddingFromJcaEncryptionPadding(String jcaPadding) { - if ("NoPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_NONE; - } else if ("PKCS7Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_PKCS7; - } else if ("PKCS1Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; - } else if ("OEAPPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_OAEP; - } else { - throw new IllegalArgumentException( - "Unsupported encryption padding scheme: " + jcaPadding); - } - } - - public static String getJcaEncryptionPaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_NONE: - return "NoPadding"; - case KeymasterDefs.KM_PAD_PKCS7: - return "PKCS7Padding"; - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: - return "PKCS1Padding"; - case KeymasterDefs.KM_PAD_RSA_OAEP: - return "OEAPPadding"; - default: - throw new IllegalArgumentException( - "Unsupported encryption padding: " + keymasterPadding); - } - } - - public static int getKeymasterPaddingFromJcaSignaturePadding(String jcaPadding) { - if ("PKCS#1".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; - } if ("PSS".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PSS; - } else { - throw new IllegalArgumentException( - "Unsupported signature padding scheme: " + jcaPadding); - } - } - - public static String getJcaSignaturePaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: - return "PKCS#1"; - case KeymasterDefs.KM_PAD_RSA_PSS: - return "PSS"; - default: - throw new IllegalArgumentException( - "Unsupported signature padding: " + keymasterPadding); - } - } - - public static int[] getKeymasterPaddingsFromJcaEncryptionPaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaEncryptionPadding(jcaPaddings[i]); - } - return result; - } - - public static int[] getKeymasterPaddingsFromJcaSignaturePaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaSignaturePadding(jcaPaddings[i]); - } - return result; - } - /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. |