summaryrefslogtreecommitdiffstats
path: root/keystore/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'keystore/java/android')
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java48
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java77
-rw-r--r--keystore/java/android/security/CryptoOperationException.java61
-rw-r--r--keystore/java/android/security/KeyExpiredException.java47
-rw-r--r--keystore/java/android/security/KeyGeneratorSpec.java487
-rw-r--r--keystore/java/android/security/KeyNotYetValidException.java48
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java436
-rw-r--r--keystore/java/android/security/KeyStore.java44
-rw-r--r--keystore/java/android/security/KeyStoreCipherSpi.java573
-rw-r--r--keystore/java/android/security/KeyStoreConnectException.java28
-rw-r--r--keystore/java/android/security/KeyStoreCryptoOperation.java31
-rw-r--r--keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java309
-rw-r--r--keystore/java/android/security/KeyStoreHmacSpi.java183
-rw-r--r--keystore/java/android/security/KeyStoreKeyCharacteristics.java68
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java208
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java210
-rw-r--r--keystore/java/android/security/KeyStoreKeySpec.java226
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java35
-rw-r--r--keystore/java/android/security/KeyStoreSecretKey.java16
-rw-r--r--keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java175
-rw-r--r--keystore/java/android/security/KeymasterException.java36
-rw-r--r--keystore/java/android/security/KeymasterUtils.java94
-rw-r--r--keystore/java/android/security/UserNotAuthenticatedException.java49
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);
+ }
+}