diff options
author | Alex Klyubin <klyubin@google.com> | 2015-03-31 23:32:40 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-03-31 23:32:41 +0000 |
commit | 19e79e12d82fa473f2f7beec337de11a0c3b3a03 (patch) | |
tree | b343591046b85799cb422221c01752e9c10b855b /keystore/java/android/security | |
parent | d720dedb03dfc937361e7e722171f8a1b9d74cf1 (diff) | |
parent | acc835f3857c1a1bf2618fabb7307794aba5d76a (diff) | |
download | frameworks_base-19e79e12d82fa473f2f7beec337de11a0c3b3a03.zip frameworks_base-19e79e12d82fa473f2f7beec337de11a0c3b3a03.tar.gz frameworks_base-19e79e12d82fa473f2f7beec337de11a0c3b3a03.tar.bz2 |
Merge "Add SecretKeyFactory backed by AndroidKeyStore."
Diffstat (limited to 'keystore/java/android/security')
6 files changed, 445 insertions, 1 deletions
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index 6cf9b7a..39f9d9c 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -40,6 +40,10 @@ public class AndroidKeyStoreProvider extends Provider { put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName()); put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName()); + // java.security.SecretKeyFactory + put("SecretKeyFactory.AES", KeyStoreSecretKeyFactorySpi.class.getName()); + put("SecretKeyFactory.HmacSHA256", KeyStoreSecretKeyFactorySpi.class.getName()); + // javax.crypto.Mac putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName()); diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java index 6274b70..61573f2 100644 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -146,7 +146,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the set of purposes for which the key can be used to the provided set of purposes. + * Gets the set of purposes for which the key can be used. * * @return set of purposes or {@code null} if the key can be used for any purpose. */ diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java new file mode 100644 index 0000000..c06dc3b --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java @@ -0,0 +1,52 @@ +package android.security; + +import android.annotation.IntDef; +import android.security.keymaster.KeymasterDefs; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Characteristics of {@code AndroidKeyStore} keys. + * + * @hide + */ +public abstract class KeyStoreKeyCharacteristics { + private KeyStoreKeyCharacteristics() {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED}) + public @interface OriginEnum {} + + /** + * Origin of the key. + */ + public static abstract class Origin { + private Origin() {} + + /** Key was generated inside a TEE. */ + public static final int GENERATED_INSIDE_TEE = 1; + + /** Key was generated outside of a TEE. */ + public static final int GENERATED_OUTSIDE_OF_TEE = 2; + + /** Key was imported. */ + public static final int IMPORTED = 0; + + /** + * @hide + */ + public static @OriginEnum int fromKeymaster(int origin) { + switch (origin) { + case KeymasterDefs.KM_ORIGIN_HARDWARE: + return GENERATED_INSIDE_TEE; + case KeymasterDefs.KM_ORIGIN_SOFTWARE: + return GENERATED_OUTSIDE_OF_TEE; + case KeymasterDefs.KM_ORIGIN_IMPORTED: + return IMPORTED; + default: + throw new IllegalArgumentException("Unknown origin: " + origin); + } + } + } +} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java new file mode 100644 index 0000000..19fba68 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -0,0 +1,210 @@ +package android.security; + +import java.security.spec.KeySpec; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android + * KeyStore</a>. + * + * @hide + */ +public class KeyStoreKeySpec implements KeySpec { + private final String mKeystoreAlias; + private final int mKeySize; + private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes; + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private final Integer mMinSecondsBetweenOperations; + private final Integer mMaxUsesPerBoot; + private final Set<Integer> mUserAuthenticators; + private final Set<Integer> mTeeBackedUserAuthenticators; + private final Integer mUserAuthenticationValidityDurationSeconds; + + + /** + * @hide + */ + KeyStoreKeySpec(String keystoreKeyAlias, + @KeyStoreKeyCharacteristics.OriginEnum int origin, + int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum int purposes, + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.PaddingEnum Integer padding, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, + Integer minSecondsBetweenOperations, + Integer maxUsesPerBoot, + Set<Integer> userAuthenticators, + Set<Integer> teeBackedUserAuthenticators, + Integer userAuthenticationValidityDurationSeconds) { + mKeystoreAlias = keystoreKeyAlias; + mOrigin = origin; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mAlgorithm = algorithm; + mPadding = padding; + mDigest = digest; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mTeeBackedUserAuthenticators = (teeBackedUserAuthenticators != null) + ? new HashSet<Integer>(teeBackedUserAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}. + */ + public String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * Gets the origin of the key. + */ + public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() { + return mOrigin; + } + + /** + * Gets the key's size in bits. + */ + public int getKeySize() { + return mKeySize; + } + + /** + * Gets the time instant before which the key is not yet valid. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key is no long valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no long valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used. + */ + public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Gets the algorithm of the key. + */ + public @KeyStoreKeyConstraints.AlgorithmEnum int getAlgorithm() { + return mAlgorithm; + } + + /** + * Gets the only block mode with which the key can be used. + * + * @return block mode or {@code null} if the block mode is not restricted. + */ + public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() { + return mBlockMode; + } + + /** + * Gets the only padding mode with which the key can be used. + * + * @return padding mode or {@code null} if the padding mode is not restricted. + */ + public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { + return mPadding; + } + + /** + * Gets the only digest algorithm with which the key can be used. + * + * @return digest algorithm or {@code null} if the digest algorithm is not restricted. + */ + public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() { + return mDigest; + } + + /** + * 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. + */ + 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. + */ + public Integer getMaxUsesPerBoot() { + return mMaxUsesPerBoot; + } + + /** + * Gets the user authenticators which protect access to the key. The key can only be used iff + * the user has authenticated to at least one of these user authenticators. + * + * @return user authenticators or empty set if the key can be used without user authentication. + */ + public Set<Integer> getUserAuthenticators() { + return new HashSet<Integer>(mUserAuthenticators); + } + + /** + * Gets the TEE-backed user authenticators which protect access to the key. This is a subset of + * the user authentications returned by {@link #getUserAuthenticators()}. + */ + public Set<Integer> getTeeBackedUserAuthenticators() { + return new HashSet<Integer>(mTeeBackedUserAuthenticators); + } + + /** + * Gets the duration of time (seconds) for which the key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } +} diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java new file mode 100644 index 0000000..70e643d --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,140 @@ +package android.security; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Collections; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link SecretKeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected KeySpec engineGetKeySpec(SecretKey key, + @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } + if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + + ((key != null) ? key.getClass().getName() : "null")); + } + if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) { + throw new InvalidKeySpecException( + "Key material export of Android KeyStore keys is not supported"); + } + if (!KeyStoreKeySpec.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = + mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new InvalidKeySpecException("Failed to obtain information about key." + + " Keystore error: " + errorCode); + } + + @KeyStoreKeyCharacteristics.OriginEnum Integer origin; + int keySize; + @KeyStoreKeyConstraints.PurposeEnum int purposes; + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm; + @KeyStoreKeyConstraints.PaddingEnum Integer padding; + @KeyStoreKeyConstraints.DigestEnum Integer digest; + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode; + try { + origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN); + if (origin == null) { + throw new InvalidKeySpecException("Key origin not available"); + } + origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin); + Integer keySizeInteger = + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE); + if (keySizeInteger == null) { + throw new InvalidKeySpecException("Key size not available"); + } + keySize = keySizeInteger; + purposes = KeyStoreKeyConstraints.Purpose.allFromKeymaster( + KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PURPOSE)); + Integer alg = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ALGORITHM); + if (alg == null) { + throw new InvalidKeySpecException("Key algorithm not available"); + } + algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg); + padding = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING); + if (padding != null) { + padding = KeyStoreKeyConstraints.Padding.fromKeymaster(padding); + } + digest = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST); + if (digest != null) { + digest = KeyStoreKeyConstraints.Digest.fromKeymaster(digest); + } + blockMode = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE); + if (blockMode != null) { + blockMode = KeyStoreKeyConstraints.BlockMode.fromKeymaster(blockMode); + } + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Unsupported key characteristic", e); + } + + // TODO: Read user authentication IDs once the Keymaster API has stabilized + Set<Integer> userAuthenticators = Collections.emptySet(); + Set<Integer> teeBackedUserAuthenticators = Collections.emptySet(); +// Set<Integer> userAuthenticators = new HashSet<Integer>( +// getInts(keyCharacteristics, KeymasterDefs.KM_TAG_USER_AUTH_ID)); +// Set<Integer> teeBackedUserAuthenticators = new HashSet<Integer>( +// keyCharacteristics.hwEnforced.getInts(KeymasterDefs.KM_TAG_USER_AUTH_ID)); + + return new KeyStoreKeySpec(entryAlias, + origin, + keySize, + KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME), + KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME), + KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME), + purposes, + algorithm, + padding, + digest, + blockMode, + KeymasterUtils.getInt(keyCharacteristics, + KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS), + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT), + userAuthenticators, + teeBackedUserAuthenticators, + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT)); + } + + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } +} diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index 4f17586..e9ad69f 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -1,7 +1,12 @@ package android.security; +import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + /** * @hide */ @@ -20,4 +25,37 @@ public abstract class KeymasterUtils { KeymasterDefs.getErrorMessage(keymasterErrorCode)); } } + + public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getInt(tag, -1); + } else if (keyCharacteristics.swEnforced.containsTag(tag)) { + return keyCharacteristics.swEnforced.getInt(tag, -1); + } else { + return null; + } + } + + public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) { + List<Integer> result = new ArrayList<Integer>(); + result.addAll(keyCharacteristics.hwEnforced.getInts(tag)); + result.addAll(keyCharacteristics.swEnforced.getInts(tag)); + return result; + } + + public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) { + Date result = keyCharacteristics.hwEnforced.getDate(tag, null); + if (result == null) { + result = keyCharacteristics.swEnforced.getDate(tag, null); + } + return result; + } + + public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getBoolean(tag, false); + } else { + return keyCharacteristics.swEnforced.getBoolean(tag, false); + } + } } |