summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--keystore/java/android/security/AndroidKeyPairGenerator.java2
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java268
-rw-r--r--keystore/java/android/security/Credentials.java22
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java429
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java395
-rw-r--r--keystore/java/android/security/KeyStoreSecretKey.java39
-rw-r--r--keystore/tests/src/android/security/AndroidKeyStoreTest.java2
7 files changed, 1136 insertions, 21 deletions
diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java
index 9d9a173..5fae831 100644
--- a/keystore/java/android/security/AndroidKeyPairGenerator.java
+++ b/keystore/java/android/security/AndroidKeyPairGenerator.java
@@ -136,6 +136,8 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
throw new IllegalStateException("could not generate key in keystore");
}
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
+
final PrivateKey privKey;
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
try {
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index acbae8f..f3eb317 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -19,6 +19,9 @@ package android.security;
import com.android.org.conscrypt.OpenSSLEngine;
import com.android.org.conscrypt.OpenSSLKeyHolder;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -31,6 +34,7 @@ import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore;
+import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
@@ -50,6 +54,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
+import javax.crypto.SecretKey;
+
/**
* A java.security.KeyStore interface for the Android KeyStore. An instance of
* it can be created via the {@link java.security.KeyStore#getInstance(String)
@@ -77,18 +83,72 @@ public class AndroidKeyStore extends KeyStoreSpi {
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
UnrecoverableKeyException {
- if (!isKeyEntry(alias)) {
- return null;
- }
+ if (isPrivateKeyEntry(alias)) {
+ final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
+ try {
+ return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
+ } catch (InvalidKeyException e) {
+ UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
+ t.initCause(e);
+ throw t;
+ }
+ } else if (isSecretKeyEntry(alias)) {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
+ int errorCode = mKeyStore.getKeyCharacteristics(
+ keyAliasInKeystore, null, null, keyCharacteristics);
+ if ((errorCode != KeymasterDefs.KM_ERROR_OK)
+ && (errorCode != android.security.KeyStore.NO_ERROR)) {
+ throw new UnrecoverableKeyException("Failed to load information about key."
+ + " Error code: " + errorCode);
+ }
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
- try {
- return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
- } catch (InvalidKeyException e) {
- UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
- t.initCause(e);
- throw t;
+ int keymasterAlgorithm =
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
+ if (keymasterAlgorithm == -1) {
+ keymasterAlgorithm =
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
+ }
+ if (keymasterAlgorithm == -1) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+ @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
+ try {
+ keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(keymasterAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Unsupported key algorithm").initCause(e);
+ }
+
+ int keymasterDigest =
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
+ if (keymasterDigest == -1) {
+ keymasterDigest =
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
+ }
+ @KeyStoreKeyConstraints.DigestEnum Integer digest = null;
+ if (keymasterDigest != -1) {
+ try {
+ digest = KeyStoreKeyConstraints.Digest.fromKeymaster(keymasterDigest);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Unsupported digest").initCause(e);
+ }
+ }
+
+ String keyAlgorithmString;
+ try {
+ keyAlgorithmString = KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(
+ keyAlgorithm, digest);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
+ }
+
+ return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
}
+
+ return null;
}
@Override
@@ -186,6 +246,11 @@ public class AndroidKeyStore extends KeyStoreSpi {
return d;
}
+ d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
+ if (d != null) {
+ return d;
+ }
+
d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
if (d != null) {
return d;
@@ -203,8 +268,10 @@ public class AndroidKeyStore extends KeyStoreSpi {
if (key instanceof PrivateKey) {
setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
+ } else if (key instanceof SecretKey) {
+ setSecretKeyEntry(alias, (SecretKey) key, null);
} else {
- throw new KeyStoreException("Only PrivateKeys are supported");
+ throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
}
}
@@ -319,6 +386,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
} else {
Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
}
final int flags = (params == null) ? 0 : params.getFlags();
@@ -340,6 +408,160 @@ public class AndroidKeyStore extends KeyStoreSpi {
}
}
+ private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params)
+ throws KeyStoreException {
+ if (key instanceof KeyStoreSecretKey) {
+ // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
+ // overwrite its own entry.
+ String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
+ if (keyAliasInKeystore == null) {
+ throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
+ }
+ if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
+ throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
+ + keyAliasInKeystore);
+ }
+ String keyEntryAlias =
+ keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
+ if (!entryAlias.equals(keyEntryAlias)) {
+ throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
+ + " alias: " + entryAlias + " != " + keyEntryAlias);
+ }
+ // This is the entry where this key is already stored. No need to do anything.
+ if (params != null) {
+ throw new KeyStoreException("Modifying KeyStore-backed key using protection"
+ + " parameters not supported");
+ }
+ return;
+ }
+
+ if (params == null) {
+ throw new KeyStoreException(
+ "Protection parameters must be specified when importing a symmetric key");
+ }
+
+ // Not a KeyStore-backed secret key -- import its key material into keystore.
+ String keyExportFormat = key.getFormat();
+ if (keyExportFormat == null) {
+ throw new KeyStoreException(
+ "Only secret keys that export their key material are supported");
+ } else if (!"RAW".equals(keyExportFormat)) {
+ throw new KeyStoreException(
+ "Unsupported secret key material export format: " + keyExportFormat);
+ }
+ byte[] keyMaterial = key.getEncoded();
+ if (keyMaterial == null) {
+ throw new KeyStoreException("Key did not export its key material despite supporting"
+ + " RAW format export");
+ }
+
+ String keyAlgorithmString = key.getAlgorithm();
+ @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
+ @KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
+ try {
+ keyAlgorithm =
+ KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
+ digest = KeyStoreKeyConstraints.Digest.fromJCASecretKeyAlgorithm(keyAlgorithmString);
+ } catch (IllegalArgumentException e) {
+ throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
+ }
+
+ if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
+ throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
+ + ", parameter spec: "
+ + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
+ }
+
+ KeymasterArguments args = new KeymasterArguments();
+ args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
+ KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));
+
+ if (digest != null) {
+ // Digest available from JCA key algorithm
+ if (params.getDigest() != null) {
+ // Digest also specified in parameters -- check that these two match
+ if (digest != params.getDigest()) {
+ throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
+ + ", parameter spec: "
+ + KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
+ }
+ }
+ } else {
+ // Digest not available from JCA key algorithm
+ digest = params.getDigest();
+ }
+ if (digest != null) {
+ args.addInt(KeymasterDefs.KM_TAG_DIGEST,
+ KeyStoreKeyConstraints.Digest.toKeymaster(digest));
+ }
+
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
+ ? params.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 (params.getBlockMode() != null) {
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
+ KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
+ }
+ if (params.getPadding() != null) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING,
+ KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
+ }
+ if (params.getMaxUsesPerBoot() != null) {
+ args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
+ }
+ if (params.getMinSecondsBetweenOperations() != null) {
+ args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
+ params.getMinSecondsBetweenOperations());
+ }
+ 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);
+// }
+ }
+ 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());
+ }
+
+ // 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);
+
+ Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
+ String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
+ int errorCode = mKeyStore.importKey(
+ keyAliasInKeystore,
+ args,
+ KeymasterDefs.KM_KEY_FORMAT_RAW,
+ keyMaterial,
+ params.getFlags(),
+ new KeyCharacteristics());
+ if (errorCode != android.security.KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to import secret key. Keystore error code: "
+ + errorCode);
+ }
+ }
+
@Override
public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
throws KeyStoreException {
@@ -413,6 +635,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
}
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
+ || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias)
|| mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
|| mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
}
@@ -428,6 +651,10 @@ public class AndroidKeyStore extends KeyStoreSpi {
}
private boolean isKeyEntry(String alias) {
+ return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
+ }
+
+ private boolean isPrivateKeyEntry(String alias) {
if (alias == null) {
throw new NullPointerException("alias == null");
}
@@ -435,6 +662,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
}
+ private boolean isSecretKeyEntry(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias);
+ }
+
private boolean isCertificateEntry(String alias) {
if (alias == null) {
throw new NullPointerException("alias == null");
@@ -554,11 +789,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
PrivateKeyEntry prE = (PrivateKeyEntry) entry;
setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
(KeyStoreParameter) param);
- return;
+ } else if (entry instanceof SecretKeyEntry) {
+ SecretKeyEntry secE = (SecretKeyEntry) entry;
+ setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param);
+ } else {
+ throw new KeyStoreException(
+ "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
+ + "; was " + entry);
}
-
- throw new KeyStoreException(
- "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
}
}
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index af76d9d..6283e02 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -61,6 +61,9 @@ public class Credentials {
/** Key prefix for user private keys. */
public static final String USER_PRIVATE_KEY = "USRPKEY_";
+ /** Key prefix for user secret keys. */
+ public static final String USER_SECRET_KEY = "USRSKEY_";
+
/** Key prefix for VPN. */
public static final String VPN = "VPN_";
@@ -218,7 +221,8 @@ public class Credentials {
* Make sure every type is deleted. There can be all three types, so
* don't use a conditional here.
*/
- return keystore.delKey(Credentials.USER_PRIVATE_KEY + alias)
+ return keystore.delete(Credentials.USER_PRIVATE_KEY + alias)
+ | keystore.delete(Credentials.USER_SECRET_KEY + alias)
| deleteCertificateTypesForAlias(keystore, alias);
}
@@ -235,4 +239,20 @@ public class Credentials {
return keystore.delete(Credentials.USER_CERTIFICATE + alias)
| keystore.delete(Credentials.CA_CERTIFICATE + alias);
}
+
+ /**
+ * Delete private key for a particular {@code alias}.
+ * Returns {@code true} if an entry was was deleted.
+ */
+ static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) {
+ return keystore.delete(Credentials.USER_PRIVATE_KEY + alias);
+ }
+
+ /**
+ * Delete secret key for a particular {@code alias}.
+ * Returns {@code true} if an entry was was deleted.
+ */
+ static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
+ return keystore.delete(Credentials.USER_SECRET_KEY + alias);
+ }
}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
new file mode 100644
index 0000000..01e6dcd
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -0,0 +1,429 @@
+package android.security;
+
+import android.annotation.IntDef;
+import android.security.keymaster.KeymasterDefs;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+
+/**
+ * Constraints for {@code AndroidKeyStore} keys.
+ *
+ * @hide
+ */
+public abstract class KeyStoreKeyConstraints {
+ private KeyStoreKeyConstraints() {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
+ public @interface PurposeEnum {}
+
+ /**
+ * Purpose of key.
+ */
+ public static abstract class Purpose {
+ private Purpose() {}
+
+ /**
+ * Purpose: encryption.
+ */
+ public static final int ENCRYPT = 1 << 0;
+
+ /**
+ * Purpose: decryption.
+ */
+ public static final int DECRYPT = 1 << 1;
+
+ /**
+ * Purpose: signing.
+ */
+ public static final int SIGN = 1 << 2;
+
+ /**
+ * Purpose: signature verification.
+ */
+ public static final int VERIFY = 1 << 3;
+
+ /**
+ * Number of flags defined above. Needs to be kept in sync with the flags above.
+ */
+ private static final int VALUE_COUNT = 4;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@PurposeEnum int purpose) {
+ switch (purpose) {
+ case ENCRYPT:
+ return KeymasterDefs.KM_PURPOSE_ENCRYPT;
+ case DECRYPT:
+ return KeymasterDefs.KM_PURPOSE_DECRYPT;
+ case SIGN:
+ return KeymasterDefs.KM_PURPOSE_SIGN;
+ case VERIFY:
+ return KeymasterDefs.KM_PURPOSE_VERIFY;
+ default:
+ throw new IllegalArgumentException("Unknown purpose: " + purpose);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @PurposeEnum int fromKeymaster(int purpose) {
+ switch (purpose) {
+ case KeymasterDefs.KM_PURPOSE_ENCRYPT:
+ return ENCRYPT;
+ case KeymasterDefs.KM_PURPOSE_DECRYPT:
+ return DECRYPT;
+ case KeymasterDefs.KM_PURPOSE_SIGN:
+ return SIGN;
+ case KeymasterDefs.KM_PURPOSE_VERIFY:
+ return VERIFY;
+ default:
+ throw new IllegalArgumentException("Unknown purpose: " + purpose);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int[] allToKeymaster(int purposes) {
+ int[] result = new int[VALUE_COUNT];
+ int resultCount = 0;
+ int purpose = 1;
+ for (int i = 0; i < 32; i++) {
+ if ((purposes & 1) != 0) {
+ result[resultCount] = toKeymaster(purpose);
+ resultCount++;
+ }
+ purposes >>>= 1;
+ purpose <<= 1;
+ if (purposes == 0) {
+ break;
+ }
+ }
+ return Arrays.copyOf(result, resultCount);
+ }
+
+ /**
+ * @hide
+ */
+ public static @PurposeEnum int allFromKeymaster(Collection<Integer> purposes) {
+ @PurposeEnum int result = 0;
+ for (int keymasterPurpose : purposes) {
+ result |= fromKeymaster(keymasterPurpose);
+ }
+ return result;
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Algorithm.AES, Algorithm.HMAC})
+ public @interface AlgorithmEnum {}
+
+ /**
+ * Key algorithm.
+ */
+ public static abstract class Algorithm {
+ private Algorithm() {}
+
+ /**
+ * Key algorithm: AES.
+ */
+ public static final int AES = 0;
+
+ /**
+ * Key algorithm: HMAC.
+ */
+ public static final int HMAC = 1;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@AlgorithmEnum int algorithm) {
+ switch (algorithm) {
+ case AES:
+ return KeymasterDefs.KM_ALGORITHM_AES;
+ case HMAC:
+ return KeymasterDefs.KM_ALGORITHM_HMAC;
+ default:
+ throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @AlgorithmEnum int fromKeymaster(int algorithm) {
+ switch (algorithm) {
+ case KeymasterDefs.KM_ALGORITHM_AES:
+ return AES;
+ case KeymasterDefs.KM_ALGORITHM_HMAC:
+ return HMAC;
+ default:
+ throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String toString(@AlgorithmEnum int algorithm) {
+ switch (algorithm) {
+ case AES:
+ return "AES";
+ case HMAC:
+ return "HMAC";
+ default:
+ throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @AlgorithmEnum int fromJCASecretKeyAlgorithm(String algorithm) {
+ if (algorithm == null) {
+ throw new NullPointerException("algorithm == null");
+ } else if ("AES".equalsIgnoreCase(algorithm)) {
+ return AES;
+ } else if (algorithm.toLowerCase(Locale.US).startsWith("hmac")) {
+ return HMAC;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported secret key algorithm: " + algorithm);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String toJCASecretKeyAlgorithm(@AlgorithmEnum int algorithm,
+ @DigestEnum Integer digest) {
+ switch (algorithm) {
+ case AES:
+ return "AES";
+ case HMAC:
+ if (digest == null) {
+ throw new IllegalArgumentException("HMAC digest not specified");
+ }
+ switch (digest) {
+ case Digest.SHA256:
+ return "HmacSHA256";
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported HMAC digest: " + digest);
+ }
+ default:
+ 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)
+ @IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7})
+ public @interface PaddingEnum {}
+
+ /**
+ * Padding for signing and encryption.
+ */
+ public static abstract class Padding {
+ private Padding() {}
+
+ /**
+ * No padding.
+ */
+ public static final int NONE = 0;
+
+ /**
+ * Pad with zeros.
+ */
+ public static final int ZERO = 1;
+
+ /**
+ * PKCS#7 padding.
+ */
+ public static final int PKCS7 = 2;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(int padding) {
+ switch (padding) {
+ case NONE:
+ return KeymasterDefs.KM_PAD_NONE;
+ case ZERO:
+ return KeymasterDefs.KM_PAD_ZERO;
+ case PKCS7:
+ return KeymasterDefs.KM_PAD_PKCS7;
+ default:
+ throw new IllegalArgumentException("Unknown padding: " + padding);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @PaddingEnum int fromKeymaster(int padding) {
+ switch (padding) {
+ case KeymasterDefs.KM_PAD_NONE:
+ return NONE;
+ case KeymasterDefs.KM_PAD_ZERO:
+ return ZERO;
+ case KeymasterDefs.KM_PAD_PKCS7:
+ return PKCS7;
+ default:
+ throw new IllegalArgumentException("Unknown padding: " + padding);
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Digest.NONE, Digest.SHA256})
+ public @interface DigestEnum {}
+
+ /**
+ * Digests that can be used with a key when signing or generating Message Authentication
+ * Codes (MACs).
+ */
+ public static abstract class Digest {
+ private Digest() {}
+
+ /**
+ * No digest: sign/authenticate the raw message.
+ */
+ public static final int NONE = 0;
+
+ /**
+ * SHA-256 digest.
+ */
+ public static final int SHA256 = 1;
+
+ /**
+ * @hide
+ */
+ public static String toString(@DigestEnum int digest) {
+ switch (digest) {
+ case NONE:
+ return "NONE";
+ case SHA256:
+ return "SHA256";
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + digest);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@DigestEnum int digest) {
+ switch (digest) {
+ case NONE:
+ return KeymasterDefs.KM_DIGEST_NONE;
+ case SHA256:
+ return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + digest);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @DigestEnum int fromKeymaster(int digest) {
+ switch (digest) {
+ case KeymasterDefs.KM_DIGEST_NONE:
+ return NONE;
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return SHA256;
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + digest);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
+ String algorithmLower = algorithm.toLowerCase(Locale.US);
+ if (algorithmLower.startsWith("hmac")) {
+ if ("hmacsha256".equals(algorithmLower)) {
+ return SHA256;
+ } else {
+ throw new IllegalArgumentException("Unsupported digest: "
+ + algorithmLower.substring("hmac".length()));
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String toJCASignatureAlgorithmDigest(@DigestEnum int digest) {
+ switch (digest) {
+ case NONE:
+ return "NONE";
+ case SHA256:
+ return "SHA256";
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + digest);
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({BlockMode.ECB})
+ public @interface BlockModeEnum {}
+
+ /**
+ * Block modes that can be used when encrypting/decrypting using a key.
+ */
+ public static abstract class BlockMode {
+ private BlockMode() {}
+
+ /**
+ * Electronic Codebook (ECB) block mode.
+ */
+ public static final int ECB = 0;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@BlockModeEnum int mode) {
+ switch (mode) {
+ case ECB:
+ return KeymasterDefs.KM_MODE_ECB;
+ default:
+ throw new IllegalArgumentException("Unknown block mode: " + mode);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @BlockModeEnum int fromKeymaster(int mode) {
+ switch (mode) {
+ case KeymasterDefs.KM_MODE_ECB:
+ return ECB;
+ default:
+ throw new IllegalArgumentException("Unknown block mode: " + mode);
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 2eeb6ad..2428c2a 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -20,6 +20,10 @@ import android.content.Context;
import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
/**
* This provides the optional parameters that can be specified for
@@ -43,9 +47,51 @@ import java.security.KeyStore.ProtectionParameter;
*/
public final class KeyStoreParameter implements ProtectionParameter {
private int mFlags;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
+ private final @KeyStoreKeyConstraints.AlgorithmEnum Integer 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 Integer mUserAuthenticationValidityDurationSeconds;
+
+ private KeyStoreParameter(int flags, Date keyValidityStart,
+ Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
+ @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm,
+ @KeyStoreKeyConstraints.PaddingEnum Integer padding,
+ @KeyStoreKeyConstraints.DigestEnum Integer digest,
+ @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
+ Integer minSecondsBetweenOperations,
+ Integer maxUsesPerBoot,
+ Set<Integer> userAuthenticators,
+ Integer userAuthenticationValidityDurationSeconds) {
+ if ((userAuthenticationValidityDurationSeconds != null)
+ && (userAuthenticationValidityDurationSeconds < 0)) {
+ throw new IllegalArgumentException(
+ "userAuthenticationValidityDurationSeconds must not be negative");
+ }
- private KeyStoreParameter(int flags) {
mFlags = flags;
+ 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();
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
}
/**
@@ -64,6 +110,141 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Gets the time instant before which the key 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 is no long valid for decryption and verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key is no long valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used to the provided set of purposes.
+ *
+ * @return set of purposes or {@code null} if the key can be used for any purpose.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the algorithm to which the key is restricted.
+ *
+ * @return algorithm or {@code null} if it's not restricted.
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ /**
+ * 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 digest to which the key is restricted when generating Message Authentication Codes
+ * (MACs).
+ *
+ * @return digest or {@code null} if the digest is not restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
+ return mDigest;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
* Builder class for {@link KeyStoreParameter} objects.
* <p>
* This will build protection parameters for use with the
@@ -82,6 +263,18 @@ public final class KeyStoreParameter implements ProtectionParameter {
*/
public final static class Builder {
private int mFlags;
+ private Date mKeyValidityStart;
+ private Date mKeyValidityForOriginationEnd;
+ private Date mKeyValidityForConsumptionEnd;
+ private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
+ private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
+ private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
+ private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
+ 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
@@ -113,13 +306,207 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Builds the instance of the {@code KeyPairGeneratorSpec}.
+ * 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;
+ }
+
+ /**
+ * Sets the algorithm of the key.
+ *
+ * <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly
+ * specifying the algorithm of symmetric keys using this method is not necessary.
+ *
+ * @hide
+ */
+ public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) {
+ mAlgorithm = algorithm;
+ 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 digest when generating 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.
+ *
+ * @see java.security.Key#getAlgorithm()
+ *
+ * @hide
+ */
+ public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
+ mDigest = digest;
+ 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 the instance of the {@code KeyStoreParameter}.
*
* @throws IllegalArgumentException if a required field is missing
- * @return built instance of {@code KeyPairGeneratorSpec}
+ * @return built instance of {@code KeyStoreParameter}
*/
public KeyStoreParameter build() {
- return new KeyStoreParameter(mFlags);
+ 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
new file mode 100644
index 0000000..9410127
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreSecretKey.java
@@ -0,0 +1,39 @@
+package android.security;
+
+import javax.crypto.SecretKey;
+
+/**
+ * {@link SecretKey} backed by keystore.
+ *
+ * @hide
+ */
+public class KeyStoreSecretKey implements SecretKey {
+ private final String mAlias;
+ private final String mAlgorithm;
+
+ public KeyStoreSecretKey(String alias, String algorithm) {
+ mAlias = alias;
+ mAlgorithm = algorithm;
+ }
+
+ String getAlias() {
+ return mAlias;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ @Override
+ public String getFormat() {
+ // This key does not export its key material
+ return null;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ // This key does not export its key material
+ return null;
+ }
+}
diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
index 9775e64..7a88dee 100644
--- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
@@ -2127,7 +2127,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
assertEquals("The keystore size should match expected", 2, mKeyStore.size());
assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
- assertTrue(mAndroidKeyStore.delKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
+ assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
assertEquals("The keystore size should match expected", 1, mKeyStore.size());
assertAliases(new String[] { TEST_ALIAS_2 });