diff options
author | Alex Klyubin <klyubin@google.com> | 2015-03-27 19:21:12 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-03-27 19:21:13 +0000 |
commit | 274a4ee3446e76a34a9cfe987e98f7bf4e53f78d (patch) | |
tree | 72e5c4457d88fd48f17864be47ac0792bc9d58cc /keystore | |
parent | ee80414d0ddd6a27bbf86e0de47dd86bc335431d (diff) | |
parent | baf2838fd2c7ddf517bd5bd9917551a4706af5b6 (diff) | |
download | frameworks_base-274a4ee3446e76a34a9cfe987e98f7bf4e53f78d.zip frameworks_base-274a4ee3446e76a34a9cfe987e98f7bf4e53f78d.tar.gz frameworks_base-274a4ee3446e76a34a9cfe987e98f7bf4e53f78d.tar.bz2 |
Merge "Symmetric key import for AndroidKeyStore."
Diffstat (limited to 'keystore')
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 }); |