diff options
Diffstat (limited to 'keystore/java/android')
23 files changed, 3437 insertions, 52 deletions
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index f3eb317..dcc79be 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi { String keyAlgorithmString = key.getAlgorithm(); @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm; - @KeyStoreKeyConstraints.AlgorithmEnum Integer digest; + @KeyStoreKeyConstraints.DigestEnum Integer digest; try { keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString); @@ -493,6 +493,19 @@ public class AndroidKeyStore extends KeyStoreSpi { if (digest != null) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeyStoreKeyConstraints.Digest.toKeymaster(digest)); + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest); + if (digestOutputSizeBytes != null) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); + } + } + if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) { + if (digest == null) { + throw new IllegalStateException("Digest algorithm must be specified for key" + + " algorithm " + keyAlgorithmString); + } } @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null) @@ -523,30 +536,33 @@ public class AndroidKeyStore extends KeyStoreSpi { if (params.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 : params.getUserAuthenticators()) { -// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId); -// } + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, + KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster( + params.getUserAuthenticators())); } if (params.getUserAuthenticationValidityDurationSeconds() != null) { args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, params.getUserAuthenticationValidityDurationSeconds()); } - if (params.getKeyValidityStart() != null) { - args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart()); - } - if (params.getKeyValidityForOriginationEnd() != null) { - args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - params.getKeyValidityForOriginationEnd()); - } - if (params.getKeyValidityForConsumptionEnd() != null) { - args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - params.getKeyValidityForConsumptionEnd()); - } + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (params.getKeyValidityStart() != null) + ? params.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (params.getKeyValidityForOriginationEnd() != null) + ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (params.getKeyValidityForConsumptionEnd() != null) + ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); // TODO: Remove this once keymaster does not require us to specify the size of imported key. args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); + if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0) + || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) { + // Permit caller-specified IV. This is needed for the Cipher abstraction. + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; int errorCode = mKeyStore.importKey( diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index 9081e92..a7c2ddb 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -16,8 +16,12 @@ package android.security; +import java.lang.reflect.Method; import java.security.Provider; +import javax.crypto.Cipher; +import javax.crypto.Mac; + /** * A provider focused on providing JCA interfaces for the Android KeyStore. * @@ -35,5 +39,78 @@ 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()); + + // java.security.SecretKeyFactory + put("SecretKeyFactory.AES", KeyStoreSecretKeyFactorySpi.class.getName()); + put("SecretKeyFactory.HmacSHA256", KeyStoreSecretKeyFactorySpi.class.getName()); + + // javax.crypto.Mac + putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName()); + + // javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + KeyStoreCipherSpi.AES.ECB.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + KeyStoreCipherSpi.AES.ECB.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + KeyStoreCipherSpi.AES.CBC.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + KeyStoreCipherSpi.AES.CBC.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + KeyStoreCipherSpi.AES.CTR.NoPadding.class.getName()); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); + } + + /** + * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * primitive. + * + * <p>The following primitives are supported: {@link Cipher} and {@link Mac}. + * + * @return KeyStore operation handle or {@code null} if the provided primitive's KeyStore + * operation is not in progress. + * + * @throws IllegalArgumentException if the provided primitive is not supported or is not backed + * by AndroidKeyStore provider. + */ + public static Long getKeyStoreOperationHandle(Object cryptoPrimitive) { + if (cryptoPrimitive == null) { + throw new NullPointerException(); + } + if ((!(cryptoPrimitive instanceof Mac)) && (!(cryptoPrimitive instanceof Cipher))) { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive); + } + Object spi; + // TODO: Replace this Reflection based codewith direct invocations once the libcore changes + // are in. + try { + Method getSpiMethod = cryptoPrimitive.getClass().getDeclaredMethod("getSpi"); + getSpiMethod.setAccessible(true); + spi = getSpiMethod.invoke(cryptoPrimitive); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException( + "Unsupported crypto primitive: " + cryptoPrimitive, e); + } + if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by Android KeyStore: " + cryptoPrimitive + + ", spi: " + spi); + } + return ((KeyStoreCryptoOperation) spi).getOperationHandle(); } } diff --git a/keystore/java/android/security/CryptoOperationException.java b/keystore/java/android/security/CryptoOperationException.java new file mode 100644 index 0000000..00c142f --- /dev/null +++ b/keystore/java/android/security/CryptoOperationException.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * 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/KeyExpiredException.java b/keystore/java/android/security/KeyExpiredException.java new file mode 100644 index 0000000..35a5acc --- /dev/null +++ b/keystore/java/android/security/KeyExpiredException.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Indicates that a cryptographic operation failed because the employed key's validity end date + * is in the past. + * + * @hide + */ +public class KeyExpiredException extends CryptoOperationException { + + /** + * Constructs a new {@code KeyExpiredException} without detail message and cause. + */ + public KeyExpiredException() { + super("Key expired"); + } + + /** + * Constructs a new {@code KeyExpiredException} with the provided detail message and no cause. + */ + public KeyExpiredException(String message) { + super(message); + } + + /** + * Constructs a new {@code KeyExpiredException} with the provided detail message and cause. + */ + public KeyExpiredException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java new file mode 100644 index 0000000..1311368 --- /dev/null +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.content.Context; +import android.text.TextUtils; + +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.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 longer valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no longer 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. + * + * @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/KeyNotYetValidException.java b/keystore/java/android/security/KeyNotYetValidException.java new file mode 100644 index 0000000..f1c2cac --- /dev/null +++ b/keystore/java/android/security/KeyNotYetValidException.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Indicates that a cryptographic operation failed because the employed key's validity start date + * is in the future. + * + * @hide + */ +public class KeyNotYetValidException extends CryptoOperationException { + + /** + * Constructs a new {@code KeyNotYetValidException} without detail message and cause. + */ + public KeyNotYetValidException() { + super("Key not yet valid"); + } + + /** + * Constructs a new {@code KeyNotYetValidException} with the provided detail message and no + * cause. + */ + public KeyNotYetValidException(String message) { + super(message); + } + + /** + * Constructs a new {@code KeyNotYetValidException} with the provided detail message and cause. + */ + public KeyNotYetValidException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index cc097aa..0001604 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -24,7 +24,10 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; 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.security.auth.x500.X500Principal; @@ -72,6 +75,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final int mFlags; + private final Date mKeyValidityStart; + + private final Date mKeyValidityForOriginationEnd; + + private final Date mKeyValidityForConsumptionEnd; + + private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + + 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; + /** * Parameter specification for the "{@code AndroidKeyPairGenerator}" * instance of the {@link java.security.KeyPairGenerator} API. The @@ -106,7 +131,18 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { */ public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, - Date startDate, Date endDate, int flags) { + Date startDate, Date endDate, int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum Integer purposes, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + @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)) { @@ -121,6 +157,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { throw new IllegalArgumentException("endDate == null"); } else if (endDate.before(startDate)) { throw new IllegalArgumentException("endDate < startDate"); + } else if ((userAuthenticationValidityDurationSeconds != null) + && (userAuthenticationValidityDurationSeconds < 0)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); } mContext = context; @@ -133,6 +173,31 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mStartDate = startDate; mEndDate = endDate; mFlags = flags; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mDigest = digest; + mPadding = padding; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * TODO: Remove this constructor once tests are switched over to the new one above. + * @hide + */ + public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, + AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, + Date startDate, Date endDate, int flags) { + this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate, + endDate, flags, startDate, endDate, endDate, null, null, null, null, null, null, + null, null); } /** @@ -222,6 +287,145 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * Gets the time instant before which the key pair is not yet valid. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key pair is no longer valid for decryption and + * verification. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key pair is no longer valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used. + * + * @return set of purposes or {@code null} if the key can be used for any purpose. + * + * @hide + */ + public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() { + return mPurposes; + } + + /** + * Gets the digest to which the key is restricted. + * + * @return digest or {@code null} if the digest is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() { + return mDigest; + } + + /** + * Gets the padding scheme to which the key is restricted. + * + * @return padding scheme or {@code null} if the padding scheme is not restricted. + * + * @hide + */ + 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 private + * key before it can be used again. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @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 private key can be used without rebooting the device. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @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 the private key. The key can only be + * used iff the user has authenticated to at least one of these user authenticators. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return user authenticators or 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 the private key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + * + * @hide + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** * Builder class for {@link KeyPairGeneratorSpec} objects. * <p> * This will build a parameter spec for use with the <a href="{@docRoot} @@ -263,6 +467,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private int mFlags; + private Date mKeyValidityStart; + + private Date mKeyValidityForOriginationEnd; + + private Date mKeyValidityForConsumptionEnd; + + private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + + private @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + + 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 @@ -389,14 +615,218 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * 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 digest. Attempts to use the key + * with any other digests be rejected. + * + * <p>This restriction must be specified for keys which are used for signing/verification. + * + * @hide + */ + public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) { + mDigest = digest; + 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. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @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. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @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. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @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. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticators(Set) + * + * @hide + */ + public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** * Builds the instance of the {@code KeyPairGeneratorSpec}. * * @throws IllegalArgumentException if a required field is missing * @return built instance of {@code KeyPairGeneratorSpec} */ public KeyPairGeneratorSpec build() { - return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec, - mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags); + return new KeyPairGeneratorSpec(mContext, + mKeystoreAlias, + mKeyType, + mKeySize, + mSpec, + mSubjectDN, + mSerialNumber, + mStartDate, + mEndDate, + mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mDigest, + mPadding, + mBlockMode, + mMinSecondsBetweenOperations, + mMaxUsesPerBoot, + mUserAuthenticators, + mUserAuthenticationValidityDurationSeconds); } } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 957e3c1..94a479b 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -389,19 +389,19 @@ public class KeyStore { } } - public int generateKey(String alias, KeymasterArguments args, int uid, int flags, - KeyCharacteristics outCharacteristics) { + public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid, + int flags, KeyCharacteristics outCharacteristics) { try { - return mBinder.generateKey(alias, args, uid, flags, outCharacteristics); + return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return SYSTEM_ERROR; } } - public int generateKey(String alias, KeymasterArguments args, int flags, + public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int flags, KeyCharacteristics outCharacteristics) { - return generateKey(alias, args, UID_SELF, flags, outCharacteristics); + return generateKey(alias, args, entropy, UID_SELF, flags, outCharacteristics); } public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId, @@ -441,9 +441,9 @@ public class KeyStore { } public OperationResult begin(String alias, int purpose, boolean pruneable, - KeymasterArguments args, KeymasterArguments outArgs) { + KeymasterArguments args, byte[] entropy, KeymasterArguments outArgs) { try { - return mBinder.begin(getToken(), alias, purpose, pruneable, args, outArgs); + return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, outArgs); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; @@ -476,4 +476,34 @@ public class KeyStore { return SYSTEM_ERROR; } } + + /** + * Check if the operation referenced by {@code token} is currently authorized. + * + * @param token An operation token returned by a call to {@link KeyStore.begin}. + */ + public boolean isOperationAuthorized(IBinder token) { + try { + return mBinder.isOperationAuthorized(token); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + /** + * Add an authentication record to the keystore authorization table. + * + * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. + * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to + * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. + */ + public int addAuthToken(byte[] authToken) { + try { + return mBinder.addAuthToken(authToken); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } } diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java new file mode 100644 index 0000000..afb5e36 --- /dev/null +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.IBinder; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers. + * + * @hide + */ +public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation { + + public abstract static class AES extends KeyStoreCipherSpi { + protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) { + super(KeyStoreKeyConstraints.Algorithm.AES, + blockMode, + padding, + 16, + ivUsed); + } + + public abstract static class ECB extends AES { + protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CBC extends AES { + protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CTR extends AES { + protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + } + } + + private final KeyStore mKeyStore; + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode; + private final @KeyStoreKeyConstraints.PaddingEnum int mPadding; + private final int mBlockSizeBytes; + private final boolean mIvUsed; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + protected boolean mEncrypting; + private KeyStoreSecretKey mKey; + private SecureRandom mRng; + private boolean mFirstOperationInitiated; + byte[] mIv; + + // Fields below must be reset + private byte[] mAdditionalEntropyForBegin; + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some + * error conditions in between. + */ + private IBinder mOperationToken; + private Long mOperationHandle; + private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; + + protected KeyStoreCipherSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, + int blockSizeBytes, + boolean ivUsed) { + mKeyStore = KeyStore.getInstance(); + mAlgorithm = algorithm; + mBlockMode = blockMode; + mPadding = padding; + mBlockSizeBytes = blockSizeBytes; + mIvUsed = ivUsed; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + reset(); + if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + mKey = (KeyStoreSecretKey) key; + mRng = random; + mIv = null; + mFirstOperationInitiated = false; + + if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { + throw new UnsupportedOperationException( + "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + } + mEncrypting = opmode == Cipher.ENCRYPT_MODE; + } + + private void reset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = null; + mMainDataStreamer = null; + mAdditionalEntropyForBegin = null; + } + + private void ensureKeystoreOperationInitialized() { + if (mMainDataStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + mAdditionalEntropyForBegin, + keymasterOutputArgs); + mAdditionalEntropyForBegin = null; + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getCryptoOperationException(opResult.resultCode); + } + + if (opResult.token == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); + mFirstOperationInitiated = true; + mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, opResult.token)); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + ensureKeystoreOperationInitialized(); + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + + if (output.length == 0) { + return null; + } + + return output; + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException { + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + ensureKeystoreOperationInitialized(); + + byte[] output; + try { + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + } catch (KeymasterException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw new IllegalBlockSizeException(); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw new BadPaddingException(); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw new AEADBadTagException(); + default: + throw KeymasterUtils.getCryptoOperationException(e); + } + } + + reset(); + return output; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected int engineGetBlockSize() { + return mBlockSizeBytes; + } + + @Override + protected byte[] engineGetIV() { + return (mIv != null) ? mIv.clone() : null; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * engineGetBlockSize(); + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public Long getOperationHandle() { + return mOperationHandle; + } + + // The methods below may need to be overridden by subclasses that use algorithm-specific + // parameters. + + /** + * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} + * if no algorithm-specific parameters are used. + * + * <p>This implementation only handles the IV parameter. + */ + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvUsed) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new RuntimeException( + "Failed to initialize AES AlgorithmParameters with an IV", e); + } + } + return null; + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameter. + * + * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured + * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvUsed) { + return; + } + + // IV is used + if (!mEncrypting) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * <p>The default implementation takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + if (!mFirstOperationInitiated) { + // First begin operation -- see if we need to provide additional entropy for IV + // generation. + if (mIvUsed) { + // IV is needed + if ((mIv == null) && (mEncrypting)) { + // TODO: Switch to keymaster-generated IV code below once keymaster supports + // that. + // IV is needed but was not provided by the caller -- generate an IV. + mIv = new byte[mBlockSizeBytes]; + SecureRandom rng = (mRng != null) ? mRng : new SecureRandom(); + rng.nextBytes(mIv); +// // IV was not provided by the caller and thus will be generated by keymaster. +// // Mix in some additional entropy from the provided SecureRandom. +// if (mRng != null) { +// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; +// mRng.nextBytes(mAdditionalEntropyForBegin); +// } + } + } + } + + if ((mIvUsed) && (mIv != null)) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + /** + * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the + * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after + * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. + * + * <p>The default implementation only takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeymasterArguments keymasterArgs) { + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvUsed) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new CryptoOperationException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new CryptoOperationException( + "IV in use despite IV not being used by this transformation"); + } + } + } +} diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java new file mode 100644 index 0000000..8ed6e04 --- /dev/null +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Indicates a communications error with keystore service. + * + * @hide + */ +public class KeyStoreConnectException extends CryptoOperationException { + public KeyStoreConnectException() { + super("Failed to communicate with keystore service"); + } +} diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/KeyStoreCryptoOperation.java new file mode 100644 index 0000000..19abd05 --- /dev/null +++ b/keystore/java/android/security/KeyStoreCryptoOperation.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Cryptographic operation backed by {@link KeyStore}. + * + * @hide + */ +public interface KeyStoreCryptoOperation { + /** + * Gets the KeyStore operation handle of this crypto operation. + * + * @return handle or {@code null} if the KeyStore operation is not in progress. + */ + Long getOperationHandle(); +} diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 0000000..993614b --- /dev/null +++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.IBinder; +import android.security.keymaster.OperationResult; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement + * various JCA crypto primitives. + * + * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as + * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional + * parameters to {@code update} and {@code final} operations. + * + * @hide + */ +public class KeyStoreCryptoOperationChunkedStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + public interface Stream { + /** + * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't + * be reached. + */ + OperationResult update(byte[] input); + + /** + * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't + * be reached. + */ + OperationResult finish(); + } + + // Binder buffer is about 1MB, but it's shared between all active transactions of the process. + // Thus, it's safer to use a much smaller upper bound. + private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private final Stream mKeyStoreStream; + private final int mMaxChunkSize; + + private byte[] mBuffered = EMPTY_BYTE_ARRAY; + private int mBufferedOffset; + private int mBufferedLength; + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_MAX_CHUNK_SIZE); + } + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) { + mKeyStoreStream = operation; + mMaxChunkSize = maxChunkSize; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException { + if (inputLength == 0) { + // No input provided + return EMPTY_BYTE_ARRAY; + } + + ByteArrayOutputStream bufferedOutput = null; + + while (inputLength > 0) { + byte[] chunk; + int inputBytesInChunk; + if ((mBufferedLength + inputLength) > mMaxChunkSize) { + // Too much input for one chunk -- extract one max-sized chunk and feed it into the + // update operation. + inputBytesInChunk = mMaxChunkSize - mBufferedLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } else { + // All of available input fits into one chunk. + if ((mBufferedLength == 0) && (inputOffset == 0) + && (inputLength == input.length)) { + // Nothing buffered and all of input array needs to be fed into the update + // operation. + chunk = input; + inputBytesInChunk = input.length; + } else { + // Need to combine buffered data with input data into one array. + inputBytesInChunk = inputLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } + } + // Update input array references to reflect that some of its bytes are now in mBuffered. + inputOffset += inputBytesInChunk; + inputLength -= inputBytesInChunk; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + if (opResult.inputConsumed == chunk.length) { + // The whole chunk was consumed + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedOffset = 0; + mBufferedLength = 0; + } else if (opResult.inputConsumed == 0) { + // Nothing was consumed. More input needed. + if (inputLength > 0) { + // More input is available, but it wasn't included into the previous chunk + // because the chunk reached its maximum permitted size. + // Shouldn't have happened. + throw new CryptoOperationException("Nothing consumed from max-sized chunk: " + + chunk.length + " bytes"); + } + mBuffered = chunk; + mBufferedOffset = 0; + mBufferedLength = chunk.length; + } else if (opResult.inputConsumed < chunk.length) { + // The chunk was consumed only partially -- buffer the rest of the chunk + mBuffered = chunk; + mBufferedOffset = opResult.inputConsumed; + mBufferedLength = chunk.length - opResult.inputConsumed; + } else { + throw new CryptoOperationException("Consumed more than provided: " + + opResult.inputConsumed + ", provided: " + chunk.length); + } + + if ((opResult.output != null) && (opResult.output.length > 0)) { + if (inputLength > 0) { + // More output might be produced in this loop -- buffer the current output + if (bufferedOutput == null) { + bufferedOutput = new ByteArrayOutputStream(); + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + } + } else { + // No more output will be produced in this loop + if (bufferedOutput == null) { + // No previously buffered output + return opResult.output; + } else { + // There was some previously buffered output + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + return bufferedOutput.toByteArray(); + } + } + } + } + + if (bufferedOutput == null) { + // No output produced + return EMPTY_BYTE_ARRAY; + } else { + return bufferedOutput.toByteArray(); + } + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + throws KeymasterException { + if (inputLength == 0) { + // No input provided -- simplify the rest of the code + input = EMPTY_BYTE_ARRAY; + inputOffset = 0; + } + + // Flush all buffered input and provided input into keystore/keymaster. + byte[] output = update(input, inputOffset, inputLength); + output = concat(output, flush()); + + OperationResult opResult = mKeyStoreStream.finish(); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + return concat(output, opResult.output); + } + + /** + * Passes all of buffered input into the the KeyStore operation (via the {@code update} + * operation) and returns output. + */ + public byte[] flush() throws KeymasterException { + if (mBufferedLength <= 0) { + return EMPTY_BYTE_ARRAY; + } + + byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength); + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedLength = 0; + mBufferedOffset = 0; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + if (opResult.inputConsumed < chunk.length) { + throw new CryptoOperationException("Keystore failed to consume all input. Provided: " + + chunk.length + ", consumed: " + opResult.inputConsumed); + } else if (opResult.inputConsumed > chunk.length) { + throw new CryptoOperationException("Keystore consumed more input than provided" + + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); + } + + return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY; + } + + private static byte[] concat(byte[] arr1, byte[] arr2) { + if ((arr1 == null) || (arr1.length == 0)) { + return arr2; + } else if ((arr2 == null) || (arr2.length == 0)) { + return arr1; + } else { + byte[] result = new byte[arr1.length + arr2.length]; + System.arraycopy(arr1, 0, result, 0, arr1.length); + System.arraycopy(arr2, 0, result, arr1.length, arr2.length); + return result; + } + } + + private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, + int len2) { + if (len1 == 0) { + return subarray(arr2, offset2, len2); + } else if (len2 == 0) { + return subarray(arr1, offset1, len1); + } else { + byte[] result = new byte[len1 + len2]; + System.arraycopy(arr1, offset1, result, 0, len1); + System.arraycopy(arr2, offset2, result, len1, len2); + return result; + } + } + + private static byte[] subarray(byte[] arr, int offset, int len) { + if (len == 0) { + return EMPTY_BYTE_ARRAY; + } + if ((offset == 0) && (len == arr.length)) { + return arr; + } + byte[] result = new byte[len]; + System.arraycopy(arr, offset, result, 0, len); + return result; + } + + /** + * Main data stream via a KeyStore streaming operation. + * + * <p>For example, for an encryption operation, this is the stream through which plaintext is + * provided and ciphertext is obtained. + */ + public static class MainDataStream implements Stream { + + private final KeyStore mKeyStore; + private final IBinder mOperationToken; + + public MainDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + return mKeyStore.update(mOperationToken, null, input); + } + + @Override + public OperationResult finish() { + return mKeyStore.finish(mOperationToken, null, null); + } + } +} diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java new file mode 100644 index 0000000..6d0e1ae --- /dev/null +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.os.IBinder; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.MacSpi; + +/** + * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. + * + * @hide + */ +public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + + public static class HmacSHA256 extends KeyStoreHmacSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.DigestEnum int mDigest; + private final int mMacSizeBytes; + + private String mKeyAliasInKeyStore; + + // The fields below are reset by the engineReset operation. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private IBinder mOperationToken; + private Long mOperationHandle; + + protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) { + mDigest = digest; + mMacSizeBytes = macSizeBytes; + } + + @Override + protected int engineGetMacLength() { + return mMacSizeBytes; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias(); + if (mKeyAliasInKeyStore == null) { + throw new InvalidKeyException("Key's KeyStore alias not known"); + } + engineReset(); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineReset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = null; + mChunkedStreamer = null; + } + + private void ensureKeystoreOperationInitialized() { + if (mChunkedStreamer != null) { + return; + } + if (mKeyAliasInKeyStore == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterArgs = new KeymasterArguments(); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeyStoreKeyConstraints.Algorithm.HMAC); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest); + + OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore, + KeymasterDefs.KM_PURPOSE_SIGN, + true, + keymasterArgs, + null, + new KeymasterArguments()); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getCryptoOperationException(opResult.resultCode); + } + if (opResult.token == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, mOperationToken)); + } + + @Override + protected void engineUpdate(byte input) { + engineUpdate(new byte[] {input}, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + ensureKeystoreOperationInitialized(); + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + if ((output != null) && (output.length != 0)) { + throw new CryptoOperationException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + ensureKeystoreOperationInitialized(); + + byte[] result; + try { + result = mChunkedStreamer.doFinal(null, 0, 0); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + + engineReset(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public Long getOperationHandle() { + return mOperationHandle; + } +} diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java new file mode 100644 index 0000000..543b5d8 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.annotation.IntDef; +import android.security.keymaster.KeymasterDefs; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Characteristics of {@code AndroidKeyStore} keys. + * + * @hide + */ +public abstract class KeyStoreKeyCharacteristics { + private KeyStoreKeyCharacteristics() {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({Origin.GENERATED_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/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java index 01e6dcd..c27ccb1 100644 --- a/keystore/java/android/security/KeyStoreKeyConstraints.java +++ b/keystore/java/android/security/KeyStoreKeyConstraints.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.security; import android.annotation.IntDef; @@ -7,7 +23,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; /** * Constraints for {@code AndroidKeyStore} keys. @@ -222,16 +241,6 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm); } } - - /** - * @hide - */ - public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) { - switch (algorithm) { - default: - throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm); - } - } } @Retention(RetentionPolicy.SOURCE) @@ -290,6 +299,36 @@ 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); + } + } + + /** + * @hide + */ + public static @PaddingEnum int fromJCAPadding(String padding) { + String paddingLower = padding.toLowerCase(Locale.US); + if ("nopadding".equals(paddingLower)) { + return NONE; + } else if ("pkcs7padding".equals(paddingLower)) { + return PKCS7; + } else { + throw new IllegalArgumentException("Unknown padding: " + padding); + } + } } @Retention(RetentionPolicy.SOURCE) @@ -385,10 +424,24 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown digest: " + digest); } } + + /** + * @hide + */ + public static Integer getOutputSizeBytes(@DigestEnum int digest) { + switch (digest) { + case NONE: + return null; + case SHA256: + return 256 / 8; + default: + throw new IllegalArgumentException("Unknown digest: " + digest); + } + } } @Retention(RetentionPolicy.SOURCE) - @IntDef({BlockMode.ECB}) + @IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR}) public @interface BlockModeEnum {} /** @@ -397,11 +450,15 @@ public abstract class KeyStoreKeyConstraints { public static abstract class BlockMode { private BlockMode() {} - /** - * Electronic Codebook (ECB) block mode. - */ + /** Electronic Codebook (ECB) block mode. */ public static final int ECB = 0; + /** Cipher Block Chaining (CBC) block mode. */ + public static final int CBC = 1; + + /** Counter (CTR) block mode. */ + public static final int CTR = 2; + /** * @hide */ @@ -409,6 +466,10 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case ECB: return KeymasterDefs.KM_MODE_ECB; + case CBC: + return KeymasterDefs.KM_MODE_CBC; + case CTR: + return KeymasterDefs.KM_MODE_CTR; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } @@ -421,9 +482,128 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case KeymasterDefs.KM_MODE_ECB: return ECB; + case KeymasterDefs.KM_MODE_CBC: + return CBC; + case KeymasterDefs.KM_MODE_CTR: + return CTR; + default: + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } + + /** + * @hide + */ + public static String toString(@BlockModeEnum int mode) { + switch (mode) { + case ECB: + return "ECB"; + case CBC: + return "CBC"; + case CTR: + return "CTR"; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } } + + /** + * @hide + */ + public static @BlockModeEnum int fromJCAMode(String mode) { + String modeLower = mode.toLowerCase(Locale.US); + if ("ecb".equals(modeLower)) { + return ECB; + } else if ("cbc".equals(modeLower)) { + return CBC; + } else if ("ctr".equals(modeLower)) { + return CTR; + } else { + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UserAuthenticator.LOCK_SCREEN}) + public @interface UserAuthenticatorEnum {} + + /** + * User authenticators which can be used to restrict/protect access to keys. + */ + public static abstract class UserAuthenticator { + private UserAuthenticator() {} + + /** Lock screen. */ + public static final int LOCK_SCREEN = 1; + + /** + * @hide + */ + public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return LOCK_SCREEN; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return LOCK_SCREEN; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static int allToKeymaster(Set<Integer> userAuthenticators) { + int result = 0; + for (@UserAuthenticatorEnum int userAuthenticator : userAuthenticators) { + result |= toKeymaster(userAuthenticator); + } + return result; + } + + /** + * @hide + */ + public static Set<Integer> allFromKeymaster(int userAuthenticators) { + int userAuthenticator = 1; + Set<Integer> result = null; + while (userAuthenticators != 0) { + if ((userAuthenticators & 1) != 0) { + if (result == null) { + result = new HashSet<Integer>(); + } + result.add(fromKeymaster(userAuthenticator)); + } + userAuthenticators >>>= 1; + userAuthenticator <<= 1; + } + return (result != null) ? result : Collections.<Integer>emptySet(); + } + + /** + * @hide + */ + public static String toString(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return "LOCK_SCREEN"; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java new file mode 100644 index 0000000..69533b4 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidAlgorithmParameterException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Date; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@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, + KeyStoreKeyConstraints.Digest.getOutputSizeBytes( + KeyStoreKeyConstraints.Digest.SHA256) * 8); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.DigestEnum 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)); + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest); + if (digestOutputSizeBytes != null) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); + } + } + if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) { + if (mDigest == null) { + throw new IllegalStateException("Digest algorithm must be specified for key" + + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm)); + } + } + int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits; + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); + @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null) + ? spec.getPurposes() + : (KeyStoreKeyConstraints.Purpose.ENCRYPT + | KeyStoreKeyConstraints.Purpose.DECRYPT + | KeyStoreKeyConstraints.Purpose.SIGN + | KeyStoreKeyConstraints.Purpose.VERIFY); + 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 { + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, + KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster( + spec.getUserAuthenticators())); + } + if (spec.getUserAuthenticationValidityDurationSeconds() != null) { + args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds()); + } + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (spec.getKeyValidityStart() != null) + ? spec.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (spec.getKeyValidityForOriginationEnd() != null) + ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (spec.getKeyValidityForConsumptionEnd() != null) + ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + + if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0) + || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) { + // Permit caller-specified IV. This is needed due to the Cipher abstraction. + 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 KeymasterUtils.getCryptoOperationException(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/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java new file mode 100644 index 0000000..ddeefbd --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import java.security.spec.KeySpec; +import java.util.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/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index 2428c2a..998e1d9 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -60,8 +60,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Set<Integer> mUserAuthenticators; private final Integer mUserAuthenticationValidityDurationSeconds; - private KeyStoreParameter(int flags, Date keyValidityStart, - Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, + private KeyStoreParameter(int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, @KeyStoreKeyConstraints.PurposeEnum Integer purposes, @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm, @KeyStoreKeyConstraints.PaddingEnum Integer padding, @@ -174,8 +176,8 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Gets the digest to which the key is restricted when generating Message Authentication Codes - * (MACs). + * Gets the digest to which the key is restricted when generating signatures or Message + * Authentication Codes (MACs). * * @return digest or {@code null} if the digest is not restricted. * @@ -404,12 +406,13 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Restricts the key to being used only with the provided digest when generating Message - * Authentication Codes (MACs). Attempts to use the key with any other digest will be - * rejected. + * Restricts the key to being used only with the provided digest when generating signatures + * or Message Authentication Codes (MACs). Attempts to use the key with any other digest + * will be rejected. * * <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm - * name. + * name. For asymmetric signing keys this constraint must be specified because there is no + * default. * * @see java.security.Key#getAlgorithm() * @@ -502,10 +505,18 @@ public final class KeyStoreParameter implements ProtectionParameter { * @return built instance of {@code KeyStoreParameter} */ public KeyStoreParameter build() { - return new KeyStoreParameter(mFlags, mKeyValidityStart, - mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, mPurposes, - mAlgorithm, mPadding, mDigest, mBlockMode, mMinSecondsBetweenOperations, - mMaxUsesPerBoot, mUserAuthenticators, + return new KeyStoreParameter(mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mAlgorithm, + mPadding, + mDigest, + mBlockMode, + mMinSecondsBetweenOperations, + mMaxUsesPerBoot, + mUserAuthenticators, mUserAuthenticationValidityDurationSeconds); } } diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java index 9410127..7f0e3d3 100644 --- a/keystore/java/android/security/KeyStoreSecretKey.java +++ b/keystore/java/android/security/KeyStoreSecretKey.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.security; import javax.crypto.SecretKey; diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java new file mode 100644 index 0000000..f552759 --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Date; +import 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); + } + + Date keyValidityStart = + KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME); + if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) { + keyValidityStart = null; + } + Date keyValidityForOriginationEnd = KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); + if ((keyValidityForOriginationEnd != null) + && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForOriginationEnd = null; + } + Date keyValidityForConsumptionEnd = KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); + if ((keyValidityForConsumptionEnd != null) + && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForConsumptionEnd = null; + } + + int swEnforcedUserAuthenticatorIds = + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + int hwEnforcedUserAuthenticatorIds = + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + int userAuthenticatorIds = swEnforcedUserAuthenticatorIds | hwEnforcedUserAuthenticatorIds; + Set<Integer> userAuthenticators = + KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(userAuthenticatorIds); + Set<Integer> teeBackedUserAuthenticators = + KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster( + hwEnforcedUserAuthenticatorIds); + + return new KeyStoreKeySpec(entryAlias, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + 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/KeymasterException.java b/keystore/java/android/security/KeymasterException.java new file mode 100644 index 0000000..484be12 --- /dev/null +++ b/keystore/java/android/security/KeymasterException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Keymaster exception. + * + * @hide + */ +public class KeymasterException extends Exception { + + private final int mErrorCode; + + public KeymasterException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } +} diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java new file mode 100644 index 0000000..c426a34 --- /dev/null +++ b/keystore/java/android/security/KeymasterUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @hide + */ +public abstract class KeymasterUtils { + private KeymasterUtils() {} + + public static KeymasterException getKeymasterException(int keymasterErrorCode) { + switch (keymasterErrorCode) { + case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: + // The name of this parameter significantly differs between Keymaster and framework + // APIs. Use the framework wording to make life easier for developers. + return new KeymasterException(keymasterErrorCode, + "Invalid user authentication validity duration"); + default: + return new KeymasterException(keymasterErrorCode, + KeymasterDefs.getErrorMessage(keymasterErrorCode)); + } + } + + public static CryptoOperationException getCryptoOperationException(KeymasterException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_KEY_EXPIRED: + return new KeyExpiredException(); + case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: + return new KeyNotYetValidException(); + case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED: + return new UserNotAuthenticatedException(); + default: + return new CryptoOperationException("Crypto operation failed", e); + } + } + + public static CryptoOperationException getCryptoOperationException(int keymasterErrorCode) { + return getCryptoOperationException(getKeymasterException(keymasterErrorCode)); + } + + public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getInt(tag, -1); + } else if (keyCharacteristics.swEnforced.containsTag(tag)) { + return keyCharacteristics.swEnforced.getInt(tag, -1); + } else { + return null; + } + } + + public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) { + List<Integer> result = new ArrayList<Integer>(); + result.addAll(keyCharacteristics.hwEnforced.getInts(tag)); + result.addAll(keyCharacteristics.swEnforced.getInts(tag)); + return result; + } + + public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) { + Date result = keyCharacteristics.hwEnforced.getDate(tag, null); + if (result == null) { + result = keyCharacteristics.swEnforced.getDate(tag, null); + } + return result; + } + + public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getBoolean(tag, false); + } else { + return keyCharacteristics.swEnforced.getBoolean(tag, false); + } + } +} diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/UserNotAuthenticatedException.java new file mode 100644 index 0000000..e6342ef --- /dev/null +++ b/keystore/java/android/security/UserNotAuthenticatedException.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Indicates that a cryptographic operation could not be performed because the user has not been + * authenticated recently enough. + * + * @hide + */ +public class UserNotAuthenticatedException extends CryptoOperationException { + + /** + * Constructs a new {@code UserNotAuthenticatedException} without detail message and cause. + */ + public UserNotAuthenticatedException() { + super("User not authenticated"); + } + + /** + * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and + * no cause. + */ + public UserNotAuthenticatedException(String message) { + super(message); + } + + /** + * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and + * cause. + */ + public UserNotAuthenticatedException(String message, Throwable cause) { + super(message, cause); + } +} |
