diff options
author | Alex Klyubin <klyubin@google.com> | 2015-03-27 14:39:28 -0700 |
---|---|---|
committer | Alex Klyubin <klyubin@google.com> | 2015-03-27 15:57:53 -0700 |
commit | d23a1f706f0c24fade1a1c2f604009a7c9e70002 (patch) | |
tree | 97a12bf99f2bf2c839554c609e5970bf30b1bd56 | |
parent | dbd9a4b651aed25a50976ca0a68a979cc3f299fa (diff) | |
download | frameworks_base-d23a1f706f0c24fade1a1c2f604009a7c9e70002.zip frameworks_base-d23a1f706f0c24fade1a1c2f604009a7c9e70002.tar.gz frameworks_base-d23a1f706f0c24fade1a1c2f604009a7c9e70002.tar.bz2 |
Symmetric key generation for AndroidKeyStore.
This currently supports AES and HMAC with SHA-256.
Bug: 18088752
Change-Id: Ife55438cf4129b895295681bb35091cd37eb73fb
8 files changed, 814 insertions, 0 deletions
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index e653b74..c2ebbc6 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -16,6 +16,9 @@ package android.security.keymaster; +import java.util.HashMap; +import java.util.Map; + /** * Class tracking all the keymaster enum values needed for the binder API to keystore. * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h @@ -224,7 +227,53 @@ public final class KeymasterDefs { public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; + public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>(); + static { + sErrorCodeToString.put(KM_ERROR_OK, "OK"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG_LENGTH, + "Unsupported authentication tag length"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest"); + sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time"); + sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID"); + sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT, + "Invalid user authorization timeout"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format"); + sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length"); + sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid"); + sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired"); + sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated"); + sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle"); + sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed"); + sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations"); + sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob"); + sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag"); + sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag"); + sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"); + sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); + sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); + } + public static int getTagType(int tag) { return tag & (0xF << 28); } + + public static String getErrorMessage(int errorCode) { + String result = sErrorCodeToString.get(errorCode); + if (result != null) { + return result; + } + return String.valueOf(errorCode); + } } diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index 9081e92..598bcd8 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -35,5 +35,9 @@ public class AndroidKeyStoreProvider extends Provider { // java.security.KeyPairGenerator put("KeyPairGenerator.EC", AndroidKeyPairGenerator.EC.class.getName()); put("KeyPairGenerator.RSA", AndroidKeyPairGenerator.RSA.class.getName()); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName()); + put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName()); } } diff --git a/keystore/java/android/security/CryptoOperationException.java b/keystore/java/android/security/CryptoOperationException.java new file mode 100644 index 0000000..ce64455 --- /dev/null +++ b/keystore/java/android/security/CryptoOperationException.java @@ -0,0 +1,45 @@ +package android.security; + +/** + * Base class for exceptions during cryptographic operations which cannot throw a suitable checked + * exception. + * + * <p>The contract of the majority of crypto primitives/operations (e.g. {@code Cipher} or + * {@code Signature}) is that they can throw a checked exception during initialization, but are not + * permitted to throw a checked exception during operation. Because crypto operations can fail + * for a variety of reasons after initialization, this base class provides type-safety for unchecked + * exceptions that may be thrown in those cases. + * + * @hide + */ +public class CryptoOperationException extends RuntimeException { + + /** + * Constructs a new {@code CryptoOperationException} without detail message and cause. + */ + public CryptoOperationException() { + super(); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided detail message and no + * cause. + */ + public CryptoOperationException(String message) { + super(message); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided detail message and cause. + */ + public CryptoOperationException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided cause. + */ + public CryptoOperationException(Throwable cause) { + super(cause); + } +} diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java new file mode 100644 index 0000000..6274b70 --- /dev/null +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -0,0 +1,471 @@ +package android.security; + +import android.content.Context; +import android.text.TextUtils; + +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.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>. + * + * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API + * using the {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up + * some UI to ask the user to unlock or initialize the Android KeyStore facility. + * + * <p>After generation, the {@code keyStoreAlias} is used with the + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)} + * interface to retrieve the {@link SecretKey} and its associated {@link Certificate} chain. + * + * @hide + */ +public class KeyGeneratorSpec implements AlgorithmParameterSpec { + + private final Context mContext; + private final String mKeystoreAlias; + private final int mFlags; + private final Integer mKeySize; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private final Integer mMinSecondsBetweenOperations; + private final Integer mMaxUsesPerBoot; + private final Set<Integer> mUserAuthenticators; + private final Integer mUserAuthenticationValidityDurationSeconds; + + private KeyGeneratorSpec( + Context context, + String keyStoreAlias, + int flags, + Integer keySize, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum Integer purposes, + @KeyStoreKeyConstraints.PaddingEnum Integer padding, + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, + Integer minSecondsBetweenOperations, + Integer maxUsesPerBoot, + Set<Integer> userAuthenticators, + Integer userAuthenticationValidityDurationSeconds) { + if (context == null) { + throw new IllegalArgumentException("context == null"); + } else if (TextUtils.isEmpty(keyStoreAlias)) { + throw new IllegalArgumentException("keyStoreAlias must not be empty"); + } else if ((userAuthenticationValidityDurationSeconds != null) + && (userAuthenticationValidityDurationSeconds < 0)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); + } + + mContext = context; + mKeystoreAlias = keyStoreAlias; + mFlags = flags; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mPadding = padding; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * Gets the Android context used for operations with this instance. + */ + public Context getContext() { + return mContext; + } + + /** + * Returns the alias that will be used in the {@code java.security.KeyStore} in conjunction with + * the {@code AndroidKeyStore}. + */ + public String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** + * Gets the requested key size or {@code null} if the default size should be used. + */ + public Integer getKeySize() { + return mKeySize; + } + + /** + * Gets the time instant before which the key is not yet valid. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key is no long valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no long valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used to the provided set of purposes. + * + * @return set of purposes or {@code null} if the key can be used for any purpose. + */ + public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() { + return mPurposes; + } + + /** + * Gets the padding scheme to which the key is restricted. + * + * @return padding scheme or {@code null} if the padding scheme is not restricted. + */ + public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { + return mPadding; + } + + /** + * Gets the block mode to which the key is restricted when used for encryption or decryption. + * + * @return block more or {@code null} if block mode is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() { + return mBlockMode; + } + + /** + * Gets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * @return number of seconds or {@code null} if there is no restriction on how frequently a key + * can be used. + * + * @hide + */ + public Integer getMinSecondsBetweenOperations() { + return mMinSecondsBetweenOperations; + } + + /** + * Gets the number of times the key can be used without rebooting the device. + * + * @return maximum number of times or {@code null} if there is no restriction. + * @hide + */ + public Integer getMaxUsesPerBoot() { + return mMaxUsesPerBoot; + } + + /** + * Gets the user authenticators which protect access to this key. The key can only be used iff + * the user has authenticated to at least one of these user authenticators. + * + * @return user authenticators or empty set if the key can be used without user authentication. + * + * @hide + */ + public Set<Integer> getUserAuthenticators() { + return new HashSet<Integer>(mUserAuthenticators); + } + + /** + * Gets the duration of time (seconds) for which this key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + * + * @hide + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** + * Returns {@code true} if the key must be encrypted in the {@link java.security.KeyStore}. + */ + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + public static class Builder { + private final Context mContext; + private String mKeystoreAlias; + private int mFlags; + private Integer mKeySize; + private Date mKeyValidityStart; + private Date mKeyValidityForOriginationEnd; + private Date mKeyValidityForConsumptionEnd; + private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private Integer mMinSecondsBetweenOperations; + private Integer mMaxUsesPerBoot; + private Set<Integer> mUserAuthenticators; + private Integer mUserAuthenticationValidityDurationSeconds; + + /** + * Creates a new instance of the {@code Builder} with the given {@code context}. 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. + */ + public Builder(Context context) { + if (context == null) { + throw new NullPointerException("context == null"); + } + mContext = context; + } + + /** + * Sets the alias to be used to retrieve the key later from a {@link java.security.KeyStore} + * instance using the {@code AndroidKeyStore} provider. + * + * <p>The alias must be provided. There is no default. + */ + public Builder setAlias(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + mKeystoreAlias = alias; + return this; + } + + /** + * Sets the size (in bits) of the key to be generated. + * + * <p>By default, the key size will be determines based on the key algorithm. For example, + * for {@code HmacSHA256}, the key size will default to {@code 256}. + */ + public Builder setKeySize(int keySize) { + mKeySize = keySize; + return this; + } + + /** + * 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. + */ + public Builder setEncryptionRequired(boolean required) { + if (required) { + mFlags |= KeyStore.FLAG_ENCRYPTED; + } else { + mFlags &= ~KeyStore.FLAG_ENCRYPTED; + } + return this; + } + + /** + * Sets the time instant before which the key is not yet valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityEnd(Date) + * + * @hide + */ + public Builder setKeyValidityStart(Date startDate) { + mKeyValidityStart = startDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityStart(Date) + * @see #setKeyValidityForConsumptionEnd(Date) + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityEnd(Date endDate) { + setKeyValidityForOriginationEnd(endDate); + setKeyValidityForConsumptionEnd(endDate); + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for encryption and signing. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForConsumptionEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForOriginationEnd(Date endDate) { + mKeyValidityForOriginationEnd = endDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for decryption and + * verification. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Restricts the purposes for which the key can be used to the provided set of purposes. + * + * <p>By default, the key can be used for encryption, decryption, signing, and verification. + * + * @hide + */ + public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) { + mPurposes = purposes; + return this; + } + + /** + * Restricts the key to being used only with the provided padding scheme. Attempts to use + * the key with any other padding will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) { + mPadding = padding; + return this; + } + + /** + * Restricts the key to being used only with the provided block mode when encrypting or + * decrypting. Attempts to use the key with any other block modes will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) { + mBlockMode = blockMode; + return this; + } + + /** + * Sets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * <p>By default, there is no restriction on how frequently a key can be used. + * + * @hide + */ + public Builder setMinSecondsBetweenOperations(int seconds) { + mMinSecondsBetweenOperations = seconds; + return this; + } + + /** + * Sets the maximum number of times a key can be used without rebooting the device. + * + * <p>By default, the key can be used for an unlimited number of times. + * + * @hide + */ + public Builder setMaxUsesPerBoot(int count) { + mMaxUsesPerBoot = count; + return this; + } + + /** + * Sets the user authenticators which protect access to this key. The key can only be used + * iff the user has authenticated to at least one of these user authenticators. + * + * <p>By default, the key can be used without user authentication. + * + * @param userAuthenticators user authenticators or empty list if this key can be accessed + * without user authentication. + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + * + * @hide + */ + public Builder setUserAuthenticators(Set<Integer> userAuthenticators) { + mUserAuthenticators = + (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null; + return this; + } + + /** + * Sets the duration of time (seconds) for which this key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * <p>By default, the user needs to authenticate for every use of the key. + * + * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticators(Set) + * + * @hide + */ + public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** + * Builds a new instance instance of {@code KeyGeneratorSpec}. + * + * @throws IllegalArgumentException if a required field is missing or violates a constraint. + */ + public KeyGeneratorSpec build() { + return new KeyGeneratorSpec(mContext, mKeystoreAlias, mFlags, mKeySize, + mKeyValidityStart, mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, + mPurposes, mPadding, mBlockMode, mMinSecondsBetweenOperations, mMaxUsesPerBoot, + mUserAuthenticators, mUserAuthenticationValidityDurationSeconds); + } + } +} diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java index 01e6dcd..47bb1cc 100644 --- a/keystore/java/android/security/KeyStoreKeyConstraints.java +++ b/keystore/java/android/security/KeyStoreKeyConstraints.java @@ -290,6 +290,22 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown padding: " + padding); } } + + /** + * @hide + */ + public static String toString(@PaddingEnum int padding) { + switch (padding) { + case NONE: + return "NONE"; + case ZERO: + return "ZERO"; + case PKCS7: + return "PKCS#7"; + default: + throw new IllegalArgumentException("Unknown padding: " + padding); + } + } } @Retention(RetentionPolicy.SOURCE) @@ -425,5 +441,17 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown block mode: " + mode); } } + + /** + * @hide + */ + public static String toString(@BlockModeEnum int mode) { + switch (mode) { + case ECB: + return "ECB"; + default: + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java new file mode 100644 index 0000000..86950dd --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -0,0 +1,183 @@ +package android.security; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidAlgorithmParameterException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@link KeyGeneratorSpi} backed by Android KeyStore. + * + * @hide + */ +public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + + public static class AES extends KeyStoreKeyGeneratorSpi { + public AES() { + super(KeyStoreKeyConstraints.Algorithm.AES, 128); + } + } + + public static class HmacSHA256 extends KeyStoreKeyGeneratorSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Algorithm.HMAC, + KeyStoreKeyConstraints.Digest.SHA256, + 256); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mDigest; + private final int mDefaultKeySizeBits; + + private KeyGeneratorSpec mSpec; + private SecureRandom mRng; + + protected KeyStoreKeyGeneratorSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + int defaultKeySizeBits) { + this(algorithm, null, defaultKeySizeBits); + } + + protected KeyStoreKeyGeneratorSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + int defaultKeySizeBits) { + mAlgorithm = algorithm; + mDigest = digest; + mDefaultKeySizeBits = defaultKeySizeBits; + } + + @Override + protected SecretKey engineGenerateKey() { + KeyGeneratorSpec spec = mSpec; + if (spec == null) { + throw new IllegalStateException("Not initialized"); + } + + if ((spec.isEncryptionRequired()) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Android KeyStore must be in initialized and unlocked state if encryption is" + + " required"); + } + + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, + KeyStoreKeyConstraints.Algorithm.toKeymaster(mAlgorithm)); + if (mDigest != null) { + args.addInt(KeymasterDefs.KM_TAG_DIGEST, + KeyStoreKeyConstraints.Digest.toKeymaster(mDigest)); + } + int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits; + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); + @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null) + ? spec.getPurposes() + : (KeyStoreKeyConstraints.Purpose.ENCRYPT + | KeyStoreKeyConstraints.Purpose.DECRYPT + | KeyStoreKeyConstraints.Purpose.SIGN + | KeyStoreKeyConstraints.Purpose.VERIFY); + for (int keymasterPurpose : + KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) { + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); + } + if (spec.getBlockMode() != null) { + args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyStoreKeyConstraints.BlockMode.toKeymaster(spec.getBlockMode())); + } + if (spec.getPadding() != null) { + args.addInt(KeymasterDefs.KM_TAG_PADDING, + KeyStoreKeyConstraints.Padding.toKeymaster(spec.getPadding())); + } + if (spec.getMaxUsesPerBoot() != null) { + args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, spec.getMaxUsesPerBoot()); + } + if (spec.getMinSecondsBetweenOperations() != null) { + args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS, + spec.getMinSecondsBetweenOperations()); + } + if (spec.getUserAuthenticators().isEmpty()) { + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + } else { + // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized +// for (int userAuthenticatorId : spec.getUserAuthenticators()) { +// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId); +// } + } + if (spec.getUserAuthenticationValidityDurationSeconds() != null) { + args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds()); + } + if (spec.getKeyValidityStart() != null) { + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); + } + if (spec.getKeyValidityForOriginationEnd() != null) { + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd()); + } + if (spec.getKeyValidityForConsumptionEnd() != null) { + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd()); + } + + if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0) + || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) { + // Permit caller-specified IV. This is needed due to the Cipher abstraction. + 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); + } + + int flags = spec.getFlags(); + String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + int errorCode = mKeyStore.generateKey( + keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics()); + if (errorCode != KeyStore.NO_ERROR) { + throw new CryptoOperationException("Failed to generate key", + KeymasterUtils.getExceptionForKeymasterError(errorCode)); + } + String keyAlgorithmJCA = + KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(mAlgorithm, mDigest); + 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"); + } + + mSpec = spec; + mRng = random; + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGeneratorSpec.class.getName() + " parameter"); + } +} diff --git a/keystore/java/android/security/KeymasterException.java b/keystore/java/android/security/KeymasterException.java new file mode 100644 index 0000000..4ff7115 --- /dev/null +++ b/keystore/java/android/security/KeymasterException.java @@ -0,0 +1,13 @@ +package android.security; + +/** + * Keymaster exception. + * + * @hide + */ +public class KeymasterException extends Exception { + + public KeymasterException(String message) { + super(message); + } +} diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java new file mode 100644 index 0000000..e6e88c7 --- /dev/null +++ b/keystore/java/android/security/KeymasterUtils.java @@ -0,0 +1,21 @@ +package android.security; + +import android.security.keymaster.KeymasterDefs; + +/** + * @hide + */ +public abstract class KeymasterUtils { + private KeymasterUtils() {} + + public static KeymasterException getExceptionForKeymasterError(int keymasterErrorCode) { + switch (keymasterErrorCode) { + case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: + // The name of this parameter significantly differs between Keymaster and framework + // APIs. Use the framework wording to make life easier for developers. + return new KeymasterException("Invalid user authentication validity duration"); + default: + return new KeymasterException(KeymasterDefs.getErrorMessage(keymasterErrorCode)); + } + } +} |