summaryrefslogtreecommitdiffstats
path: root/keystore
diff options
context:
space:
mode:
Diffstat (limited to 'keystore')
-rw-r--r--keystore/java/android/security/AndroidKeyPairGenerator.java313
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java814
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java133
-rw-r--r--keystore/java/android/security/Credentials.java6
-rw-r--r--keystore/java/android/security/EcIesParameterSpec.java264
-rw-r--r--keystore/java/android/security/GateKeeper.java50
-rw-r--r--keystore/java/android/security/KeyChain.java145
-rw-r--r--keystore/java/android/security/KeyChainAliasCallback.java4
-rw-r--r--keystore/java/android/security/KeyGeneratorSpec.java459
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java568
-rw-r--r--keystore/java/android/security/KeyStore.java326
-rw-r--r--keystore/java/android/security/KeyStoreCipherSpi.java665
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java238
-rw-r--r--keystore/java/android/security/KeyStoreKeyProperties.java284
-rw-r--r--keystore/java/android/security/KeyStoreKeySpec.java203
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java472
-rw-r--r--keystore/java/android/security/KeyStoreSecretKey.java55
-rw-r--r--keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java191
-rw-r--r--keystore/java/android/security/KeymasterUtils.java342
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java443
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java259
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java856
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java202
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java40
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java57
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java (renamed from keystore/java/android/security/KeyStoreHmacSpi.java)91
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKey.java93
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java145
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java334
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java816
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java31
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreProvider.java306
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java71
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java604
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java41
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java55
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java164
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java31
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java199
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java431
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSpi.java973
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java313
-rw-r--r--keystore/java/android/security/keystore/ArrayUtils.java (renamed from keystore/java/android/security/ArrayUtils.java)24
-rw-r--r--keystore/java/android/security/keystore/DelegatingX509Certificate.java212
-rw-r--r--keystore/java/android/security/keystore/KeyExpiredException.java (renamed from keystore/java/android/security/KeyExpiredException.java)4
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java867
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java282
-rw-r--r--keystore/java/android/security/keystore/KeyNotYetValidException.java (renamed from keystore/java/android/security/KeyNotYetValidException.java)4
-rw-r--r--keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java56
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java730
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java602
-rw-r--r--keystore/java/android/security/keystore/KeyStoreConnectException.java (renamed from keystore/java/android/security/KeyStoreConnectException.java)6
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperation.java (renamed from keystore/java/android/security/KeyStoreCryptoOperation.java)8
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java (renamed from keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java)71
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java42
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java118
-rw-r--r--keystore/java/android/security/keystore/KeymasterUtils.java136
-rw-r--r--keystore/java/android/security/keystore/UserNotAuthenticatedException.java (renamed from keystore/java/android/security/UserNotAuthenticatedException.java)6
-rw-r--r--keystore/java/android/security/keystore/Utils.java32
-rw-r--r--keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java56
-rw-r--r--keystore/tests/src/android/security/KeyStoreTest.java269
-rw-r--r--keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java (renamed from keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java)74
-rw-r--r--keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java (renamed from keystore/tests/src/android/security/AndroidKeyStoreTest.java)77
63 files changed, 10472 insertions, 5291 deletions
diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java
deleted file mode 100644
index 3b25ba6..0000000
--- a/keystore/java/android/security/AndroidKeyPairGenerator.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-import com.android.org.conscrypt.NativeConstants;
-import com.android.org.conscrypt.OpenSSLEngine;
-
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyPairGeneratorSpi;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAKeyGenParameterSpec;
-import java.security.spec.X509EncodedKeySpec;
-
-/**
- * Provides a way to create instances of a KeyPair which will be placed in the
- * Android keystore service usable only by the application that called it. This
- * can be used in conjunction with
- * {@link java.security.KeyStore#getInstance(String)} using the
- * {@code "AndroidKeyStore"} type.
- * <p>
- * This class can not be directly instantiated and must instead be used via the
- * {@link KeyPairGenerator#getInstance(String)
- * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API.
- *
- * {@hide}
- */
-public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
-
- public static class RSA extends AndroidKeyPairGenerator {
- public RSA() {
- super("RSA");
- }
- }
-
- public static class EC extends AndroidKeyPairGenerator {
- public EC() {
- super("EC");
- }
- }
-
- /*
- * These must be kept in sync with system/security/keystore/defaults.h
- */
-
- /* EC */
- private static final int EC_DEFAULT_KEY_SIZE = 256;
- private static final int EC_MIN_KEY_SIZE = 192;
- private static final int EC_MAX_KEY_SIZE = 521;
-
- /* RSA */
- private static final int RSA_DEFAULT_KEY_SIZE = 2048;
- private static final int RSA_MIN_KEY_SIZE = 512;
- private static final int RSA_MAX_KEY_SIZE = 8192;
-
- private final String mAlgorithm;
-
- private android.security.KeyStore mKeyStore;
-
- private KeyPairGeneratorSpec mSpec;
- private String mKeyAlgorithm;
- private int mKeyType;
- private int mKeySize;
-
- protected AndroidKeyPairGenerator(String algorithm) {
- mAlgorithm = algorithm;
- }
-
- public String getAlgorithm() {
- return mAlgorithm;
- }
-
- /**
- * Generate a KeyPair which is backed by the Android keystore service. You
- * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
- * with an {@link KeyPairGeneratorSpec} as the {@code params}
- * argument before calling this otherwise an {@code IllegalStateException}
- * will be thrown.
- * <p>
- * This will create an entry in the Android keystore service with a
- * self-signed certificate using the {@code params} specified in the
- * {@code initialize(params)} call.
- *
- * @throws IllegalStateException when called before calling
- * {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
- * @see java.security.KeyPairGeneratorSpi#generateKeyPair()
- */
- @Override
- public KeyPair generateKeyPair() {
- if (mKeyStore == null || mSpec == null) {
- throw new IllegalStateException(
- "Must call initialize with an android.security.KeyPairGeneratorSpec first");
- }
-
- if (((mSpec.getFlags() & KeyStore.FLAG_ENCRYPTED) != 0)
- && (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
- throw new IllegalStateException(
- "Android keystore must be in initialized and unlocked state "
- + "if encryption is required");
- }
-
- final String alias = mSpec.getKeystoreAlias();
-
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
-
- byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec());
-
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
- if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize,
- mSpec.getFlags(), args)) {
- throw new IllegalStateException("could not generate key in keystore");
- }
-
- Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
-
- final PrivateKey privKey;
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
- try {
- privKey = engine.getPrivateKeyById(privateKeyAlias);
- } catch (InvalidKeyException e) {
- throw new RuntimeException("Can't get key", e);
- }
-
- final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias);
-
- final PublicKey pubKey;
- try {
- final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm);
- pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Can't instantiate key generator", e);
- } catch (InvalidKeySpecException e) {
- throw new IllegalStateException("keystore returned invalid key encoding", e);
- }
-
- final X509Certificate cert;
- try {
- cert = generateCertificate(privKey, pubKey);
- } catch (Exception e) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new IllegalStateException("Can't generate certificate", e);
- }
-
- byte[] certBytes;
- try {
- certBytes = cert.getEncoded();
- } catch (CertificateEncodingException e) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new IllegalStateException("Can't get encoding of certificate", e);
- }
-
- if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF,
- mSpec.getFlags())) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new IllegalStateException("Can't store certificate in AndroidKeyStore");
- }
-
- return new KeyPair(pubKey, privKey);
- }
-
- @SuppressWarnings("deprecation")
- private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey)
- throws Exception {
- final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
- certGen.setPublicKey(publicKey);
- certGen.setSerialNumber(mSpec.getSerialNumber());
- certGen.setSubjectDN(mSpec.getSubjectDN());
- certGen.setIssuerDN(mSpec.getSubjectDN());
- certGen.setNotBefore(mSpec.getStartDate());
- certGen.setNotAfter(mSpec.getEndDate());
- certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm));
- return certGen.generate(privateKey);
- }
-
- private String getKeyAlgorithm(KeyPairGeneratorSpec spec) {
- String result = spec.getKeyType();
- if (result != null) {
- return result;
- }
- return getAlgorithm();
- }
-
- private static int getDefaultKeySize(int keyType) {
- if (keyType == NativeConstants.EVP_PKEY_EC) {
- return EC_DEFAULT_KEY_SIZE;
- } else if (keyType == NativeConstants.EVP_PKEY_RSA) {
- return RSA_DEFAULT_KEY_SIZE;
- }
- return -1;
- }
-
- private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize)
- throws InvalidAlgorithmParameterException {
- if (keyType == NativeConstants.EVP_PKEY_EC) {
- if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) {
- throw new InvalidAlgorithmParameterException("EC keys must be >= "
- + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE);
- }
- } else if (keyType == NativeConstants.EVP_PKEY_RSA) {
- if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
- throw new InvalidAlgorithmParameterException("RSA keys must be >= "
- + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
- }
- } else {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
- }
- }
-
- private static void checkCorrectParametersSpec(int keyType, int keySize,
- AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
- if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) {
- if (spec instanceof RSAKeyGenParameterSpec) {
- RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
- if (keySize != -1 && keySize != rsaSpec.getKeysize()) {
- throw new InvalidAlgorithmParameterException("RSA key size must match: "
- + keySize + " vs " + rsaSpec.getKeysize());
- }
- } else {
- throw new InvalidAlgorithmParameterException(
- "RSA may only use RSAKeyGenParameterSpec");
- }
- }
- }
-
- private static String getDefaultSignatureAlgorithmForKeyAlgorithm(String algorithm) {
- if ("RSA".equalsIgnoreCase(algorithm)) {
- return "sha256WithRSA";
- } else if ("EC".equalsIgnoreCase(algorithm)) {
- return "sha256WithECDSA";
- } else {
- throw new IllegalArgumentException("Unsupported key type " + algorithm);
- }
- }
-
- private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) {
- switch (keyType) {
- case NativeConstants.EVP_PKEY_RSA:
- if (spec instanceof RSAKeyGenParameterSpec) {
- RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
- return new byte[][] { rsaSpec.getPublicExponent().toByteArray() };
- }
- break;
- }
- return null;
- }
-
- @Override
- public void initialize(int keysize, SecureRandom random) {
- throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
- }
-
- @Override
- public void initialize(AlgorithmParameterSpec params, SecureRandom random)
- throws InvalidAlgorithmParameterException {
- if (params == null) {
- throw new InvalidAlgorithmParameterException(
- "must supply params of type android.security.KeyPairGeneratorSpec");
- } else if (!(params instanceof KeyPairGeneratorSpec)) {
- throw new InvalidAlgorithmParameterException(
- "params must be of type android.security.KeyPairGeneratorSpec");
- }
-
- KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params;
- String keyAlgorithm = getKeyAlgorithm(spec);
- int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm);
- if (keyType == -1) {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
- }
- int keySize = spec.getKeySize();
- if (keySize == -1) {
- keySize = getDefaultKeySize(keyType);
- if (keySize == -1) {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
- }
- }
- checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec());
- checkValidKeySize(keyAlgorithm, keyType, keySize);
-
- mKeyAlgorithm = keyAlgorithm;
- mKeyType = keyType;
- mKeySize = keySize;
- mSpec = spec;
- mKeyStore = android.security.KeyStore.getInstance();
- }
-}
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
deleted file mode 100644
index 964fb21..0000000
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ /dev/null
@@ -1,814 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.org.conscrypt.OpenSSLEngine;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
-import libcore.util.EmptyArray;
-
-import android.security.keymaster.KeyCharacteristics;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterDefs;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.Key;
-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;
-import java.security.PrivateKey;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-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)
- * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
- * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
- * <p>
- * This is built on top of Android's keystore daemon. The convention of alias
- * use is:
- * <p>
- * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
- * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
- * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
- * entry which will have the rest of the chain concatenated in BER format.
- * <p>
- * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
- * with a single certificate.
- *
- * @hide
- */
-public class AndroidKeyStore extends KeyStoreSpi {
- public static final String NAME = "AndroidKeyStore";
-
- private android.security.KeyStore mKeyStore;
-
- @Override
- public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
- UnrecoverableKeyException {
- 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);
- }
-
- 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");
- }
-
- List<Integer> keymasterDigests =
- keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST);
- int keymasterDigest;
- if (keymasterDigests.isEmpty()) {
- keymasterDigest = -1;
- } else {
- // More than one digest can be permitted for this key. Use the first one to form the
- // JCA key algorithm name.
- keymasterDigest = keymasterDigests.get(0);
- }
-
- String keyAlgorithmString;
- try {
- keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm(
- keymasterAlgorithm, keymasterDigest);
- } catch (IllegalArgumentException e) {
- throw (UnrecoverableKeyException)
- new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
- }
-
- return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
- }
-
- return null;
- }
-
- @Override
- public Certificate[] engineGetCertificateChain(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
- if (leaf == null) {
- return null;
- }
-
- final Certificate[] caList;
-
- final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
- if (caBytes != null) {
- final Collection<X509Certificate> caChain = toCertificates(caBytes);
-
- caList = new Certificate[caChain.size() + 1];
-
- final Iterator<X509Certificate> it = caChain.iterator();
- int i = 1;
- while (it.hasNext()) {
- caList[i++] = it.next();
- }
- } else {
- caList = new Certificate[1];
- }
-
- caList[0] = leaf;
-
- return caList;
- }
-
- @Override
- public Certificate engineGetCertificate(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
- if (certificate != null) {
- return toCertificate(certificate);
- }
-
- certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
- if (certificate != null) {
- return toCertificate(certificate);
- }
-
- return null;
- }
-
- private static X509Certificate toCertificate(byte[] bytes) {
- try {
- final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) certFactory
- .generateCertificate(new ByteArrayInputStream(bytes));
- } catch (CertificateException e) {
- Log.w(NAME, "Couldn't parse certificate in keystore", e);
- return null;
- }
- }
-
- @SuppressWarnings("unchecked")
- private static Collection<X509Certificate> toCertificates(byte[] bytes) {
- try {
- final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- return (Collection<X509Certificate>) certFactory
- .generateCertificates(new ByteArrayInputStream(bytes));
- } catch (CertificateException e) {
- Log.w(NAME, "Couldn't parse certificates in keystore", e);
- return new ArrayList<X509Certificate>();
- }
- }
-
- private Date getModificationDate(String alias) {
- final long epochMillis = mKeyStore.getmtime(alias);
- if (epochMillis == -1L) {
- return null;
- }
-
- return new Date(epochMillis);
- }
-
- @Override
- public Date engineGetCreationDate(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
- if (d != null) {
- 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;
- }
-
- return getModificationDate(Credentials.CA_CERTIFICATE + alias);
- }
-
- @Override
- public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
- throws KeyStoreException {
- if ((password != null) && (password.length > 0)) {
- throw new KeyStoreException("entries cannot be protected with passwords");
- }
-
- 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 PrivateKey and SecretKey are supported");
- }
- }
-
- private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
- KeyStoreParameter params) throws KeyStoreException {
- byte[] keyBytes = null;
-
- final String pkeyAlias;
- if (key instanceof OpenSSLKeyHolder) {
- pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
- } else {
- pkeyAlias = null;
- }
-
- final boolean shouldReplacePrivateKey;
- if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
- final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
- if (!alias.equals(keySubalias)) {
- throw new KeyStoreException("Can only replace keys with same alias: " + alias
- + " != " + keySubalias);
- }
-
- shouldReplacePrivateKey = false;
- } else {
- // Make sure the PrivateKey format is the one we support.
- final String keyFormat = key.getFormat();
- if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
- throw new KeyStoreException(
- "Only PrivateKeys that can be encoded into PKCS#8 are supported");
- }
-
- // Make sure we can actually encode the key.
- keyBytes = key.getEncoded();
- if (keyBytes == null) {
- throw new KeyStoreException("PrivateKey has no encoding");
- }
-
- shouldReplacePrivateKey = true;
- }
-
- // Make sure the chain exists since this is a PrivateKey
- if ((chain == null) || (chain.length == 0)) {
- throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
- }
-
- // Do chain type checking.
- X509Certificate[] x509chain = new X509Certificate[chain.length];
- for (int i = 0; i < chain.length; i++) {
- if (!"X.509".equals(chain[i].getType())) {
- throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
- + i);
- }
-
- if (!(chain[i] instanceof X509Certificate)) {
- throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
- + i);
- }
-
- x509chain[i] = (X509Certificate) chain[i];
- }
-
- final byte[] userCertBytes;
- try {
- userCertBytes = x509chain[0].getEncoded();
- } catch (CertificateEncodingException e) {
- throw new KeyStoreException("Couldn't encode certificate #1", e);
- }
-
- /*
- * If we have a chain, store it in the CA certificate slot for this
- * alias as concatenated DER-encoded certificates. These can be
- * deserialized by {@link CertificateFactory#generateCertificates}.
- */
- final byte[] chainBytes;
- if (chain.length > 1) {
- /*
- * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
- * so we only need the certificates starting at index 1.
- */
- final byte[][] certsBytes = new byte[x509chain.length - 1][];
- int totalCertLength = 0;
- for (int i = 0; i < certsBytes.length; i++) {
- try {
- certsBytes[i] = x509chain[i + 1].getEncoded();
- totalCertLength += certsBytes[i].length;
- } catch (CertificateEncodingException e) {
- throw new KeyStoreException("Can't encode Certificate #" + i, e);
- }
- }
-
- /*
- * Serialize this into one byte array so we can later call
- * CertificateFactory#generateCertificates to recover them.
- */
- chainBytes = new byte[totalCertLength];
- int outputOffset = 0;
- for (int i = 0; i < certsBytes.length; i++) {
- final int certLength = certsBytes[i].length;
- System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
- outputOffset += certLength;
- certsBytes[i] = null;
- }
- } else {
- chainBytes = null;
- }
-
- /*
- * Make sure we clear out all the appropriate types before trying to
- * write.
- */
- if (shouldReplacePrivateKey) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- } else {
- Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
- Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
- }
-
- final int flags = (params == null) ? 0 : params.getFlags();
-
- if (shouldReplacePrivateKey
- && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put private key in keystore");
- } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put certificate #1 in keystore");
- } else if (chainBytes != null
- && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put certificate chain in keystore");
- }
- }
-
- 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();
- int keymasterAlgorithm;
- int keymasterDigest;
- try {
- keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(
- keyAlgorithmString);
- keymasterDigest =
- KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString);
- } catch (IllegalArgumentException e) {
- throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
- }
-
- KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
-
- int[] keymasterDigests;
- if (params.isDigestsSpecified()) {
- // Digest(s) specified in parameters
- keymasterDigests =
- KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests());
- if (keymasterDigest != -1) {
- // Digest also specified in the JCA key algorithm name.
- if (!com.android.internal.util.ArrayUtils.contains(
- keymasterDigests, keymasterDigest)) {
- throw new KeyStoreException("Key digest mismatch"
- + ". Key: " + keyAlgorithmString
- + ", parameter spec: " + Arrays.asList(params.getDigests()));
- }
- }
- } else {
- // No digest specified in parameters
- if (keymasterDigest != -1) {
- // Digest specified in the JCA key algorithm name.
- keymasterDigests = new int[] {keymasterDigest};
- } else {
- keymasterDigests = EmptyArray.INT;
- }
- }
- args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
- if (keymasterDigests.length > 0) {
- // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
- // This code will blow up if mode than one digest is specified.
- int digestOutputSizeBytes =
- KeymasterUtils.getDigestOutputSizeBytes(keymasterDigests[0]);
- if (digestOutputSizeBytes != -1) {
- // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
- }
- }
- if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
- if (keymasterDigests.length == 0) {
- throw new KeyStoreException("At least one digest algorithm must be specified"
- + " for key algorithm " + keyAlgorithmString);
- }
- }
-
- @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes();
- int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes(
- params.getBlockModes());
- if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
- && (params.isRandomizedEncryptionRequired())) {
- for (int keymasterBlockMode : keymasterBlockModes) {
- if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) {
- throw new KeyStoreException(
- "Randomized encryption (IND-CPA) required but may be violated by block"
- + " mode: "
- + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode(
- keymasterBlockMode)
- + ". See KeyStoreParameter documentation.");
- }
- }
- }
- for (int keymasterPurpose : KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) {
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
- }
- args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
- int[] keymasterPaddings = ArrayUtils.concat(
- KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings(
- params.getEncryptionPaddings()),
- KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings(
- params.getSignaturePaddings()));
- args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
- if (params.getUserAuthenticators() == 0) {
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- } else {
- args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
- KeyStoreKeyProperties.UserAuthenticator.allToKeymaster(
- params.getUserAuthenticators()));
- }
- if (params.getUserAuthenticationValidityDurationSeconds() != -1) {
- args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
- params.getUserAuthenticationValidityDurationSeconds());
- }
- 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 & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
- && (!params.isRandomizedEncryptionRequired())) {
- // Permit caller-provided IV when encrypting with this key
- args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
- }
-
- 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 {
- throw new KeyStoreException("Operation not supported because key encoding is unknown");
- }
-
- @Override
- public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
- if (isKeyEntry(alias)) {
- throw new KeyStoreException("Entry exists and is not a trusted certificate");
- }
-
- // We can't set something to null.
- if (cert == null) {
- throw new NullPointerException("cert == null");
- }
-
- final byte[] encoded;
- try {
- encoded = cert.getEncoded();
- } catch (CertificateEncodingException e) {
- throw new KeyStoreException(e);
- }
-
- if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
- android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
- throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
- }
- }
-
- @Override
- public void engineDeleteEntry(String alias) throws KeyStoreException {
- if (!isKeyEntry(alias) && !isCertificateEntry(alias)) {
- return;
- }
-
- if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
- throw new KeyStoreException("No such entry " + alias);
- }
- }
-
- private Set<String> getUniqueAliases() {
- final String[] rawAliases = mKeyStore.saw("");
- if (rawAliases == null) {
- return new HashSet<String>();
- }
-
- final Set<String> aliases = new HashSet<String>(rawAliases.length);
- for (String alias : rawAliases) {
- final int idx = alias.indexOf('_');
- if ((idx == -1) || (alias.length() <= idx)) {
- Log.e(NAME, "invalid alias: " + alias);
- continue;
- }
-
- aliases.add(new String(alias.substring(idx + 1)));
- }
-
- return aliases;
- }
-
- @Override
- public Enumeration<String> engineAliases() {
- return Collections.enumeration(getUniqueAliases());
- }
-
- @Override
- public boolean engineContainsAlias(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- 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);
- }
-
- @Override
- public int engineSize() {
- return getUniqueAliases().size();
- }
-
- @Override
- public boolean engineIsKeyEntry(String alias) {
- return isKeyEntry(alias);
- }
-
- private boolean isKeyEntry(String alias) {
- return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
- }
-
- private boolean isPrivateKeyEntry(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- 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");
- }
-
- return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
- }
-
- @Override
- public boolean engineIsCertificateEntry(String alias) {
- return !isKeyEntry(alias) && isCertificateEntry(alias);
- }
-
- @Override
- public String engineGetCertificateAlias(Certificate cert) {
- if (cert == null) {
- return null;
- }
-
- final Set<String> nonCaEntries = new HashSet<String>();
-
- /*
- * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
- * says to only compare the first certificate in the chain which is
- * equivalent to the USER_CERTIFICATE prefix for the Android keystore
- * convention.
- */
- final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE);
- if (certAliases != null) {
- for (String alias : certAliases) {
- final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
- if (certBytes == null) {
- continue;
- }
-
- final Certificate c = toCertificate(certBytes);
- nonCaEntries.add(alias);
-
- if (cert.equals(c)) {
- return alias;
- }
- }
- }
-
- /*
- * Look at all the TrustedCertificateEntry types. Skip all the
- * PrivateKeyEntry we looked at above.
- */
- final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
- if (certAliases != null) {
- for (String alias : caAliases) {
- if (nonCaEntries.contains(alias)) {
- continue;
- }
-
- final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
- if (certBytes == null) {
- continue;
- }
-
- final Certificate c =
- toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
- if (cert.equals(c)) {
- return alias;
- }
- }
- }
-
- return null;
- }
-
- @Override
- public void engineStore(OutputStream stream, char[] password) throws IOException,
- NoSuchAlgorithmException, CertificateException {
- throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
- }
-
- @Override
- public void engineLoad(InputStream stream, char[] password) throws IOException,
- NoSuchAlgorithmException, CertificateException {
- if (stream != null) {
- throw new IllegalArgumentException("InputStream not supported");
- }
-
- if (password != null) {
- throw new IllegalArgumentException("password not supported");
- }
-
- // Unfortunate name collision.
- mKeyStore = android.security.KeyStore.getInstance();
- }
-
- @Override
- public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
- throws KeyStoreException {
- if (entry == null) {
- throw new KeyStoreException("entry == null");
- }
-
- if (engineContainsAlias(alias)) {
- engineDeleteEntry(alias);
- }
-
- if (entry instanceof KeyStore.TrustedCertificateEntry) {
- KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
- engineSetCertificateEntry(alias, trE.getTrustedCertificate());
- return;
- }
-
- if (param != null && !(param instanceof KeyStoreParameter)) {
- throw new KeyStoreException(
- "protParam should be android.security.KeyStoreParameter; was: "
- + param.getClass().getName());
- }
-
- if (entry instanceof PrivateKeyEntry) {
- PrivateKeyEntry prE = (PrivateKeyEntry) entry;
- setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
- (KeyStoreParameter) param);
- } 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);
- }
- }
-
-}
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
deleted file mode 100644
index 43f3b30..0000000
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2012 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.Provider;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-
-/**
- * A provider focused on providing JCA interfaces for the Android KeyStore.
- *
- * @hide
- */
-public class AndroidKeyStoreProvider extends Provider {
- public static final String PROVIDER_NAME = "AndroidKeyStore";
-
- // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
- // classes when this provider is instantiated and installed early on during each app's
- // initialization process.
-
- private static final String PACKAGE_NAME = "android.security";
- private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
- PACKAGE_NAME + ".KeyStoreSecretKey";
-
- public AndroidKeyStoreProvider() {
- super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
-
- // java.security.KeyStore
- put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore");
-
- // java.security.KeyPairGenerator
- put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC");
- put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA");
-
- // javax.crypto.KeyGenerator
- put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES");
- put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1");
- put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224");
- put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256");
- put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384");
- put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512");
-
- // java.security.SecretKeyFactory
- putSecretKeyFactoryImpl("AES");
- putSecretKeyFactoryImpl("HmacSHA1");
- putSecretKeyFactoryImpl("HmacSHA224");
- putSecretKeyFactoryImpl("HmacSHA256");
- putSecretKeyFactoryImpl("HmacSHA384");
- putSecretKeyFactoryImpl("HmacSHA512");
-
- // javax.crypto.Mac
- putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1");
- putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
- putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
- putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
- putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512");
-
- // javax.crypto.Cipher
- putSymmetricCipherImpl("AES/ECB/NoPadding",
- PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding");
- putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
- PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding");
-
- putSymmetricCipherImpl("AES/CBC/NoPadding",
- PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding");
- putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
- PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding");
-
- putSymmetricCipherImpl("AES/CTR/NoPadding",
- PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding");
- }
-
- private void putSecretKeyFactoryImpl(String algorithm) {
- put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
- }
-
- private void putMacImpl(String algorithm, String implClass) {
- put("Mac." + algorithm, implClass);
- put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
- }
-
- private void putSymmetricCipherImpl(String transformation, String implClass) {
- put("Cipher." + transformation, implClass);
- put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
- }
-
- /**
- * 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();
- }
- Object spi;
- if (cryptoPrimitive instanceof Mac) {
- spi = ((Mac) cryptoPrimitive).getSpi();
- } else if (cryptoPrimitive instanceof Cipher) {
- spi = ((Cipher) cryptoPrimitive).getSpi();
- } else {
- throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive);
- }
- if (!(spi instanceof KeyStoreCryptoOperation)) {
- throw new IllegalArgumentException(
- "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive
- + ", spi: " + spi);
- }
- return ((KeyStoreCryptoOperation) spi).getOperationHandle();
- }
-}
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 6283e02..5d777b0 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -216,7 +216,7 @@ public class Credentials {
* particular {@code alias}. All three can exist for any given alias.
* Returns {@code true} if there was at least one of those types.
*/
- static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) {
+ public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) {
/*
* Make sure every type is deleted. There can be all three types, so
* don't use a conditional here.
@@ -231,7 +231,7 @@ public class Credentials {
* particular {@code alias}. All three can exist for any given alias.
* Returns {@code true} if there was at least one of those types.
*/
- static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) {
+ public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) {
/*
* Make sure every certificate type is deleted. There can be two types,
* so don't use a conditional here.
@@ -252,7 +252,7 @@ public class Credentials {
* Delete secret key for a particular {@code alias}.
* Returns {@code true} if an entry was was deleted.
*/
- static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
+ public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
return keystore.delete(Credentials.USER_SECRET_KEY + alias);
}
}
diff --git a/keystore/java/android/security/EcIesParameterSpec.java b/keystore/java/android/security/EcIesParameterSpec.java
deleted file mode 100644
index 3372da9..0000000
--- a/keystore/java/android/security/EcIesParameterSpec.java
+++ /dev/null
@@ -1,264 +0,0 @@
-package android.security;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.security.spec.AlgorithmParameterSpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-
-/**
- * {@link AlgorithmParameterSpec} for ECIES (Integrated Encryption Scheme using Elliptic Curve
- * cryptography) based on {@code ISO/IEC 18033-2}.
- *
- * <p>ECIES is a hybrid authenticated encryption scheme. Encryption is performed using an Elliptic
- * Curve (EC) public key. The resulting ciphertext can be decrypted only using the corresponding EC
- * private key. The scheme is called hybrid because the EC key is only used to securely encapsulate
- * symmetric key material. Encryption of plaintext and authentication of the corresponding
- * ciphertext is performed using symmetric cryptography.
- *
- * <p>Encryption using ECIES consists of two stages:
- * <ol>
- * <li>Key Encapsulation Mechanism (KEM) randomly generates symmetric key material and securely
- * encapsulates it in the output so that it can be extracted by the KEM when decrypting.
- * Encapsulated key material is represented in the output as an EC point.</li>
- * <li>The above symmetric key material is used by Data Encapsulation Mechanism (DEM) to encrypt the
- * provided plaintext and authenticate the ciphertext. The resulting authenticated ciphertext is
- * then output. When decrypting, the DEM first authenticates the ciphertext and, only if it
- * authenticates, decrypts the ciphertext and outputs the plaintext.</li>
- * </ol>
- *
- * <p>Details of KEM:
- * <ul>
- * <li>Only curves with cofactor of {@code 1} are supported.</li>
- * <li>{@code CheckMode}, {@code OldCofactorMode}, {@code CofactorMode}, and {@code SingleHashMode}
- * are {@code 0}.
- * <li>Point format is specified by {@link #getKemPointFormat()}.</li>
- * <li>KDF algorithm is specified by {@link #getKemKdfAlgorithm()}.</li>
- * </ul>
- *
- * <p>Details of DEM:
- * <ul>
- * <li>Only DEM1-like mechanism is supported, with its symmetric cipher (SC) specified by
- * {@link #getDemCipherTransformation()} (e.g., {@code AES/CBC/NoPadding} for standard DEM1) and
- * MAC algorithm specified by {@link #getDemMacAlgorithm()} (e.g., {@code HmacSHA1} for standard
- * DEM1).</li>
- * </ul>
- *
- * @hide
- */
-public class EcIesParameterSpec implements AlgorithmParameterSpec {
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {PointFormat.UNCOMPRESSED, PointFormat.COMPRESSED})
- public @interface PointFormatEnum {}
-
- /**
- * Wire format of the EC point.
- */
- public static abstract class PointFormat {
-
- private PointFormat() {}
-
- /** Unspecified point format. */
- public static final int UNSPECIFIED = -1;
-
- /**
- * Uncompressed point format: both coordinates are stored separately.
- *
- * <p>The wire format is byte {@code 0x04} followed by binary representation of the
- * {@code x} coordinate followed by binary representation of the {@code y} coordinate. See
- * {@code ISO 18033-2} section {@code 5.4.3}.
- */
- public static final int UNCOMPRESSED = 0;
-
- /**
- * Compressed point format: only one coordinate is stored.
- *
- * <p>The wire format is byte {@code 0x02} or {@code 0x03} (depending on the value of the
- * stored coordinate) followed by the binary representation of the {@code x} coordinate.
- * See {@code ISO 18033-2} section {@code 5.4.3}.
- */
- public static final int COMPRESSED = 1;
- }
-
- /**
- * Default parameter spec: compressed point format, {@code HKDFwithSHA256}, DEM uses 128-bit AES
- * GCM.
- */
- public static final EcIesParameterSpec DEFAULT = new EcIesParameterSpec(
- PointFormat.COMPRESSED,
- "HKDFwithSHA256",
- "AES/GCM/NoPadding",
- 128,
- null,
- 0);
-
- private final @PointFormatEnum int mKemPointFormat;
- private final String mKemKdfAlgorithm;
- private final String mDemCipherTransformation;
- private final int mDemCipherKeySize;
- private final String mDemMacAlgorithm;
- private final int mDemMacKeySize;
-
- private EcIesParameterSpec(
- @PointFormatEnum int kemPointFormat,
- String kemKdfAlgorithm,
- String demCipherTransformation,
- int demCipherKeySize,
- String demMacAlgorithm,
- int demMacKeySize) {
- mKemPointFormat = kemPointFormat;
- mKemKdfAlgorithm = kemKdfAlgorithm;
- mDemCipherTransformation = demCipherTransformation;
- mDemCipherKeySize = demCipherKeySize;
- mDemMacAlgorithm = demMacAlgorithm;
- mDemMacKeySize = demMacKeySize;
- }
-
- /**
- * Returns KEM EC point wire format or {@link PointFormat#UNSPECIFIED} if not specified.
- */
- public @PointFormatEnum int getKemPointFormat() {
- return mKemPointFormat;
- }
-
- /**
- * Returns KEM KDF algorithm (e.g., {@code HKDFwithSHA256} or {@code KDF1withSHA1}) or
- * {@code null} if not specified.
- */
- public String getKemKdfAlgorithm() {
- return mKemKdfAlgorithm;
- }
-
- /**
- * Returns DEM {@link Cipher} transformation (e.g., {@code AES/GCM/NoPadding} or
- * {@code AES/CBC/PKCS7Padding}) or {@code null} if not specified.
- *
- * @see Cipher#getInstance(String)
- * @see #getDemCipherKeySize()
- */
- public String getDemCipherTransformation() {
- return mDemCipherTransformation;
- }
-
- /**
- * Returns DEM {@link Cipher} key size in bits.
- *
- * @see #getDemCipherTransformation()
- */
- public int getDemCipherKeySize() {
- return mDemCipherKeySize;
- }
-
- /**
- * Returns DEM {@link Mac} algorithm (e.g., {@code HmacSHA256} or {@code HmacSHA1}) or
- * {@code null} if not specified.
- *
- * @see Mac#getInstance(String)
- * @see #getDemMacKeySize()
- */
- public String getDemMacAlgorithm() {
- return mDemMacAlgorithm;
- }
-
- /**
- * Returns DEM {@link Mac} key size in bits.
- *
- * @see #getDemCipherTransformation()
- */
- public int getDemMacKeySize() {
- return mDemMacKeySize;
- }
-
- /**
- * Builder of {@link EcIesParameterSpec}.
- */
- public static class Builder {
- private @PointFormatEnum int mKemPointFormat = PointFormat.UNSPECIFIED;
- private String mKemKdfAlgorithm;
- private String mDemCipherTransformation;
- private int mDemCipherKeySize = 128;
- private String mDemMacAlgorithm;
- private int mDemMacKeySize = -1;
-
- /**
- * Sets KEM EC point wire format.
- */
- public Builder setKemPointFormat(@PointFormatEnum int pointFormat) {
- mKemPointFormat = pointFormat;
- return this;
- }
-
- /**
- * Sets KEM KDF algorithm. For example, {@code HKDFwithSHA256}, {@code KDF2withSHA256}, or
- * {@code KDF1withSHA1}.
- */
- public Builder setKemKdfAlgorithm(String algorithm) {
- mKemKdfAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Sets DEM {@link Cipher} transformation. For example, {@code AES/GCM/NoPadding},
- * {@code AES/CBC/PKCS7Padding} or {@code AES/CTR/NoPadding}.
- *
- * @see Cipher#getInstance(String)
- */
- public Builder setDemCipherTransformation(String transformation) {
- mDemCipherTransformation = transformation;
- return this;
- }
-
- /**
- * Returns DEM {@link Cipher} key size in bits.
- *
- * <p>The default value is {@code 128} bits.
- *
- * @see #setDemCipherTransformation(String)
- */
- public Builder setDemCipherKeySize(int sizeBits) {
- mDemCipherKeySize = sizeBits;
- return this;
- }
-
- /**
- * Sets DEM {@link Mac} algorithm. For example, {@code HmacSHA256} or {@code HmacSHA1}.
- *
- * @see Mac#getInstance(String)
- */
- public Builder setDemMacAlgorithm(String algorithm) {
- mDemMacAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Sets DEM {@link Mac} key size in bits.
- *
- * <p>By default, {@code Mac} key size is the same as the {@code Cipher} key size.
- *
- * @see #setDemCipherKeySize(int)
- */
- public Builder setDemMacKeySize(int sizeBits) {
- mDemMacKeySize = sizeBits;
- return this;
- }
-
- /**
- * Returns a new {@link EcIesParameterSpec} based on the current state of this builder.
- */
- public EcIesParameterSpec build() {
- int demMacKeySize = (mDemMacKeySize != -1) ? mDemMacKeySize : mDemCipherKeySize;
- return new EcIesParameterSpec(
- mKemPointFormat,
- mKemKdfAlgorithm,
- mDemCipherTransformation,
- mDemCipherKeySize,
- mDemMacAlgorithm,
- demMacKeySize
- );
- }
- }
-}
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
new file mode 100644
index 0000000..c1df28c
--- /dev/null
+++ b/keystore/java/android/security/GateKeeper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.gatekeeper.IGateKeeperService;
+
+/**
+ * Convenience class for accessing the gatekeeper service.
+ *
+ * @hide
+ */
+public abstract class GateKeeper {
+
+ private GateKeeper() {}
+
+ public static IGateKeeperService getService() {
+ IGateKeeperService service = IGateKeeperService.Stub.asInterface(
+ ServiceManager.getService("android.service.gatekeeper.IGateKeeperService"));
+ if (service == null) {
+ throw new IllegalStateException("Gatekeeper service not available");
+ }
+ return service;
+ }
+
+ public static long getSecureUserId() throws IllegalStateException {
+ try {
+ return getService().getSecureUserId(UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Failed to obtain secure user ID from gatekeeper", e);
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index dfa41e8..7de26d6 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -15,22 +15,29 @@
*/
package android.security;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.Uri;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyProperties;
+
import java.io.ByteArrayInputStream;
import java.io.Closeable;
-import java.security.InvalidKeyException;
import java.security.Principal;
import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -40,7 +47,6 @@ import java.util.Locale;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
-import com.android.org.conscrypt.OpenSSLEngine;
import com.android.org.conscrypt.TrustedCertificateStore;
/**
@@ -83,8 +89,6 @@ import com.android.org.conscrypt.TrustedCertificateStore;
// TODO reference intent for credential installation when public
public final class KeyChain {
- private static final String TAG = "KeyChain";
-
/**
* @hide Also used by KeyChainService implementation
*/
@@ -115,13 +119,7 @@ public final class KeyChain {
* Extra for use with {@link #ACTION_CHOOSER}
* @hide Also used by KeyChainActivity implementation
*/
- public static final String EXTRA_HOST = "host";
-
- /**
- * Extra for use with {@link #ACTION_CHOOSER}
- * @hide Also used by KeyChainActivity implementation
- */
- public static final String EXTRA_PORT = "port";
+ public static final String EXTRA_URI = "uri";
/**
* Extra for use with {@link #ACTION_CHOOSER}
@@ -211,6 +209,7 @@ public final class KeyChain {
* successfully installed, otherwise {@link
* Activity#RESULT_CANCELED} will be returned.
*/
+ @NonNull
public static Intent createInstallIntent() {
Intent intent = new Intent(ACTION_INSTALL);
intent.setClassName(CERT_INSTALLER_PACKAGE,
@@ -224,6 +223,9 @@ public final class KeyChain {
* selected alias or null will be returned via the
* KeyChainAliasCallback callback.
*
+ * <p>The device or profile owner can intercept this before the activity
+ * is shown, to pick a specific private key alias.
+ *
* <p>{@code keyTypes} and {@code issuers} may be used to
* highlight suggested choices to the user, although to cope with
* sometimes erroneous values provided by servers, the user may be
@@ -242,7 +244,7 @@ public final class KeyChain {
* @param response Callback to invoke when the request completes;
* must not be null
* @param keyTypes The acceptable types of asymmetric keys such as
- * "EC" or "RSA", or a null array.
+ * "RSA" or "DSA", or a null array.
* @param issuers The acceptable certificate issuers for the
* certificate matching the private key, or null.
* @param host The host name of the server requesting the
@@ -252,10 +254,58 @@ public final class KeyChain {
* @param alias The alias to preselect if available, or null if
* unavailable.
*/
- public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response,
- String[] keyTypes, Principal[] issuers,
- String host, int port,
- String alias) {
+ public static void choosePrivateKeyAlias(@NonNull Activity activity,
+ @NonNull KeyChainAliasCallback response,
+ @KeyProperties.KeyAlgorithmEnum String[] keyTypes, Principal[] issuers,
+ @Nullable String host, int port, @Nullable String alias) {
+ Uri uri = null;
+ if (host != null) {
+ uri = new Uri.Builder()
+ .authority(host + (port != -1 ? ":" + port : ""))
+ .build();
+ }
+ choosePrivateKeyAlias(activity, response, keyTypes, issuers, uri, alias);
+ }
+
+ /**
+ * Launches an {@code Activity} for the user to select the alias
+ * for a private key and certificate pair for authentication. The
+ * selected alias or null will be returned via the
+ * KeyChainAliasCallback callback.
+ *
+ * <p>The device or profile owner can intercept this before the activity
+ * is shown, to pick a specific private key alias.</p>
+ *
+ * <p>{@code keyTypes} and {@code issuers} may be used to
+ * highlight suggested choices to the user, although to cope with
+ * sometimes erroneous values provided by servers, the user may be
+ * able to override these suggestions.
+ *
+ * <p>{@code host} and {@code port} may be used to give the user
+ * more context about the server requesting the credentials.
+ *
+ * <p>{@code alias} allows the chooser to preselect an existing
+ * alias which will still be subject to user confirmation.
+ *
+ * @param activity The {@link Activity} context to use for
+ * launching the new sub-Activity to prompt the user to select
+ * a private key; used only to call startActivity(); must not
+ * be null.
+ * @param response Callback to invoke when the request completes;
+ * must not be null
+ * @param keyTypes The acceptable types of asymmetric keys such as
+ * "EC" or "RSA", or a null array.
+ * @param issuers The acceptable certificate issuers for the
+ * certificate matching the private key, or null.
+ * @param uri The full URI the server is requesting the certificate
+ * for, or null if unavailable.
+ * @param alias The alias to preselect if available, or null if
+ * unavailable.
+ */
+ public static void choosePrivateKeyAlias(@NonNull Activity activity,
+ @NonNull KeyChainAliasCallback response,
+ @KeyProperties.KeyAlgorithmEnum String[] keyTypes, Principal[] issuers,
+ @Nullable Uri uri, @Nullable String alias) {
/*
* TODO currently keyTypes, issuers are unused. They are meant
* to follow the semantics and purpose of X509KeyManager
@@ -281,8 +331,7 @@ public final class KeyChain {
Intent intent = new Intent(ACTION_CHOOSER);
intent.setPackage(KEYCHAIN_PACKAGE);
intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response));
- intent.putExtra(EXTRA_HOST, host);
- intent.putExtra(EXTRA_PORT, port);
+ intent.putExtra(EXTRA_URI, uri);
intent.putExtra(EXTRA_ALIAS, alias);
// the PendingIntent is used to get calling package name
intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0));
@@ -303,11 +352,16 @@ public final class KeyChain {
* Returns the {@code PrivateKey} for the requested alias, or null
* if no there is no result.
*
- * @param alias The alias of the desired private key, typically
- * returned via {@link KeyChainAliasCallback#alias}.
+ * <p> This method may block while waiting for a connection to another process, and must never
+ * be called from the main thread.
+ *
+ * @param alias The alias of the desired private key, typically returned via
+ * {@link KeyChainAliasCallback#alias}.
* @throws KeyChainException if the alias was valid but there was some problem accessing it.
+ * @throws IllegalStateException if called from the main thread.
*/
- public static PrivateKey getPrivateKey(Context context, String alias)
+ @Nullable @WorkerThread
+ public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias)
throws KeyChainException, InterruptedException {
if (alias == null) {
throw new NullPointerException("alias == null");
@@ -319,15 +373,14 @@ public final class KeyChain {
if (keyId == null) {
throw new KeyChainException("keystore had a problem");
}
-
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
- return engine.getPrivateKeyById(keyId);
+ return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ KeyStore.getInstance(), keyId);
} catch (RemoteException e) {
throw new KeyChainException(e);
} catch (RuntimeException e) {
// only certain RuntimeExceptions can be propagated across the IKeyChainService call
throw new KeyChainException(e);
- } catch (InvalidKeyException e) {
+ } catch (UnrecoverableKeyException e) {
throw new KeyChainException(e);
} finally {
keyChainConnection.close();
@@ -338,12 +391,17 @@ public final class KeyChain {
* Returns the {@code X509Certificate} chain for the requested
* alias, or null if no there is no result.
*
+ * <p> This method may block while waiting for a connection to another process, and must never
+ * be called from the main thread.
+ *
* @param alias The alias of the desired certificate chain, typically
* returned via {@link KeyChainAliasCallback#alias}.
* @throws KeyChainException if the alias was valid but there was some problem accessing it.
+ * @throws IllegalStateException if called from the main thread.
*/
- public static X509Certificate[] getCertificateChain(Context context, String alias)
- throws KeyChainException, InterruptedException {
+ @Nullable @WorkerThread
+ public static X509Certificate[] getCertificateChain(@NonNull Context context,
+ @NonNull String alias) throws KeyChainException, InterruptedException {
if (alias == null) {
throw new NullPointerException("alias == null");
}
@@ -377,9 +435,11 @@ public final class KeyChain {
* specific {@code PrivateKey} type indicated by {@code algorithm} (e.g.,
* "RSA").
*/
- public static boolean isKeyAlgorithmSupported(String algorithm) {
+ public static boolean isKeyAlgorithmSupported(
+ @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) {
final String algUpper = algorithm.toUpperCase(Locale.US);
- return "EC".equals(algUpper) || "RSA".equals(algUpper);
+ return KeyProperties.KEY_ALGORITHM_EC.equals(algUpper)
+ || KeyProperties.KEY_ALGORITHM_RSA.equals(algUpper);
}
/**
@@ -388,8 +448,22 @@ public final class KeyChain {
* imported or generated. This can be used to tell if there is special
* hardware support that can be used to bind keys to the device in a way
* that makes it non-exportable.
+ *
+ * @deprecated Whether the key is bound to the secure hardware is known only
+ * once the key has been imported. To find out, use:
+ * <pre>{@code
+ * PrivateKey key = ...; // private key from KeyChain
+ *
+ * KeyFactory keyFactory =
+ * KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
+ * KeyInfo keyInfo = keyFactory.getKeySpec(key, KeyInfo.class);
+ * if (keyInfo.isInsideSecureHardware()) &#123;
+ * // The key is bound to the secure hardware of this Android
+ * &#125;}</pre>
*/
- public static boolean isBoundKeyAlgorithm(String algorithm) {
+ @Deprecated
+ public static boolean isBoundKeyAlgorithm(
+ @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) {
if (!isKeyAlgorithmSupported(algorithm)) {
return false;
}
@@ -398,7 +472,8 @@ public final class KeyChain {
}
/** @hide */
- public static X509Certificate toCertificate(byte[] bytes) {
+ @NonNull
+ public static X509Certificate toCertificate(@NonNull byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("bytes == null");
}
@@ -439,14 +514,16 @@ public final class KeyChain {
*
* Caller should call unbindService on the result when finished.
*/
- public static KeyChainConnection bind(Context context) throws InterruptedException {
+ @WorkerThread
+ public static KeyChainConnection bind(@NonNull Context context) throws InterruptedException {
return bindAsUser(context, Process.myUserHandle());
}
/**
* @hide
*/
- public static KeyChainConnection bindAsUser(Context context, UserHandle user)
+ @WorkerThread
+ public static KeyChainConnection bindAsUser(@NonNull Context context, UserHandle user)
throws InterruptedException {
if (context == null) {
throw new NullPointerException("context == null");
@@ -480,7 +557,7 @@ public final class KeyChain {
return new KeyChainConnection(context, keyChainServiceConnection, q.take());
}
- private static void ensureNotOnMainThread(Context context) {
+ private static void ensureNotOnMainThread(@NonNull Context context) {
Looper looper = Looper.myLooper();
if (looper != null && looper == context.getMainLooper()) {
throw new IllegalStateException(
diff --git a/keystore/java/android/security/KeyChainAliasCallback.java b/keystore/java/android/security/KeyChainAliasCallback.java
index 2500863..8e41377 100644
--- a/keystore/java/android/security/KeyChainAliasCallback.java
+++ b/keystore/java/android/security/KeyChainAliasCallback.java
@@ -15,6 +15,8 @@
*/
package android.security;
+import android.annotation.Nullable;
+
/**
* The KeyChainAliasCallback is the callback for {@link
* KeyChain#choosePrivateKeyAlias}.
@@ -25,5 +27,5 @@ public interface KeyChainAliasCallback {
* Called with the alias of the certificate chosen by the user, or
* null if no value was chosen.
*/
- public void alias(String alias);
+ public void alias(@Nullable String alias);
}
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
deleted file mode 100644
index a22c31c..0000000
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * 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.spec.AlgorithmParameterSpec;
-import java.util.Date;
-
-import javax.crypto.Cipher;
-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}.
- *
- * @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 @KeyStoreKeyProperties.PurposeEnum int mPurposes;
- private final String[] mEncryptionPaddings;
- private final String[] mBlockModes;
- private final boolean mRandomizedEncryptionRequired;
- private final @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
- private final int mUserAuthenticationValidityDurationSeconds;
-
- private KeyGeneratorSpec(
- Context context,
- String keyStoreAlias,
- int flags,
- Integer keySize,
- Date keyValidityStart,
- Date keyValidityForOriginationEnd,
- Date keyValidityForConsumptionEnd,
- @KeyStoreKeyProperties.PurposeEnum int purposes,
- String[] encryptionPaddings,
- String[] blockModes,
- boolean randomizedEncryptionRequired,
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators,
- int 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 < 0)
- && (userAuthenticationValidityDurationSeconds != -1)) {
- throw new IllegalArgumentException(
- "userAuthenticationValidityDurationSeconds must not be negative");
- }
-
- mContext = context;
- mKeystoreAlias = keyStoreAlias;
- mFlags = flags;
- mKeySize = keySize;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
- mPurposes = purposes;
- mEncryptionPaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
- mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
- mRandomizedEncryptionRequired = randomizedEncryptionRequired;
- mUserAuthenticators = userAuthenticators;
- 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.
- */
- 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.
- */
- public @KeyStoreKeyProperties.PurposeEnum int getPurposes() {
- return mPurposes;
- }
-
- /**
- * Gets the set of padding schemes with which the key can be used when encrypting/decrypting.
- */
- public String[] getEncryptionPaddings() {
- return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
- }
-
- /**
- * Gets the set of block modes with which the key can be used.
- */
- public String[] getBlockModes() {
- return ArrayUtils.cloneIfNotEmpty(mBlockModes);
- }
-
- /**
- * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic property
- * being required is <em>indistinguishability under chosen-plaintext attack ({@code
- * IND-CPA})</em>. This property is important because it mitigates several classes of
- * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
- * given plaintext always produces the same ciphertext, an attacker may see the repeated
- * ciphertexts and be able to deduce something about the plaintext.
- */
- public boolean isRandomizedEncryptionRequired() {
- return mRandomizedEncryptionRequired;
- }
-
- /**
- * Gets the set of 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 {@code 0} if the key can be used without user authentication.
- */
- public @KeyStoreKeyProperties.UserAuthenticatorEnum int getUserAuthenticators() {
- return 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 -1} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
- */
- public int 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 @KeyStoreKeyProperties.PurposeEnum int mPurposes;
- private String[] mEncryptionPaddings;
- private String[] mBlockModes;
- private boolean mRandomizedEncryptionRequired = true;
- private @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
- private int mUserAuthenticationValidityDurationSeconds = -1;
-
- /**
- * 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.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityEnd(Date)
- */
- public Builder setKeyValidityStart(Date startDate) {
- mKeyValidityStart = startDate;
- return this;
- }
-
- /**
- * Sets the time instant after which the key is no longer valid.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityStart(Date)
- * @see #setKeyValidityForConsumptionEnd(Date)
- * @see #setKeyValidityForOriginationEnd(Date)
- */
- 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.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityForConsumptionEnd(Date)
- */
- 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.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityForOriginationEnd(Date)
- */
- public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
- return this;
- }
-
- /**
- * Sets the set of purposes for which the key can be used.
- *
- * <p>This must be specified for all keys. There is no default.
- */
- public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) {
- mPurposes = purposes;
- return this;
- }
-
- /**
- * Sets the set of padding schemes with which the key can be used when
- * encrypting/decrypting. Attempts to use the key with any other padding scheme will be
- * rejected.
- *
- * <p>This must be specified for keys which are used for encryption/decryption.
- */
- public Builder setEncryptionPaddings(String... paddings) {
- mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings);
- return this;
- }
-
- /**
- * Sets the set of block modes with which the key can be used when encrypting/decrypting.
- * Attempts to use the key with any other block modes will be rejected.
- *
- * <p>This must be specified for encryption/decryption keys.
- */
- public Builder setBlockModes(String... blockModes) {
- mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes);
- return this;
- }
-
- /**
- * Sets whether encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic
- * property being required is <em>indistinguishability under chosen-plaintext attack
- * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
- * of weaknesses due to which ciphertext may leak information about plaintext. For example,
- * if a given plaintext always produces the same ciphertext, an attacker may see the
- * repeated ciphertexts and be able to deduce something about the plaintext.
- *
- * <p>By default, {@code IND-CPA} is required.
- *
- * <p>When {@code IND-CPA} is required:
- * <ul>
- * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited;
- * </li>
- * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
- * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
- * used.</li>
- *
- * <p>Before disabling this requirement, consider the following approaches instead:
- * <ul>
- * <li>If you are generating a random IV for encryption and then initializing a {@code}
- * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
- * instead. This will occur if the {@code Cipher} is initialized for encryption without an
- * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
- * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
- * random, such as the name of the file being encrypted, or transaction ID, or password,
- * or a device identifier), consider changing your design to use a random IV which will then
- * be provided in addition to the ciphertext to the entities which need to decrypt the
- * ciphertext.</li>
- * </ul>
- */
- public Builder setRandomizedEncryptionRequired(boolean required) {
- mRandomizedEncryptionRequired = required;
- 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)
- */
- public Builder setUserAuthenticators(
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators) {
- mUserAuthenticators = userAuthenticators;
- 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(int)
- */
- 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,
- mEncryptionPaddings,
- mBlockModes,
- mRandomizedEncryptionRequired,
- mUserAuthenticators,
- mUserAuthenticationValidityDurationSeconds);
- }
- }
-}
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index fce02df..d849317 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -16,7 +16,12 @@
package android.security;
+import android.app.KeyguardManager;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import java.math.BigInteger;
@@ -49,14 +54,12 @@ import javax.security.auth.x500.X500Principal;
* <p>
* The self-signed X.509 certificate may be replaced at a later time by a
* certificate signed by a real Certificate Authority.
+ *
+ * @deprecated Use {@link KeyGenParameterSpec} instead.
*/
+@Deprecated
public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
- private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
- private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
- private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
- private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
-
private final Context mContext;
private final String mKeystoreAlias;
@@ -77,28 +80,6 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private final int mFlags;
- private final Date mKeyValidityStart;
-
- private final Date mKeyValidityForOriginationEnd;
-
- private final Date mKeyValidityForConsumptionEnd;
-
- private final @KeyStoreKeyProperties.PurposeEnum int mPurposes;
-
- private final String[] mDigests;
-
- private final String[] mEncryptionPaddings;
-
- private final String[] mSignaturePaddings;
-
- private final String[] mBlockModes;
-
- private final boolean mRandomizedEncryptionRequired;
-
- private final @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
-
- private final int mUserAuthenticationValidityDurationSeconds;
-
/**
* Parameter specification for the "{@code AndroidKeyPairGenerator}"
* instance of the {@link java.security.KeyPairGenerator} API. The
@@ -119,7 +100,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* @param context Android context for the activity
* @param keyStoreAlias name to use for the generated key in the Android
* keystore
- * @param keyType key algorithm to use (EC, RSA)
+ * @param keyType key algorithm to use (RSA, DSA, EC)
* @param keySize size of key to generate
* @param spec the underlying key type parameters
* @param subjectDN X.509 v3 Subject Distinguished Name
@@ -133,39 +114,21 @@ 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 keyValidityStart,
- Date keyValidityForOriginationEnd,
- Date keyValidityForConsumptionEnd,
- @KeyStoreKeyProperties.PurposeEnum int purposes,
- String[] digests,
- String[] encryptionPaddings,
- String[] signaturePaddings,
- String[] blockModes,
- boolean randomizedEncryptionRequired,
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators,
- int userAuthenticationValidityDurationSeconds) {
+ Date startDate, Date endDate, int flags) {
if (context == null) {
throw new IllegalArgumentException("context == null");
} else if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
- } else if ((userAuthenticationValidityDurationSeconds < 0)
- && (userAuthenticationValidityDurationSeconds != -1)) {
- throw new IllegalArgumentException(
- "userAuthenticationValidityDurationSeconds must not be negative");
- }
-
- if (subjectDN == null) {
- subjectDN = DEFAULT_CERT_SUBJECT;
- }
- if (startDate == null) {
- startDate = DEFAULT_CERT_NOT_BEFORE;
- }
- if (endDate == null) {
- endDate = DEFAULT_CERT_NOT_AFTER;
- }
- if (serialNumber == null) {
- serialNumber = DEFAULT_CERT_SERIAL_NUMBER;
+ } else if (subjectDN == null) {
+ throw new IllegalArgumentException("subjectDN == null");
+ } else if (serialNumber == null) {
+ throw new IllegalArgumentException("serialNumber == null");
+ } else if (startDate == null) {
+ throw new IllegalArgumentException("startDate == null");
+ } else if (endDate == null) {
+ throw new IllegalArgumentException("endDate == null");
+ } else if (endDate.before(startDate)) {
+ throw new IllegalArgumentException("endDate < startDate");
}
if (endDate.before(startDate)) {
@@ -182,49 +145,6 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mStartDate = startDate;
mEndDate = endDate;
mFlags = flags;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
- mPurposes = purposes;
- mDigests = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(digests));
- mEncryptionPaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
- mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
- mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
- mRandomizedEncryptionRequired = randomizedEncryptionRequired;
- mUserAuthenticators = userAuthenticators;
- 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,
- 0, // purposes
- null, // digests
- null, // encryption paddings
- null, // signature paddings
- null, // block modes
- false, // randomized encryption required
- 0, // user authenticators
- -1 // user authentication validity duration (seconds)
- );
}
/**
@@ -243,9 +163,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Returns the key type (e.g., "EC", "RSA") specified by this parameter.
+ * Returns the type of key pair (e.g., {@code EC}, {@code RSA}) to be generated. See
+ * {@link KeyProperties}.{@code KEY_ALGORITHM} constants.
*/
- public String getKeyType() {
+ @Nullable
+ public @KeyProperties.KeyAlgorithmEnum String getKeyType() {
return mKeyType;
}
@@ -262,6 +184,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Returns the {@link AlgorithmParameterSpec} that will be used for creation
* of the key pair.
*/
+ @NonNull
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
return mSpec;
}
@@ -270,6 +193,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Gets the subject distinguished name to be used on the X.509 certificate
* that will be put in the {@link java.security.KeyStore}.
*/
+ @NonNull
public X500Principal getSubjectDN() {
return mSubjectDN;
}
@@ -278,6 +202,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Gets the serial number to be used on the X.509 certificate that will be
* put in the {@link java.security.KeyStore}.
*/
+ @NonNull
public BigInteger getSerialNumber() {
return mSerialNumber;
}
@@ -286,6 +211,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Gets the start date to be used on the X.509 certificate that will be put
* in the {@link java.security.KeyStore}.
*/
+ @NonNull
public Date getStartDate() {
return mStartDate;
}
@@ -294,6 +220,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Gets the end date to be used on the X.509 certificate that will be put in
* the {@link java.security.KeyStore}.
*/
+ @NonNull
public Date getEndDate() {
return mEndDate;
}
@@ -301,141 +228,24 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* @hide
*/
- int getFlags() {
+ public int getFlags() {
return mFlags;
}
/**
- * Returns {@code true} if this parameter will require generated keys to be
- * encrypted in the {@link java.security.KeyStore}.
- */
- public boolean isEncryptionRequired() {
- return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
- }
-
- /**
- * Gets the time instant before which the key pair is not yet valid.
- *
- * @return instant or {@code null} if not restricted.
+ * Returns {@code true} if the key must be encrypted at rest. This will protect the key pair
+ * with the secure lock screen credential (e.g., password, PIN, or pattern).
*
- * @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.
+ * <p>Note that encrypting the key at rest requires that the secure lock screen (e.g., password,
+ * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be
+ * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device
+ * Administrator). Finally, this key cannot be used until the user unlocks the secure lock
+ * screen after boot.
*
- * @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.
- *
- * @hide
+ * @see KeyguardManager#isDeviceSecure()
*/
- public @KeyStoreKeyProperties.PurposeEnum int getPurposes() {
- return mPurposes;
- }
-
- /**
- * Gets the set of digest algorithms with which the key can be used.
- *
- * @hide
- */
- public String[] getDigests() {
- return ArrayUtils.cloneIfNotEmpty(mDigests);
- }
-
- /**
- * Gets the set of padding schemes with which the key can be used when encrypting/decrypting.
- *
- * @hide
- */
- public String[] getEncryptionPaddings() {
- return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
- }
-
- /**
- * Gets the set of padding schemes with which the key can be used when signing/verifying.
- *
- * @hide
- */
- public String[] getSignaturePaddings() {
- return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
- }
-
- /**
- * Gets the set of block modes with which the key can be used.
- *
- * @hide
- */
- public String[] getBlockModes() {
- return ArrayUtils.cloneIfNotEmpty(mBlockModes);
- }
-
- /**
- * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic property
- * being required is <em>indistinguishability under chosen-plaintext attack ({@code
- * IND-CPA})</em>. This property is important because it mitigates several classes of
- * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
- * given plaintext always produces the same ciphertext, an attacker may see the repeated
- * ciphertexts and be able to deduce something about the plaintext.
- *
- * @hide
- */
- public boolean isRandomizedEncryptionRequired() {
- return mRandomizedEncryptionRequired;
- }
-
- /**
- * Gets the set of 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 {@code 0} if the key can be used without user authentication.
- *
- * @hide
- */
- public @KeyStoreKeyProperties.UserAuthenticatorEnum int getUserAuthenticators() {
- return 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 -1} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
- *
- * @hide
- */
- public int getUserAuthenticationValidityDurationSeconds() {
- return mUserAuthenticationValidityDurationSeconds;
+ public boolean isEncryptionRequired() {
+ return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
}
/**
@@ -458,7 +268,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* .setSubject(new X500Principal(&quot;CN=myKey&quot;)).setSerial(BigInteger.valueOf(1337))
* .setStartDate(start.getTime()).setEndDate(end.getTime()).build();
* </pre>
+ *
+ * @deprecated Use {@link KeyGenParameterSpec.Builder} instead.
*/
+ @Deprecated
public final static class Builder {
private final Context mContext;
@@ -480,35 +293,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private int mFlags;
- private Date mKeyValidityStart;
-
- private Date mKeyValidityForOriginationEnd;
-
- private Date mKeyValidityForConsumptionEnd;
-
- private @KeyStoreKeyProperties.PurposeEnum int mPurposes;
-
- private String[] mDigests;
-
- private String[] mEncryptionPaddings;
-
- private String[] mSignaturePaddings;
-
- private String[] mBlockModes;
-
- private boolean mRandomizedEncryptionRequired = true;
-
- private @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
-
- private int mUserAuthenticationValidityDurationSeconds = -1;
-
/**
* 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) {
+ public Builder(@NonNull Context context) {
if (context == null) {
throw new NullPointerException("context == null");
}
@@ -520,7 +311,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* {@link java.security.KeyStore} instance using the
* {@code AndroidKeyStore} provider.
*/
- public Builder setAlias(String alias) {
+ @NonNull
+ public Builder setAlias(@NonNull String alias) {
if (alias == null) {
throw new NullPointerException("alias == null");
}
@@ -529,13 +321,19 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * Sets the key type (e.g., EC, RSA) of the keypair to be created.
+ * Sets the type of key pair (e.g., {@code EC}, {@code RSA}) of the key pair to be
+ * generated. See {@link KeyProperties}.{@code KEY_ALGORITHM} constants.
+ *
*/
- public Builder setKeyType(String keyType) throws NoSuchAlgorithmException {
+ @NonNull
+ public Builder setKeyType(@NonNull @KeyProperties.KeyAlgorithmEnum String keyType)
+ throws NoSuchAlgorithmException {
if (keyType == null) {
throw new NullPointerException("keyType == null");
} else {
- if (KeyStore.getKeyTypeForAlgorithm(keyType) == -1) {
+ try {
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(keyType);
+ } catch (IllegalArgumentException e) {
throw new NoSuchAlgorithmException("Unsupported key type: " + keyType);
}
}
@@ -548,6 +346,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* key type of RSA this will set the modulus size and for a key type of
* EC it will select a curve with a matching field size.
*/
+ @NonNull
public Builder setKeySize(int keySize) {
if (keySize < 0) {
throw new IllegalArgumentException("keySize < 0");
@@ -560,7 +359,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Sets the algorithm-specific key generation parameters. For example, for RSA keys
* this may be an instance of {@link java.security.spec.RSAKeyGenParameterSpec}.
*/
- public Builder setAlgorithmParameterSpec(AlgorithmParameterSpec spec) {
+ public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) {
if (spec == null) {
throw new NullPointerException("spec == null");
}
@@ -571,12 +370,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the subject used for the self-signed certificate of the
* generated key pair.
- *
- * <p>The subject must be specified on API Level
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On
- * newer platforms the subject defaults to {@code CN=fake} if not specified.
*/
- public Builder setSubject(X500Principal subject) {
+ @NonNull
+ public Builder setSubject(@NonNull X500Principal subject) {
if (subject == null) {
throw new NullPointerException("subject == null");
}
@@ -587,12 +383,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the serial number used for the self-signed certificate of the
* generated key pair.
- *
- * <p>The serial number must be specified on API Level
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On
- * newer platforms the serial number defaults to {@code 1} if not specified.
*/
- public Builder setSerialNumber(BigInteger serialNumber) {
+ @NonNull
+ public Builder setSerialNumber(@NonNull BigInteger serialNumber) {
if (serialNumber == null) {
throw new NullPointerException("serialNumber == null");
}
@@ -603,12 +396,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the start of the validity period for the self-signed certificate
* of the generated key pair.
- *
- * <p>The date must be specified on API Level
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On
- * newer platforms the date defaults to {@code Jan 1 1970} if not specified.
*/
- public Builder setStartDate(Date startDate) {
+ @NonNull
+ public Builder setStartDate(@NonNull Date startDate) {
if (startDate == null) {
throw new NullPointerException("startDate == null");
}
@@ -619,12 +409,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the end of the validity period for the self-signed certificate
* of the generated key pair.
- *
- * <p>The date must be specified on API Level
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On
- * newer platforms the date defaults to {@code Jan 1 2048} if not specified.
*/
- public Builder setEndDate(Date endDate) {
+ @NonNull
+ public Builder setEndDate(@NonNull Date endDate) {
if (endDate == null) {
throw new NullPointerException("endDate == null");
}
@@ -633,209 +420,20 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
- * 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() {
- mFlags |= KeyStore.FLAG_ENCRYPTED;
- return this;
- }
-
- /**
- * Sets the time instant before which the key is not yet valid.
- *
- * <p>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.
- *
- * <p>By default, the key is valid at any instant.
+ * Indicates that this key pair must be encrypted at rest. This will protect the key pair
+ * with the secure lock screen credential (e.g., password, PIN, or pattern).
*
- * @see #setKeyValidityStart(Date)
- * @see #setKeyValidityForConsumptionEnd(Date)
- * @see #setKeyValidityForOriginationEnd(Date)
+ * <p>Note that this feature requires that the secure lock screen (e.g., password, PIN,
+ * pattern) is set up, otherwise key pair generation will fail. Moreover, this key pair will
+ * be deleted when the secure lock screen is disabled or reset (e.g., by the user or a
+ * Device Administrator). Finally, this key pair cannot be used until the user unlocks the
+ * secure lock screen after boot.
*
- * @hide
+ * @see KeyguardManager#isDeviceSecure()
*/
- 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.
- *
- * <p>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.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityForOriginationEnd(Date)
- *
- * @hide
- */
- public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
- return this;
- }
-
- /**
- * Sets the set of purposes for which the key can be used.
- *
- * <p>This must be specified for all keys. There is no default.
- *
- * @hide
- */
- public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) {
- mPurposes = purposes;
- return this;
- }
-
- /**
- * Sets the set of digests with which the key can be used when signing/verifying. Attempts
- * to use the key with any other digest will be rejected.
- *
- * <p>This must be specified for keys which are used for signing/verification.
- *
- * @hide
- */
- public Builder setDigests(String... digests) {
- mDigests = ArrayUtils.cloneIfNotEmpty(digests);
- return this;
- }
-
- /**
- * Sets the set of padding schemes with which the key can be used when
- * encrypting/decrypting. Attempts to use the key with any other padding scheme will be
- * rejected.
- *
- * <p>This must be specified for keys which are used for encryption/decryption.
- *
- * @hide
- */
- public Builder setEncryptionPaddings(String... paddings) {
- mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings);
- return this;
- }
-
- /**
- * Sets the set of padding schemes with which the key can be used when
- * signing/verifying. Attempts to use the key with any other padding scheme will be
- * rejected.
- *
- * <p>This must be specified for RSA keys which are used for signing/verification.
- *
- * @hide
- */
- public Builder setSignaturePaddings(String... paddings) {
- mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings);
- return this;
- }
-
- /**
- * Sets the set of block modes with which the key can be used when encrypting/decrypting.
- * Attempts to use the key with any other block modes will be rejected.
- *
- * <p>This must be specified for encryption/decryption keys.
- *
- * @hide
- */
- public Builder setBlockModes(String... blockModes) {
- mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes);
- return this;
- }
-
- /**
- * Sets whether encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic
- * property being required is <em>indistinguishability under chosen-plaintext attack
- * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
- * of weaknesses due to which ciphertext may leak information about plaintext. For example,
- * if a given plaintext always produces the same ciphertext, an attacker may see the
- * repeated ciphertexts and be able to deduce something about the plaintext.
- *
- * <p>By default, {@code IND-CPA} is required.
- *
- * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not
- * offer {@code IND-CPA}, such as RSA without padding, are prohibited.
- *
- * <p>Before disabling this requirement, consider the following approaches instead:
- * <ul>
- * <li>If you are using RSA encryption without padding, consider switching to padding
- * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
- * </ul>
- *
- * @hide
- */
- public Builder setRandomizedEncryptionRequired(boolean required) {
- mRandomizedEncryptionRequired = required;
- 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 {@code 0} if this key can be accessed
- * without user authentication.
- *
- * @see #setUserAuthenticationValidityDurationSeconds(int)
- *
- * @hide
- */
- public Builder setUserAuthenticators(
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators) {
- mUserAuthenticators = userAuthenticators;
- 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(int)
- *
- * @hide
- */
- public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
- mUserAuthenticationValidityDurationSeconds = seconds;
+ @NonNull
+ public Builder setEncryptionRequired() {
+ mFlags |= KeyStore.FLAG_ENCRYPTED;
return this;
}
@@ -845,6 +443,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* @throws IllegalArgumentException if a required field is missing
* @return built instance of {@code KeyPairGeneratorSpec}
*/
+ @NonNull
public KeyPairGeneratorSpec build() {
return new KeyPairGeneratorSpec(mContext,
mKeystoreAlias,
@@ -855,18 +454,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mSerialNumber,
mStartDate,
mEndDate,
- mFlags,
- mKeyValidityStart,
- mKeyValidityForOriginationEnd,
- mKeyValidityForConsumptionEnd,
- mPurposes,
- mDigests,
- mEncryptionPaddings,
- mSignaturePaddings,
- mBlockModes,
- mRandomizedEncryptionRequired,
- mUserAuthenticators,
- mUserAuthenticationValidityDurationSeconds);
+ mFlags);
}
}
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 3bc1b17..98b44dc 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,21 +16,33 @@
package android.security;
-import com.android.org.conscrypt.NativeConstants;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
+import android.security.keystore.KeyExpiredException;
+import android.security.keystore.KeyNotYetValidException;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
+import java.math.BigInteger;
import java.security.InvalidKeyException;
+import java.util.List;
import java.util.Locale;
/**
@@ -53,11 +65,32 @@ public class KeyStore {
public static final int UNDEFINED_ACTION = 9;
public static final int WRONG_PASSWORD = 10;
+ /**
+ * Per operation authentication is needed before this operation is valid.
+ * This is returned from {@link #begin} when begin succeeds but the operation uses
+ * per-operation authentication and must authenticate before calling {@link #update} or
+ * {@link #finish}.
+ */
+ public static final int OP_AUTH_NEEDED = 15;
+
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
// Flags for "put" "import" and "generate"
public static final int FLAG_NONE = 0;
+
+ /**
+ * Indicates that this key (or key pair) must be encrypted at rest. This will protect the key
+ * (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern).
+ *
+ * <p>Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set
+ * up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key
+ * pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or
+ * a Device Administrator). Finally, this key (or key pair) cannot be used until the user
+ * unlocks the secure lock screen after boot.
+ *
+ * @see KeyguardManager#isDeviceSecure()
+ */
public static final int FLAG_ENCRYPTED = 1;
// States
@@ -66,11 +99,22 @@ public class KeyStore {
private int mError = NO_ERROR;
private final IKeystoreService mBinder;
+ private final Context mContext;
private IBinder mToken;
private KeyStore(IKeystoreService binder) {
mBinder = binder;
+ mContext = getApplicationContext();
+ }
+
+ public static Context getApplicationContext() {
+ Application application = ActivityThread.currentApplication();
+ if (application == null) {
+ throw new IllegalStateException(
+ "Failed to obtain application Context from ActivityThread");
+ }
+ return application;
}
public static KeyStore getInstance() {
@@ -86,20 +130,10 @@ public class KeyStore {
return mToken;
}
- static int getKeyTypeForAlgorithm(String keyType) {
- if ("RSA".equalsIgnoreCase(keyType)) {
- return NativeConstants.EVP_PKEY_RSA;
- } else if ("EC".equalsIgnoreCase(keyType)) {
- return NativeConstants.EVP_PKEY_EC;
- } else {
- return -1;
- }
- }
-
- public State state() {
+ public State state(int userId) {
final int ret;
try {
- ret = mBinder.test();
+ ret = mBinder.getState(userId);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
throw new AssertionError(e);
@@ -113,6 +147,10 @@ public class KeyStore {
}
}
+ public State state() {
+ return state(UserHandle.myUserId());
+ }
+
public boolean isUnlocked() {
return state() == State.UNLOCKED;
}
@@ -127,11 +165,15 @@ public class KeyStore {
}
public boolean put(String key, byte[] value, int uid, int flags) {
+ return insert(key, value, uid, flags) == NO_ERROR;
+ }
+
+ public int insert(String key, byte[] value, int uid, int flags) {
try {
- return mBinder.insert(key, value, uid, flags) == NO_ERROR;
+ return mBinder.insert(key, value, uid, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
- return false;
+ return SYSTEM_ERROR;
}
}
@@ -161,17 +203,20 @@ public class KeyStore {
return contains(key, UID_SELF);
}
- public String[] saw(String prefix, int uid) {
+ /**
+ * List all entries in the keystore for {@code uid} starting with {@code prefix}.
+ */
+ public String[] list(String prefix, int uid) {
try {
- return mBinder.saw(prefix, uid);
+ return mBinder.list(prefix, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
}
}
- public String[] saw(String prefix) {
- return saw(prefix, UID_SELF);
+ public String[] list(String prefix) {
+ return list(prefix, UID_SELF);
}
public boolean reset() {
@@ -183,9 +228,15 @@ public class KeyStore {
}
}
- public boolean password(String password) {
+ /**
+ * Attempt to lock the keystore for {@code user}.
+ *
+ * @param user Android user to lock.
+ * @return whether {@code user}'s keystore was locked.
+ */
+ public boolean lock(int userId) {
try {
- return mBinder.password(password) == NO_ERROR;
+ return mBinder.lock(userId) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
@@ -193,8 +244,24 @@ public class KeyStore {
}
public boolean lock() {
+ return lock(UserHandle.myUserId());
+ }
+
+ /**
+ * Attempt to unlock the keystore for {@code user} with the password {@code password}.
+ * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or
+ * created.
+ *
+ * @param user Android user ID to operate on
+ * @param password user's keystore password. Should be the most recent value passed to
+ * {@link #onUserPasswordChanged} for the user.
+ *
+ * @return whether the keystore was unlocked.
+ */
+ public boolean unlock(int userId, String password) {
try {
- return mBinder.lock() == NO_ERROR;
+ mError = mBinder.unlock(userId, password);
+ return mError == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
@@ -202,9 +269,15 @@ public class KeyStore {
}
public boolean unlock(String password) {
+ return unlock(UserHandle.getUserId(Process.myUid()), password);
+ }
+
+ /**
+ * Check if the keystore for {@code userId} is empty.
+ */
+ public boolean isEmpty(int userId) {
try {
- mError = mBinder.unlock(password);
- return mError == NO_ERROR;
+ return mBinder.isEmpty(userId) != 0;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
@@ -212,12 +285,7 @@ public class KeyStore {
}
public boolean isEmpty() {
- try {
- return mBinder.zero() == KEY_NOT_FOUND;
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return false;
- }
+ return isEmpty(UserHandle.myUserId());
}
public boolean generate(String key, int uid, int keyType, int keySize, int flags,
@@ -240,28 +308,6 @@ public class KeyStore {
}
}
- public byte[] getPubkey(String key) {
- try {
- return mBinder.get_pubkey(key);
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return null;
- }
- }
-
- public boolean delKey(String key, int uid) {
- try {
- return mBinder.del_key(key, uid) == NO_ERROR;
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return false;
- }
- }
-
- public boolean delKey(String key) {
- return delKey(key, UID_SELF);
- }
-
public byte[] sign(String key, byte[] data) {
try {
return mBinder.sign(key, data);
@@ -325,7 +371,7 @@ public class KeyStore {
}
}
- // TODO remove this when it's removed from Settings
+ // TODO: remove this when it's removed from Settings
public boolean isHardwareBacked() {
return isHardwareBacked("RSA");
}
@@ -348,36 +394,6 @@ public class KeyStore {
}
}
- public boolean resetUid(int uid) {
- try {
- mError = mBinder.reset_uid(uid);
- return mError == NO_ERROR;
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return false;
- }
- }
-
- public boolean syncUid(int sourceUid, int targetUid) {
- try {
- mError = mBinder.sync_uid(sourceUid, targetUid);
- return mError == NO_ERROR;
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return false;
- }
- }
-
- public boolean passwordUid(String password, int uid) {
- try {
- mError = mBinder.password_uid(password, uid);
- return mError == NO_ERROR;
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to keystore", e);
- return false;
- }
- }
-
public int getLastError() {
return mError;
}
@@ -443,9 +459,9 @@ public class KeyStore {
}
public OperationResult begin(String alias, int purpose, boolean pruneable,
- KeymasterArguments args, byte[] entropy, KeymasterArguments outArgs) {
+ KeymasterArguments args, byte[] entropy) {
try {
- return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, outArgs);
+ return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
@@ -461,15 +477,20 @@ public class KeyStore {
}
}
- public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
+ public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature,
+ byte[] entropy) {
try {
- return mBinder.finish(token, arguments, signature);
+ return mBinder.finish(token, arguments, signature, entropy);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
}
}
+ public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
+ return finish(token, arguments, signature, null);
+ }
+
public int abort(IBinder token) {
try {
return mBinder.abort(token);
@@ -482,7 +503,8 @@ public class KeyStore {
/**
* Check if the operation referenced by {@code token} is currently authorized.
*
- * @param token An operation token returned by a call to {@link KeyStore.begin}.
+ * @param token An operation token returned by a call to
+ * {@link #begin(String, int, boolean, KeymasterArguments, byte[], KeymasterArguments) begin}.
*/
public boolean isOperationAuthorized(IBinder token) {
try {
@@ -510,17 +532,79 @@ public class KeyStore {
}
/**
+ * Notify keystore that a user's password has changed.
+ *
+ * @param userId the user whose password changed.
+ * @param newPassword the new password or "" if the password was removed.
+ */
+ public boolean onUserPasswordChanged(int userId, String newPassword) {
+ // Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that
+ // explicit here.
+ if (newPassword == null) {
+ newPassword = "";
+ }
+ try {
+ return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ /**
+ * Notify keystore that a user was added.
+ *
+ * @param userId the new user.
+ * @param parentId the parent of the new user, or -1 if the user has no parent. If parentId is
+ * specified then the new user's keystore will be intialized with the same secure lockscreen
+ * password as the parent.
+ */
+ public void onUserAdded(int userId, int parentId) {
+ try {
+ mBinder.onUserAdded(userId, parentId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ }
+ }
+
+ /**
+ * Notify keystore that a user was added.
+ *
+ * @param userId the new user.
+ */
+ public void onUserAdded(int userId) {
+ onUserAdded(userId, -1);
+ }
+
+ /**
+ * Notify keystore that a user was removed.
+ *
+ * @param userId the removed user.
+ */
+ public void onUserRemoved(int userId) {
+ try {
+ mBinder.onUserRemoved(userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ }
+ }
+
+ public boolean onUserPasswordChanged(String newPassword) {
+ return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
+ }
+
+ /**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
*/
- static KeyStoreException getKeyStoreException(int errorCode) {
+ public static KeyStoreException getKeyStoreException(int errorCode) {
if (errorCode > 0) {
// KeyStore layer error
switch (errorCode) {
case NO_ERROR:
return new KeyStoreException(errorCode, "OK");
case LOCKED:
- return new KeyStoreException(errorCode, "Keystore locked");
+ return new KeyStoreException(errorCode, "User authentication required");
case UNINITIALIZED:
return new KeyStoreException(errorCode, "Keystore not initialized");
case SYSTEM_ERROR:
@@ -531,6 +615,8 @@ public class KeyStore {
return new KeyStoreException(errorCode, "Key not found");
case VALUE_CORRUPTED:
return new KeyStoreException(errorCode, "Key blob corrupted");
+ case OP_AUTH_NEEDED:
+ return new KeyStoreException(errorCode, "Operation requires authorization");
default:
return new KeyStoreException(errorCode, String.valueOf(errorCode));
}
@@ -553,24 +639,76 @@ public class KeyStore {
* Returns an {@link InvalidKeyException} corresponding to the provided
* {@link KeyStoreException}.
*/
- static InvalidKeyException getInvalidKeyException(KeyStoreException e) {
+ public InvalidKeyException getInvalidKeyException(
+ String keystoreKeyAlias, KeyStoreException e) {
switch (e.getErrorCode()) {
+ case LOCKED:
+ return new UserNotAuthenticatedException();
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();
+ case OP_AUTH_NEEDED:
+ {
+ // We now need to determine whether the key/operation can become usable if user
+ // authentication is performed, or whether it can never become usable again.
+ // User authentication requirements are contained in the key's characteristics. We
+ // need to check whether these requirements can be be satisfied by asking the user
+ // to authenticate.
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int getKeyCharacteristicsErrorCode =
+ getKeyCharacteristics(keystoreKeyAlias, null, null, keyCharacteristics);
+ if (getKeyCharacteristicsErrorCode != NO_ERROR) {
+ return new InvalidKeyException(
+ "Failed to obtained key characteristics",
+ getKeyStoreException(getKeyCharacteristicsErrorCode));
+ }
+ List<BigInteger> keySids =
+ keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
+ if (keySids.isEmpty()) {
+ // Key is not bound to any SIDs -- no amount of authentication will help here.
+ return new KeyPermanentlyInvalidatedException();
+ }
+ long rootSid = GateKeeper.getSecureUserId();
+ if ((rootSid != 0) && (keySids.contains(KeymasterArguments.toUint64(rootSid)))) {
+ // One of the key's SIDs is the current root SID -- user can be authenticated
+ // against that SID.
+ return new UserNotAuthenticatedException();
+ }
+
+ long fingerprintOnlySid = getFingerprintOnlySid();
+ if ((fingerprintOnlySid != 0)
+ && (keySids.contains(KeymasterArguments.toUint64(fingerprintOnlySid)))) {
+ // One of the key's SIDs is the current fingerprint SID -- user can be
+ // authenticated against that SID.
+ return new UserNotAuthenticatedException();
+ }
+
+ // None of the key's SIDs can ever be authenticated
+ return new KeyPermanentlyInvalidatedException();
+ }
default:
return new InvalidKeyException("Keystore operation failed", e);
}
}
+ private long getFingerprintOnlySid() {
+ FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ if (fingerprintManager == null) {
+ return 0;
+ }
+
+ // TODO: Restore USE_FINGERPRINT permission check in
+ // FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
+ return fingerprintManager.getAuthenticatorId();
+ }
+
/**
* Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error
* code.
*/
- static InvalidKeyException getInvalidKeyException(int errorCode) {
- return getInvalidKeyException(getKeyStoreException(errorCode));
+ public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) {
+ return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode));
}
}
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
deleted file mode 100644
index 3b13e83..0000000
--- a/keystore/java/android/security/KeyStoreCipherSpi.java
+++ /dev/null
@@ -1,665 +0,0 @@
-/*
- * 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(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) {
- super(KeymasterDefs.KM_ALGORITHM_AES,
- keymasterBlockMode,
- keymasterPadding,
- 16,
- ivUsed);
- }
-
- public abstract static class ECB extends AES {
- protected ECB(int keymasterPadding) {
- super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
- }
-
- public static class NoPadding extends ECB {
- public NoPadding() {
- super(KeymasterDefs.KM_PAD_NONE);
- }
- }
-
- public static class PKCS7Padding extends ECB {
- public PKCS7Padding() {
- super(KeymasterDefs.KM_PAD_PKCS7);
- }
- }
- }
-
- public abstract static class CBC extends AES {
- protected CBC(int keymasterPadding) {
- super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
- }
-
- public static class NoPadding extends CBC {
- public NoPadding() {
- super(KeymasterDefs.KM_PAD_NONE);
- }
- }
-
- public static class PKCS7Padding extends CBC {
- public PKCS7Padding() {
- super(KeymasterDefs.KM_PAD_PKCS7);
- }
- }
- }
-
- public abstract static class CTR extends AES {
- protected CTR(int keymasterPadding) {
- super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
- }
-
- public static class NoPadding extends CTR {
- public NoPadding() {
- super(KeymasterDefs.KM_PAD_NONE);
- }
- }
- }
- }
-
- private final KeyStore mKeyStore;
- private final int mKeymasterAlgorithm;
- private final int mKeymasterBlockMode;
- private final int mKeymasterPadding;
- private final int mBlockSizeBytes;
-
- /** Whether this transformation requires an IV. */
- private final boolean mIvRequired;
-
- // 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;
- private byte[] mIv;
- /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
- private boolean mIvHasBeenUsed;
-
- // Fields below must be reset after doFinal
- 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;
-
- /**
- * Encountered exception which could not be immediately thrown because it was encountered inside
- * a method that does not throw checked exception. This exception will be thrown from
- * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
- * {@code engineDoFinal} start ignoring input data.
- */
- private Exception mCachedException;
-
- protected KeyStoreCipherSpi(
- int keymasterAlgorithm,
- int keymasterBlockMode,
- int keymasterPadding,
- int blockSizeBytes,
- boolean ivUsed) {
- mKeyStore = KeyStore.getInstance();
- mKeymasterAlgorithm = keymasterAlgorithm;
- mKeymasterBlockMode = keymasterBlockMode;
- mKeymasterPadding = keymasterPadding;
- mBlockSizeBytes = blockSizeBytes;
- mIvRequired = ivUsed;
- }
-
- @Override
- protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- resetAll();
-
- boolean success = false;
- try {
- init(opmode, key, random);
- initAlgorithmSpecificParameters();
- try {
- ensureKeystoreOperationInitialized();
- } catch (InvalidAlgorithmParameterException e) {
- throw new InvalidKeyException(e);
- }
- success = true;
- } finally {
- if (!success) {
- resetAll();
- }
- }
- }
-
- @Override
- protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
- throws InvalidKeyException, InvalidAlgorithmParameterException {
- resetAll();
-
- boolean success = false;
- try {
- init(opmode, key, random);
- initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
- success = true;
- } finally {
- if (!success) {
- resetAll();
- }
- }
- }
-
- @Override
- protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
- SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
- resetAll();
-
- boolean success = false;
- try {
- init(opmode, key, random);
- initAlgorithmSpecificParameters(params);
- ensureKeystoreOperationInitialized();
- success = true;
- } finally {
- if (!success) {
- resetAll();
- }
- }
- }
-
- private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- 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 resetAll() {
- IBinder operationToken = mOperationToken;
- if (operationToken != null) {
- mOperationToken = null;
- mKeyStore.abort(operationToken);
- }
- mEncrypting = false;
- mKey = null;
- mRng = null;
- mFirstOperationInitiated = false;
- mIv = null;
- mIvHasBeenUsed = false;
- mAdditionalEntropyForBegin = null;
- mOperationToken = null;
- mOperationHandle = null;
- mMainDataStreamer = null;
- mCachedException = null;
- }
-
- private void resetWhilePreservingInitState() {
- IBinder operationToken = mOperationToken;
- if (operationToken != null) {
- mOperationToken = null;
- mKeyStore.abort(operationToken);
- }
- mOperationHandle = null;
- mMainDataStreamer = null;
- mAdditionalEntropyForBegin = null;
- mCachedException = null;
- }
-
- private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
- InvalidAlgorithmParameterException {
- if (mMainDataStreamer != null) {
- return;
- }
- if (mCachedException != null) {
- return;
- }
- if (mKey == null) {
- throw new IllegalStateException("Not initialized");
- }
- if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) {
- // IV is being reused for encryption: this violates security best practices.
- throw new IllegalStateException(
- "IV has already been used. Reusing IV in encryption mode violates security best"
- + " practices.");
- }
-
- KeymasterArguments keymasterInputArgs = new KeymasterArguments();
- keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
- keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
- keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
- 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) {
- switch (opResult.resultCode) {
- case KeymasterDefs.KM_ERROR_INVALID_NONCE:
- throw new InvalidAlgorithmParameterException("Invalid IV");
- }
- throw KeyStore.getInvalidKeyException(opResult.resultCode);
- }
-
- if (opResult.token == null) {
- throw new IllegalStateException("Keystore returned null operation token");
- }
- mOperationToken = opResult.token;
- mOperationHandle = opResult.operationHandle;
- loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
- mFirstOperationInitiated = true;
- mIvHasBeenUsed = true;
- mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
- new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
- mKeyStore, opResult.token));
- }
-
- @Override
- protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
- if (mCachedException != null) {
- return null;
- }
- try {
- ensureKeystoreOperationInitialized();
- } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
- mCachedException = e;
- return null;
- }
-
- if (inputLen == 0) {
- return null;
- }
-
- byte[] output;
- try {
- output = mMainDataStreamer.update(input, inputOffset, inputLen);
- } catch (KeyStoreException e) {
- mCachedException = e;
- return null;
- }
-
- 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 {
- if (mCachedException != null) {
- throw (IllegalBlockSizeException)
- new IllegalBlockSizeException().initCause(mCachedException);
- }
-
- try {
- ensureKeystoreOperationInitialized();
- } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
- throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
- }
-
- byte[] output;
- try {
- output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
- } catch (KeyStoreException 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 (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
- }
- }
-
- resetWhilePreservingInitState();
- 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 (!mIvRequired) {
- 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 (!mIvRequired) {
- 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 (!mIvRequired) {
- 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 (!mIvRequired) {
- 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 (mIvRequired) {
- // IV is needed
- if ((mIv == null) && (mEncrypting)) {
- // 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 ((mIvRequired) && (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 (mIvRequired) {
- if (mIv == null) {
- mIv = returnedIv;
- } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
- throw new IllegalStateException("IV in use differs from provided IV");
- }
- } else {
- if (returnedIv != null) {
- throw new IllegalStateException(
- "IV in use despite IV not being used by this transformation");
- }
- }
- }
-}
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
deleted file mode 100644
index dde3b8f..0000000
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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(KeymasterDefs.KM_ALGORITHM_AES, 128);
- }
- }
-
- protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi {
- protected HmacBase(int keymasterDigest) {
- super(KeymasterDefs.KM_ALGORITHM_HMAC,
- keymasterDigest,
- KeymasterUtils.getDigestOutputSizeBytes(keymasterDigest) * 8);
- }
- }
-
- public static class HmacSHA1 extends HmacBase {
- public HmacSHA1() {
- super(KeymasterDefs.KM_DIGEST_SHA1);
- }
- }
-
- public static class HmacSHA224 extends HmacBase {
- public HmacSHA224() {
- super(KeymasterDefs.KM_DIGEST_SHA_2_224);
- }
- }
-
- public static class HmacSHA256 extends HmacBase {
- public HmacSHA256() {
- super(KeymasterDefs.KM_DIGEST_SHA_2_256);
- }
- }
-
- public static class HmacSHA384 extends HmacBase {
- public HmacSHA384() {
- super(KeymasterDefs.KM_DIGEST_SHA_2_384);
- }
- }
-
- public static class HmacSHA512 extends HmacBase {
- public HmacSHA512() {
- super(KeymasterDefs.KM_DIGEST_SHA_2_512);
- }
- }
-
- private final KeyStore mKeyStore = KeyStore.getInstance();
- private final int mKeymasterAlgorithm;
- private final int mKeymasterDigest;
- private final int mDefaultKeySizeBits;
-
- private KeyGeneratorSpec mSpec;
- private SecureRandom mRng;
-
- protected KeyStoreKeyGeneratorSpi(
- int keymasterAlgorithm,
- int defaultKeySizeBits) {
- this(keymasterAlgorithm, -1, defaultKeySizeBits);
- }
-
- protected KeyStoreKeyGeneratorSpi(
- int keymasterAlgorithm,
- int keymasterDigest,
- int defaultKeySizeBits) {
- mKeymasterAlgorithm = keymasterAlgorithm;
- mKeymasterDigest = keymasterDigest;
- 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, mKeymasterAlgorithm);
- if (mKeymasterDigest != -1) {
- args.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
- int digestOutputSizeBytes =
- KeymasterUtils.getDigestOutputSizeBytes(mKeymasterDigest);
- if (digestOutputSizeBytes != -1) {
- // 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 (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
- if (mKeymasterDigest == -1) {
- throw new IllegalStateException("Digest algorithm must be specified for HMAC key");
- }
- }
- int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
- @KeyStoreKeyProperties.PurposeEnum int purposes = spec.getPurposes();
- int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes(
- spec.getBlockModes());
- if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
- && (spec.isRandomizedEncryptionRequired())) {
- for (int keymasterBlockMode : keymasterBlockModes) {
- if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) {
- throw new IllegalStateException(
- "Randomized encryption (IND-CPA) required but may be violated by block"
- + " mode: "
- + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode(
- keymasterBlockMode)
- + ". See KeyGeneratorSpec documentation.");
- }
- }
- }
-
- for (int keymasterPurpose :
- KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) {
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
- }
- args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
- args.addInts(
- KeymasterDefs.KM_TAG_PADDING,
- KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings(
- spec.getEncryptionPaddings()));
- if (spec.getUserAuthenticators() == 0) {
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- } else {
- args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
- KeyStoreKeyProperties.UserAuthenticator.allToKeymaster(
- spec.getUserAuthenticators()));
- }
- if (spec.getUserAuthenticationValidityDurationSeconds() != -1) {
- 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 & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0)
- && (!spec.isRandomizedEncryptionRequired())) {
- // Permit caller-provided IV when encrypting with this key
- 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 new IllegalStateException(
- "Keystore operation failed", KeyStore.getKeyStoreException(errorCode));
- }
- String keyAlgorithmJCA =
- KeymasterUtils.getJcaSecretKeyAlgorithm(mKeymasterAlgorithm, mKeymasterDigest);
- 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/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java
deleted file mode 100644
index 1077af4..0000000
--- a/keystore/java/android/security/KeyStoreKeyProperties.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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 libcore.util.EmptyArray;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-
-/**
- * Properties of {@code AndroidKeyStore} keys.
- *
- * @hide
- */
-public abstract class KeyStoreKeyProperties {
- private KeyStoreKeyProperties() {}
-
- @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;
-
- /**
- * @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(@PurposeEnum int purposes) {
- int[] result = getSetFlags(purposes);
- for (int i = 0; i < result.length; i++) {
- result[i] = toKeymaster(result[i]);
- }
- return result;
- }
-
- /**
- * @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(flag = true,
- value = {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 << 0;
-
- /**
- * @hide
- */
- public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) {
- switch (userAuthenticator) {
- case LOCK_SCREEN:
- return KeymasterDefs.HW_AUTH_PASSWORD;
- default:
- throw new IllegalArgumentException(
- "Unknown user authenticator: " + userAuthenticator);
- }
- }
-
- /**
- * @hide
- */
- public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) {
- switch (userAuthenticator) {
- case KeymasterDefs.HW_AUTH_PASSWORD:
- return LOCK_SCREEN;
- default:
- throw new IllegalArgumentException(
- "Unknown user authenticator: " + userAuthenticator);
- }
- }
-
- /**
- * @hide
- */
- public static int allToKeymaster(@UserAuthenticatorEnum int userAuthenticators) {
- int result = 0;
- int userAuthenticator = 1;
- while (userAuthenticators != 0) {
- if ((userAuthenticators & 1) != 0) {
- result |= toKeymaster(userAuthenticator);
- }
- userAuthenticators >>>= 1;
- userAuthenticator <<= 1;
- }
- return result;
- }
-
- /**
- * @hide
- */
- public static @UserAuthenticatorEnum int allFromKeymaster(int userAuthenticators) {
- @UserAuthenticatorEnum int result = 0;
- int userAuthenticator = 1;
- while (userAuthenticators != 0) {
- if ((userAuthenticators & 1) != 0) {
- result |= fromKeymaster(userAuthenticator);
- }
- userAuthenticators >>>= 1;
- userAuthenticator <<= 1;
- }
- return result;
- }
-
- /**
- * @hide
- */
- public static String toString(@UserAuthenticatorEnum int userAuthenticator) {
- switch (userAuthenticator) {
- case LOCK_SCREEN:
- return "LOCK_SCREEN";
- default:
- throw new IllegalArgumentException(
- "Unknown user authenticator: " + userAuthenticator);
- }
- }
- }
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({Origin.GENERATED, Origin.IMPORTED})
- public @interface OriginEnum {}
-
- /**
- * Origin of the key.
- */
- public static abstract class Origin {
- private Origin() {}
-
- /** Key was generated inside AndroidKeyStore. */
- public static final int GENERATED = 1 << 0;
-
- /** Key was imported into AndroidKeyStore. */
- public static final int IMPORTED = 1 << 1;
-
- /**
- * Origin of the key is unknown. This can occur only for keys backed by an old TEE
- * implementation which does not record origin information.
- *
- * @hide
- */
- public static final int UNKNOWN = 1 << 2;
-
- /**
- * @hide
- */
- public static @OriginEnum int fromKeymaster(int origin) {
- switch (origin) {
- case KeymasterDefs.KM_ORIGIN_GENERATED:
- return GENERATED;
- case KeymasterDefs.KM_ORIGIN_IMPORTED:
- return IMPORTED;
- case KeymasterDefs.KM_ORIGIN_UNKNOWN:
- return UNKNOWN;
- default:
- throw new IllegalArgumentException("Unknown origin: " + origin);
- }
- }
- }
-
- private static int[] getSetFlags(int flags) {
- if (flags == 0) {
- return EmptyArray.INT;
- }
- int result[] = new int[getSetBitCount(flags)];
- int resultOffset = 0;
- int flag = 1;
- while (flags != 0) {
- if ((flags & 1) != 0) {
- result[resultOffset] = flag;
- resultOffset++;
- }
- flags >>>= 1;
- flag <<= 1;
- }
- return result;
- }
-
- private static int getSetBitCount(int value) {
- if (value == 0) {
- return 0;
- }
- int result = 0;
- while (value != 0) {
- if ((value & 1) != 0) {
- result++;
- }
- value >>>= 1;
- }
- return result;
- }
-}
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
deleted file mode 100644
index 861ed34..0000000
--- a/keystore/java/android/security/KeyStoreKeySpec.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.Date;
-
-/**
- * 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 boolean mTeeBacked;
- private final @KeyStoreKeyProperties.OriginEnum int mOrigin;
- private final Date mKeyValidityStart;
- private final Date mKeyValidityForOriginationEnd;
- private final Date mKeyValidityForConsumptionEnd;
- private final @KeyStoreKeyProperties.PurposeEnum int mPurposes;
- private final String[] mEncryptionPaddings;
- private final String[] mSignaturePaddings;
- private final String[] mDigests;
- private final String[] mBlockModes;
- private final @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
- private final @KeyStoreKeyProperties.UserAuthenticatorEnum int mTeeEnforcedUserAuthenticators;
- private final int mUserAuthenticationValidityDurationSeconds;
-
-
- /**
- * @hide
- */
- KeyStoreKeySpec(String keystoreKeyAlias,
- boolean teeBacked,
- @KeyStoreKeyProperties.OriginEnum int origin,
- int keySize,
- Date keyValidityStart,
- Date keyValidityForOriginationEnd,
- Date keyValidityForConsumptionEnd,
- @KeyStoreKeyProperties.PurposeEnum int purposes,
- String[] encryptionPaddings,
- String[] signaturePaddings,
- String[] digests,
- String[] blockModes,
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators,
- @KeyStoreKeyProperties.UserAuthenticatorEnum int teeEnforcedUserAuthenticators,
- int userAuthenticationValidityDurationSeconds) {
- mKeystoreAlias = keystoreKeyAlias;
- mTeeBacked = teeBacked;
- mOrigin = origin;
- mKeySize = keySize;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
- mPurposes = purposes;
- mEncryptionPaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
- mSignaturePaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
- mDigests = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(digests));
- mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
- mUserAuthenticators = userAuthenticators;
- mTeeEnforcedUserAuthenticators = teeEnforcedUserAuthenticators;
- mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
- }
-
- /**
- * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}.
- */
- public String getKeystoreAlias() {
- return mKeystoreAlias;
- }
-
- /**
- * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available
- * in plaintext only inside the TEE.
- */
- public boolean isTeeBacked() {
- return mTeeBacked;
- }
-
- /**
- * Gets the origin of the key.
- */
- public @KeyStoreKeyProperties.OriginEnum int getOrigin() {
- return mOrigin;
- }
-
- /**
- * Gets the size of the key 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 @KeyStoreKeyProperties.PurposeEnum int getPurposes() {
- return mPurposes;
- }
-
- /**
- * Gets the set of block modes with which the key can be used.
- */
- public String[] getBlockModes() {
- return ArrayUtils.cloneIfNotEmpty(mBlockModes);
- }
-
- /**
- * Gets the set of padding modes with which the key can be used when encrypting/decrypting.
- */
- public String[] getEncryptionPaddings() {
- return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
- }
-
- /**
- * Gets the set of padding modes with which the key can be used when signing/verifying.
- */
- public String[] getSignaturePaddings() {
- return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
- }
-
- /**
- * Gets the set of digest algorithms with which the key can be used.
- */
- public String[] getDigests() {
- return ArrayUtils.cloneIfNotEmpty(mDigests);
- }
-
- /**
- * Gets the set of 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 {@code 0} if the key can be used without user authentication.
- */
- public @KeyStoreKeyProperties.UserAuthenticatorEnum int getUserAuthenticators() {
- return mUserAuthenticators;
- }
-
- /**
- * Gets the set of user authenticators for which the TEE enforces access restrictions for this
- * key. This is a subset of the user authentications returned by
- * {@link #getUserAuthenticators()}.
- */
- public @KeyStoreKeyProperties.UserAuthenticatorEnum int getTeeEnforcedUserAuthenticators() {
- return mTeeEnforcedUserAuthenticators;
- }
-
- /**
- * 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 -1} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
- */
- public int getUserAuthenticationValidityDurationSeconds() {
- return mUserAuthenticationValidityDurationSeconds;
- }
-}
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 9fce177..66c87ed 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -16,17 +16,17 @@
package android.security;
+import android.annotation.NonNull;
+import android.app.KeyguardManager;
import android.content.Context;
+import android.security.keystore.KeyProtection;
-import java.security.Key;
+import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
-import java.util.Date;
-
-import javax.crypto.Cipher;
/**
- * Parameters specifying how to secure and restrict the use of a key being
- * imported into the
+ * This provides the optional parameters that can be specified for
+ * {@code KeyStore} entries that work with
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
* facility</a>. The Android KeyStore facility is accessed through a
* {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
@@ -37,53 +37,22 @@ import javax.crypto.Cipher;
* there is only one logical instance of the {@code KeyStore} per application
* UID so apps using the {@code sharedUid} facility will also share a
* {@code KeyStore}.
+ * <p>
+ * Keys may be generated using the {@link KeyPairGenerator} facility with a
+ * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A
+ * self-signed X.509 certificate will be attached to generated entries, but that
+ * may be replaced at a later time by a certificate signed by a real Certificate
+ * Authority.
+ *
+ * @deprecated Use {@link KeyProtection} instead.
*/
+@Deprecated
public final class KeyStoreParameter implements ProtectionParameter {
- private int mFlags;
- private final Date mKeyValidityStart;
- private final Date mKeyValidityForOriginationEnd;
- private final Date mKeyValidityForConsumptionEnd;
- private final @KeyStoreKeyProperties.PurposeEnum int mPurposes;
- private final String[] mEncryptionPaddings;
- private final String[] mSignaturePaddings;
- private final String[] mDigests;
- private final String[] mBlockModes;
- private final boolean mRandomizedEncryptionRequired;
- private final @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
- private final int mUserAuthenticationValidityDurationSeconds;
-
- private KeyStoreParameter(int flags,
- Date keyValidityStart,
- Date keyValidityForOriginationEnd,
- Date keyValidityForConsumptionEnd,
- @KeyStoreKeyProperties.PurposeEnum int purposes,
- String[] encryptionPaddings,
- String[] signaturePaddings,
- String[] digests,
- String[] blockModes,
- boolean randomizedEncryptionRequired,
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators,
- int userAuthenticationValidityDurationSeconds) {
- if ((userAuthenticationValidityDurationSeconds < 0)
- && (userAuthenticationValidityDurationSeconds != -1)) {
- throw new IllegalArgumentException(
- "userAuthenticationValidityDurationSeconds must not be negative");
- }
+ private final int mFlags;
+ private KeyStoreParameter(
+ int flags) {
mFlags = flags;
- mKeyValidityStart = keyValidityStart;
- mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
- mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
- mPurposes = purposes;
- mEncryptionPaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
- mSignaturePaddings =
- ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
- mDigests = ArrayUtils.cloneIfNotEmpty(digests);
- mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
- mRandomizedEncryptionRequired = randomizedEncryptionRequired;
- mUserAuthenticators = userAuthenticators;
- mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
}
/**
@@ -94,148 +63,20 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Returns {@code true} if this parameter requires entries to be encrypted
- * on the disk.
- */
- public boolean isEncryptionRequired() {
- return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
- }
-
- /**
- * 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.
+ * Returns {@code true} if the {@link java.security.KeyStore} entry must be encrypted at rest.
+ * This will protect the entry with the secure lock screen credential (e.g., password, PIN, or
+ * pattern).
*
- * @return instant or {@code null} if not restricted.
+ * <p>Note that encrypting the key at rest requires that the secure lock screen (e.g., password,
+ * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be
+ * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device
+ * Administrator). Finally, this key cannot be used until the user unlocks the secure lock
+ * screen after boot.
*
- * @hide
+ * @see KeyguardManager#isDeviceSecure()
*/
- 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.
- *
- * @hide
- */
- public @KeyStoreKeyProperties.PurposeEnum int getPurposes() {
- return mPurposes;
- }
-
- /**
- * Gets the set of padding schemes with which the key can be used when encrypting/decrypting.
- *
- * @hide
- */
- public String[] getEncryptionPaddings() {
- return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
- }
-
- /**
- * Gets the set of padding schemes with which the key can be used when signing or verifying
- * signatures.
- *
- * @hide
- */
- public String[] getSignaturePaddings() {
- return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
- }
-
- /**
- * Gets the set of digest algorithms with which the key can be used.
- *
- * @throws IllegalStateException if this set has not been specified.
- *
- * @see #isDigestsSpecified()
- *
- * @hide
- */
- public String[] getDigests() {
- if (mDigests == null) {
- throw new IllegalStateException("Digests not specified");
- }
- return ArrayUtils.cloneIfNotEmpty(mDigests);
- }
-
- /**
- * Returns {@code true} if the set of digest algorithms with which the key can be used has been
- * specified.
- *
- * @see #getDigests()
- *
- * @hide
- */
- public boolean isDigestsSpecified() {
- return mDigests != null;
- }
-
- /**
- * Gets the set of block modes with which the key can be used.
- *
- * @hide
- */
- public String[] getBlockModes() {
- return ArrayUtils.cloneIfNotEmpty(mBlockModes);
- }
-
- /**
- * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic property
- * being required is <em>indistinguishability under chosen-plaintext attack ({@code
- * IND-CPA})</em>. This property is important because it mitigates several classes of
- * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
- * given plaintext always produces the same ciphertext, an attacker may see the repeated
- * ciphertexts and be able to deduce something about the plaintext.
- *
- * @hide
- */
- public boolean isRandomizedEncryptionRequired() {
- return mRandomizedEncryptionRequired;
- }
-
- /**
- * Gets the set of 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 {@code 0} if the key can be used without user authentication.
- *
- * @hide
- */
- public @KeyStoreKeyProperties.UserAuthenticatorEnum int getUserAuthenticators() {
- return 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 -1} if not restricted. {@code 0} means authentication
- * is required for every use of the key.
- *
- * @hide
- */
- public int getUserAuthenticationValidityDurationSeconds() {
- return mUserAuthenticationValidityDurationSeconds;
+ public boolean isEncryptionRequired() {
+ return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
}
/**
@@ -254,20 +95,12 @@ public final class KeyStoreParameter implements ProtectionParameter {
* .setEncryptionRequired()
* .build();
* </pre>
+ *
+ * @deprecated Use {@link KeyProtection.Builder} instead.
*/
+ @Deprecated
public final static class Builder {
private int mFlags;
- private Date mKeyValidityStart;
- private Date mKeyValidityForOriginationEnd;
- private Date mKeyValidityForConsumptionEnd;
- private @KeyStoreKeyProperties.PurposeEnum int mPurposes;
- private String[] mEncryptionPaddings;
- private String[] mSignaturePaddings;
- private String[] mDigests;
- private String[] mBlockModes;
- private boolean mRandomizedEncryptionRequired = true;
- private @KeyStoreKeyProperties.UserAuthenticatorEnum int mUserAuthenticators;
- private int mUserAuthenticationValidityDurationSeconds = -1;
/**
* Creates a new instance of the {@code Builder} with the given
@@ -275,20 +108,26 @@ public final class KeyStoreParameter implements ProtectionParameter {
* some UI to ask the user to unlock or initialize the Android KeyStore
* facility.
*/
- public Builder(Context context) {
+ public Builder(@NonNull Context context) {
if (context == null) {
throw new NullPointerException("context == null");
}
-
- // Context is currently not used, but will be in the future.
}
/**
- * 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.
+ * Sets whether this {@link java.security.KeyStore} entry must be encrypted at rest.
+ * Encryption at rest will protect the entry with the secure lock screen credential (e.g.,
+ * password, PIN, or pattern).
+ *
+ * <p>Note that enabling this feature requires that the secure lock screen (e.g., password,
+ * PIN, pattern) is set up, otherwise setting the {@code KeyStore} entry will fail.
+ * Moreover, this entry will be deleted when the secure lock screen is disabled or reset
+ * (e.g., by the user or a Device Administrator). Finally, this entry cannot be used until
+ * the user unlocks the secure lock screen after boot.
+ *
+ * @see KeyguardManager#isDeviceSecure()
*/
+ @NonNull
public Builder setEncryptionRequired(boolean required) {
if (required) {
mFlags |= KeyStore.FLAG_ENCRYPTED;
@@ -299,230 +138,15 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
- * Sets the time instant before which the key is not yet valid.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>By default, the key is valid at any instant.
- *
- * @see #setKeyValidityForOriginationEnd(Date)
- *
- * @hide
- */
- public Builder setKeyValidityForConsumptionEnd(Date endDate) {
- mKeyValidityForConsumptionEnd = endDate;
- return this;
- }
-
- /**
- * Sets the set of purposes for which the key can be used.
- *
- * <p>This must be specified for all keys. There is no default.
- *
- * @hide
- */
- public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) {
- mPurposes = purposes;
- return this;
- }
-
- /**
- * Sets the set of padding schemes with which the key can be used when
- * encrypting/decrypting. Attempts to use the key with any other padding scheme will be
- * rejected.
- *
- * <p>This must be specified for keys which are used for encryption/decryption.
- *
- * @hide
- */
- public Builder setEncryptionPaddings(String... paddings) {
- mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings);
- return this;
- }
-
- /**
- * Sets the set of padding schemes with which the key can be used when
- * signing/verifying. Attempts to use the key with any other padding scheme will be
- * rejected.
- *
- * <p>This must be specified for RSA keys which are used for signing/verification.
- *
- * @hide
- */
- public Builder setSignaturePaddings(String... paddings) {
- mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings);
- return this;
- }
-
-
- /**
- * Sets the set of digests with which the key can be used when signing/verifying or
- * generating MACs. Attempts to use the key with any other digest will be rejected.
- *
- * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For
- * asymmetric signing keys this constraint must be specified.
- *
- * @hide
- */
- public Builder setDigests(String... digests) {
- mDigests = ArrayUtils.cloneIfNotEmpty(digests);
- return this;
- }
-
- /**
- * Sets the set of block modes with which the key can be used when encrypting/decrypting.
- * Attempts to use the key with any other block modes will be rejected.
- *
- * <p>This must be specified for encryption/decryption keys.
- *
- * @hide
- */
- public Builder setBlockModes(String... blockModes) {
- mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes);
- return this;
- }
-
- /**
- * Sets whether encryption using this key must be sufficiently randomized to produce
- * different ciphertexts for the same plaintext every time. The formal cryptographic
- * property being required is <em>indistinguishability under chosen-plaintext attack
- * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
- * of weaknesses due to which ciphertext may leak information about plaintext. For example,
- * if a given plaintext always produces the same ciphertext, an attacker may see the
- * repeated ciphertexts and be able to deduce something about the plaintext.
- *
- * <p>By default, {@code IND-CPA} is required.
- *
- * <p>When {@code IND-CPA} is required:
- * <ul>
- * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
- * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
- * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
- * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
- * encrypting, to ensure that only random IVs are used.</li>
- *
- * <p>Before disabling this requirement, consider the following approaches instead:
- * <ul>
- * <li>If you are generating a random IV for encryption and then initializing a {@code}
- * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
- * instead. This will occur if the {@code Cipher} is initialized for encryption without an
- * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
- * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
- * random, such as the name of the file being encrypted, or transaction ID, or password,
- * or a device identifier), consider changing your design to use a random IV which will then
- * be provided in addition to the ciphertext to the entities which need to decrypt the
- * ciphertext.</li>
- * <li>If you are using RSA encryption without padding, consider switching to padding
- * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
- * </ul>
- *
- * @hide
- */
- public Builder setRandomizedEncryptionRequired(boolean required) {
- mRandomizedEncryptionRequired = required;
- 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 {@code 0} if this key can be accessed
- * without user authentication.
- *
- * @see #setUserAuthenticationValidityDurationSeconds(int)
- *
- * @hide
- */
- public Builder setUserAuthenticators(
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators) {
- mUserAuthenticators = userAuthenticators;
- 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(int)
- *
- * @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 KeyStoreParameter}
*/
+ @NonNull
public KeyStoreParameter build() {
- return new KeyStoreParameter(mFlags,
- mKeyValidityStart,
- mKeyValidityForOriginationEnd,
- mKeyValidityForConsumptionEnd,
- mPurposes,
- mEncryptionPaddings,
- mSignaturePaddings,
- mDigests,
- mBlockModes,
- mRandomizedEncryptionRequired,
- mUserAuthenticators,
- mUserAuthenticationValidityDurationSeconds);
+ return new KeyStoreParameter(
+ mFlags);
}
}
}
diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java
deleted file mode 100644
index 7f0e3d3..0000000
--- a/keystore/java/android/security/KeyStoreSecretKey.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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;
-
-/**
- * {@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/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
deleted file mode 100644
index 33073a4..0000000
--- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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 libcore.util.EmptyArray;
-
-import java.security.InvalidKeyException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-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);
- }
-
- boolean teeBacked;
- @KeyStoreKeyProperties.OriginEnum int origin;
- int keySize;
- @KeyStoreKeyProperties.PurposeEnum int purposes;
- String[] encryptionPaddings;
- String[] digests;
- String[] blockModes;
- @KeyStoreKeyProperties.UserAuthenticatorEnum int userAuthenticators;
- @KeyStoreKeyProperties.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
- try {
- if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
- teeBacked = true;
- origin = KeyStoreKeyProperties.Origin.fromKeymaster(
- keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
- } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
- teeBacked = false;
- origin = KeyStoreKeyProperties.Origin.fromKeymaster(
- keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
- } else {
- throw new InvalidKeySpecException("Key origin not available");
- }
- Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE);
- if (keySizeInteger == null) {
- throw new InvalidKeySpecException("Key size not available");
- }
- keySize = keySizeInteger;
- purposes = KeyStoreKeyProperties.Purpose.allFromKeymaster(
- keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE));
-
- List<String> encryptionPaddingsList = new ArrayList<String>();
- for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) {
- String jcaPadding;
- try {
- jcaPadding = KeymasterUtils.getJcaEncryptionPaddingFromKeymasterPadding(
- keymasterPadding);
- } catch (IllegalArgumentException e) {
- throw new InvalidKeySpecException(
- "Unsupported encryption padding: " + keymasterPadding);
- }
- encryptionPaddingsList.add(jcaPadding);
- }
- encryptionPaddings =
- encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]);
-
- digests = KeymasterUtils.getJcaDigestAlgorithmsFromKeymasterDigests(
- keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST));
- blockModes = KeymasterUtils.getJcaBlockModesFromKeymasterBlockModes(
- keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE));
-
- @KeyStoreKeyProperties.UserAuthenticatorEnum
- int swEnforcedKeymasterUserAuthenticators =
- keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
- @KeyStoreKeyProperties.UserAuthenticatorEnum
- int hwEnforcedKeymasterUserAuthenticators =
- keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
- @KeyStoreKeyProperties.UserAuthenticatorEnum
- int keymasterUserAuthenticators =
- swEnforcedKeymasterUserAuthenticators | hwEnforcedKeymasterUserAuthenticators;
- userAuthenticators = KeyStoreKeyProperties.UserAuthenticator.allFromKeymaster(
- keymasterUserAuthenticators);
- teeEnforcedUserAuthenticators =
- KeyStoreKeyProperties.UserAuthenticator.allFromKeymaster(
- hwEnforcedKeymasterUserAuthenticators);
- } catch (IllegalArgumentException e) {
- throw new InvalidKeySpecException("Unsupported key characteristic", e);
- }
-
- Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
- if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) {
- keyValidityStart = null;
- }
- Date keyValidityForOriginationEnd =
- keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME);
- if ((keyValidityForOriginationEnd != null)
- && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) {
- keyValidityForOriginationEnd = null;
- }
- Date keyValidityForConsumptionEnd =
- keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME);
- if ((keyValidityForConsumptionEnd != null)
- && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) {
- keyValidityForConsumptionEnd = null;
- }
- int userAuthenticationValidityDurationSeconds =
- keyCharacteristics.getInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, -1);
-
- return new KeyStoreKeySpec(entryAlias,
- teeBacked,
- origin,
- keySize,
- keyValidityStart,
- keyValidityForOriginationEnd,
- keyValidityForConsumptionEnd,
- purposes,
- encryptionPaddings,
- EmptyArray.STRING, // no signature paddings -- this is symmetric crypto
- digests,
- blockModes,
- userAuthenticators,
- teeEnforcedUserAuthenticators,
- userAuthenticationValidityDurationSeconds);
- }
-
- @Override
- protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
- throw new UnsupportedOperationException(
- "Key import into Android KeyStore is not supported");
- }
-
- @Override
- protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
- throw new UnsupportedOperationException(
- "Key import into Android KeyStore is not supported");
- }
-}
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
deleted file mode 100644
index 67f75c2..0000000
--- a/keystore/java/android/security/KeymasterUtils.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * 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.KeymasterDefs;
-
-import libcore.util.EmptyArray;
-
-import java.util.Collection;
-import java.util.Locale;
-
-/**
- * @hide
- */
-public abstract class KeymasterUtils {
-
- private KeymasterUtils() {}
-
- public static int getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) {
- if ("AES".equalsIgnoreCase(jcaKeyAlgorithm)) {
- return KeymasterDefs.KM_ALGORITHM_AES;
- } else if (jcaKeyAlgorithm.toUpperCase(Locale.US).startsWith("HMAC")) {
- return KeymasterDefs.KM_ALGORITHM_HMAC;
- } else {
- throw new IllegalArgumentException(
- "Unsupported secret key algorithm: " + jcaKeyAlgorithm);
- }
- }
-
- public static String getJcaSecretKeyAlgorithm(int keymasterAlgorithm, int keymasterDigest) {
- switch (keymasterAlgorithm) {
- case KeymasterDefs.KM_ALGORITHM_AES:
- if (keymasterDigest != -1) {
- throw new IllegalArgumentException(
- "Digest not supported for AES key: " + keymasterDigest);
- }
- return "AES";
- case KeymasterDefs.KM_ALGORITHM_HMAC:
- switch (keymasterDigest) {
- case KeymasterDefs.KM_DIGEST_SHA1:
- return "HmacSHA1";
- case KeymasterDefs.KM_DIGEST_SHA_2_224:
- return "HmacSHA224";
- case KeymasterDefs.KM_DIGEST_SHA_2_256:
- return "HmacSHA256";
- case KeymasterDefs.KM_DIGEST_SHA_2_384:
- return "HmacSHA384";
- case KeymasterDefs.KM_DIGEST_SHA_2_512:
- return "HmacSHA512";
- default:
- throw new IllegalArgumentException(
- "Unsupported HMAC digest: " + keymasterDigest);
- }
- default:
- throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm);
- }
- }
-
- public static String getJcaKeyPairAlgorithmFromKeymasterAlgorithm(int keymasterAlgorithm) {
- switch (keymasterAlgorithm) {
- case KeymasterDefs.KM_ALGORITHM_RSA:
- return "RSA";
- case KeymasterDefs.KM_ALGORITHM_EC:
- return "EC";
- default:
- throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm);
- }
- }
-
- public static int getKeymasterDigestfromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) {
- String algorithmUpper = jcaKeyAlgorithm.toUpperCase(Locale.US);
- if (algorithmUpper.startsWith("HMAC")) {
- String digestUpper = algorithmUpper.substring("HMAC".length());
- switch (digestUpper) {
- case "MD5":
- return KeymasterDefs.KM_DIGEST_MD5;
- case "SHA1":
- return KeymasterDefs.KM_DIGEST_SHA1;
- case "SHA224":
- return KeymasterDefs.KM_DIGEST_SHA_2_224;
- case "SHA256":
- return KeymasterDefs.KM_DIGEST_SHA_2_256;
- case "SHA384":
- return KeymasterDefs.KM_DIGEST_SHA_2_384;
- case "SHA512":
- return KeymasterDefs.KM_DIGEST_SHA_2_512;
- default:
- throw new IllegalArgumentException("Unsupported HMAC digest: " + digestUpper);
- }
- } else {
- return -1;
- }
- }
-
- public static int getKeymasterDigestFromJcaDigestAlgorithm(String jcaDigestAlgorithm) {
- if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-1")) {
- return KeymasterDefs.KM_DIGEST_SHA1;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-224")) {
- return KeymasterDefs.KM_DIGEST_SHA_2_224;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-256")) {
- return KeymasterDefs.KM_DIGEST_SHA_2_256;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-384")) {
- return KeymasterDefs.KM_DIGEST_SHA_2_384;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-512")) {
- return KeymasterDefs.KM_DIGEST_SHA_2_512;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("NONE")) {
- return KeymasterDefs.KM_DIGEST_NONE;
- } else if (jcaDigestAlgorithm.equalsIgnoreCase("MD5")) {
- return KeymasterDefs.KM_DIGEST_MD5;
- } else {
- throw new IllegalArgumentException(
- "Unsupported digest algorithm: " + jcaDigestAlgorithm);
- }
- }
-
- public static String getJcaDigestAlgorithmFromKeymasterDigest(int keymasterDigest) {
- switch (keymasterDigest) {
- case KeymasterDefs.KM_DIGEST_NONE:
- return "NONE";
- case KeymasterDefs.KM_DIGEST_MD5:
- return "MD5";
- case KeymasterDefs.KM_DIGEST_SHA1:
- return "SHA-1";
- case KeymasterDefs.KM_DIGEST_SHA_2_224:
- return "SHA-224";
- case KeymasterDefs.KM_DIGEST_SHA_2_256:
- return "SHA-256";
- case KeymasterDefs.KM_DIGEST_SHA_2_384:
- return "SHA-384";
- case KeymasterDefs.KM_DIGEST_SHA_2_512:
- return "SHA-512";
- default:
- throw new IllegalArgumentException(
- "Unsupported digest algorithm: " + keymasterDigest);
- }
- }
-
- public static String[] getJcaDigestAlgorithmsFromKeymasterDigests(
- Collection<Integer> keymasterDigests) {
- if (keymasterDigests.isEmpty()) {
- return EmptyArray.STRING;
- }
- String[] result = new String[keymasterDigests.size()];
- int offset = 0;
- for (int keymasterDigest : keymasterDigests) {
- result[offset] = getJcaDigestAlgorithmFromKeymasterDigest(keymasterDigest);
- offset++;
- }
- return result;
- }
-
- public static int[] getKeymasterDigestsFromJcaDigestAlgorithms(String[] jcaDigestAlgorithms) {
- if ((jcaDigestAlgorithms == null) || (jcaDigestAlgorithms.length == 0)) {
- return EmptyArray.INT;
- }
- int[] result = new int[jcaDigestAlgorithms.length];
- int offset = 0;
- for (String jcaDigestAlgorithm : jcaDigestAlgorithms) {
- result[offset] = getKeymasterDigestFromJcaDigestAlgorithm(jcaDigestAlgorithm);
- offset++;
- }
- return result;
- }
-
- public static int getDigestOutputSizeBytes(int keymasterDigest) {
- switch (keymasterDigest) {
- case KeymasterDefs.KM_DIGEST_NONE:
- return -1;
- case KeymasterDefs.KM_DIGEST_MD5:
- return 128 / 8;
- case KeymasterDefs.KM_DIGEST_SHA1:
- return 160 / 8;
- case KeymasterDefs.KM_DIGEST_SHA_2_224:
- return 224 / 8;
- case KeymasterDefs.KM_DIGEST_SHA_2_256:
- return 256 / 8;
- case KeymasterDefs.KM_DIGEST_SHA_2_384:
- return 384 / 8;
- case KeymasterDefs.KM_DIGEST_SHA_2_512:
- return 512 / 8;
- default:
- throw new IllegalArgumentException("Unknown digest: " + keymasterDigest);
- }
- }
-
- public static int getKeymasterBlockModeFromJcaBlockMode(String jcaBlockMode) {
- if ("ECB".equalsIgnoreCase(jcaBlockMode)) {
- return KeymasterDefs.KM_MODE_ECB;
- } else if ("CBC".equalsIgnoreCase(jcaBlockMode)) {
- return KeymasterDefs.KM_MODE_CBC;
- } else if ("CTR".equalsIgnoreCase(jcaBlockMode)) {
- return KeymasterDefs.KM_MODE_CTR;
- } else if ("GCM".equalsIgnoreCase(jcaBlockMode)) {
- return KeymasterDefs.KM_MODE_GCM;
- } else {
- throw new IllegalArgumentException("Unsupported block mode: " + jcaBlockMode);
- }
- }
-
- public static String getJcaBlockModeFromKeymasterBlockMode(int keymasterBlockMode) {
- switch (keymasterBlockMode) {
- case KeymasterDefs.KM_MODE_ECB:
- return "ECB";
- case KeymasterDefs.KM_MODE_CBC:
- return "CBC";
- case KeymasterDefs.KM_MODE_CTR:
- return "CTR";
- case KeymasterDefs.KM_MODE_GCM:
- return "GCM";
- default:
- throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode);
- }
- }
-
- public static String[] getJcaBlockModesFromKeymasterBlockModes(
- Collection<Integer> keymasterBlockModes) {
- if ((keymasterBlockModes == null) || (keymasterBlockModes.isEmpty())) {
- return EmptyArray.STRING;
- }
- String[] result = new String[keymasterBlockModes.size()];
- int offset = 0;
- for (int keymasterBlockMode : keymasterBlockModes) {
- result[offset] = getJcaBlockModeFromKeymasterBlockMode(keymasterBlockMode);
- offset++;
- }
- return result;
- }
-
- public static int[] getKeymasterBlockModesFromJcaBlockModes(String[] jcaBlockModes) {
- if ((jcaBlockModes == null) || (jcaBlockModes.length == 0)) {
- return EmptyArray.INT;
- }
- int[] result = new int[jcaBlockModes.length];
- for (int i = 0; i < jcaBlockModes.length; i++) {
- result[i] = getKeymasterBlockModeFromJcaBlockMode(jcaBlockModes[i]);
- }
- return result;
- }
-
- public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) {
- switch (keymasterBlockMode) {
- case KeymasterDefs.KM_MODE_ECB:
- return false;
- case KeymasterDefs.KM_MODE_CBC:
- case KeymasterDefs.KM_MODE_CTR:
- case KeymasterDefs.KM_MODE_GCM:
- return true;
- default:
- throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode);
- }
- }
-
- public static int getKeymasterPaddingFromJcaEncryptionPadding(String jcaPadding) {
- if ("NoPadding".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_NONE;
- } else if ("PKCS7Padding".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_PKCS7;
- } else if ("PKCS1Padding".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
- } else if ("OEAPPadding".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_RSA_OAEP;
- } else {
- throw new IllegalArgumentException(
- "Unsupported encryption padding scheme: " + jcaPadding);
- }
- }
-
- public static String getJcaEncryptionPaddingFromKeymasterPadding(int keymasterPadding) {
- switch (keymasterPadding) {
- case KeymasterDefs.KM_PAD_NONE:
- return "NoPadding";
- case KeymasterDefs.KM_PAD_PKCS7:
- return "PKCS7Padding";
- case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
- return "PKCS1Padding";
- case KeymasterDefs.KM_PAD_RSA_OAEP:
- return "OEAPPadding";
- default:
- throw new IllegalArgumentException(
- "Unsupported encryption padding: " + keymasterPadding);
- }
- }
-
- public static int getKeymasterPaddingFromJcaSignaturePadding(String jcaPadding) {
- if ("PKCS#1".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
- } if ("PSS".equalsIgnoreCase(jcaPadding)) {
- return KeymasterDefs.KM_PAD_RSA_PSS;
- } else {
- throw new IllegalArgumentException(
- "Unsupported signature padding scheme: " + jcaPadding);
- }
- }
-
- public static String getJcaSignaturePaddingFromKeymasterPadding(int keymasterPadding) {
- switch (keymasterPadding) {
- case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
- return "PKCS#1";
- case KeymasterDefs.KM_PAD_RSA_PSS:
- return "PSS";
- default:
- throw new IllegalArgumentException(
- "Unsupported signature padding: " + keymasterPadding);
- }
- }
-
- public static int[] getKeymasterPaddingsFromJcaEncryptionPaddings(String[] jcaPaddings) {
- if ((jcaPaddings == null) || (jcaPaddings.length == 0)) {
- return EmptyArray.INT;
- }
- int[] result = new int[jcaPaddings.length];
- for (int i = 0; i < jcaPaddings.length; i++) {
- result[i] = getKeymasterPaddingFromJcaEncryptionPadding(jcaPaddings[i]);
- }
- return result;
- }
-
- public static int[] getKeymasterPaddingsFromJcaSignaturePaddings(String[] jcaPaddings) {
- if ((jcaPaddings == null) || (jcaPaddings.length == 0)) {
- return EmptyArray.INT;
- }
- int[] result = new int[jcaPaddings.length];
- for (int i = 0; i < jcaPaddings.length; i++) {
- result[i] = getKeymasterPaddingFromJcaSignaturePadding(jcaPaddings[i]);
- }
- return result;
- }
-}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java
new file mode 100644
index 0000000..6411066
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java
@@ -0,0 +1,443 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
+ private static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
+ private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
+ private static final int DEFAULT_TAG_LENGTH_BITS = 128;
+ private static final int IV_LENGTH_BYTES = 12;
+
+ private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
+
+ GCM(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
+ }
+
+ @Override
+ protected final void resetAll() {
+ mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!isEncrypting()) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException(
+ "GCMParameterSpec must be provided when decrypting");
+ }
+ return;
+ }
+ if (!(params instanceof GCMParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
+ }
+ GCMParameterSpec spec = (GCMParameterSpec) params;
+ byte[] iv = spec.getIV();
+ if (iv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
+ } else if (iv.length != IV_LENGTH_BYTES) {
+ throw new InvalidAlgorithmParameterException("Unsupported IV length: "
+ + iv.length + " bytes. Only " + IV_LENGTH_BYTES
+ + " bytes long IV supported");
+ }
+ int tagLengthBits = spec.getTLen();
+ if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
+ || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
+ || ((tagLengthBits % 8) != 0)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported tag length: " + tagLengthBits + " bits"
+ + ". Supported lengths: 96, 104, 112, 120, 128");
+ }
+ setIv(iv);
+ mTagLengthBits = tagLengthBits;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
+ }
+ return;
+ }
+
+ GCMParameterSpec spec;
+ try {
+ spec = params.getParameterSpec(GCMParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ if (!isEncrypting()) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV and tag length required when"
+ + " decrypting, but not found in parameters: " + params, e);
+ }
+ setIv(null);
+ return;
+ }
+ initAlgorithmSpecificParameters(spec);
+ }
+
+ @Nullable
+ @Override
+ protected final AlgorithmParameters engineGetParameters() {
+ byte[] iv = getIv();
+ if ((iv != null) && (iv.length > 0)) {
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
+ params.init(new GCMParameterSpec(mTagLengthBits, iv));
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain GCM AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize GCM AlgorithmParameters", e);
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ keyStore, operationToken));
+ if (isEncrypting()) {
+ return streamer;
+ } else {
+ // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
+ // plaintext before ciphertext is authenticated by KeyStore.finish.
+ return new BufferAllOutputUntilDoFinalStreamer(streamer);
+ }
+ }
+
+ @NonNull
+ @Override
+ protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new AdditionalAuthenticationDataStream(keyStore, operationToken));
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ if ((getIv() == null) && (isEncrypting())) {
+ // IV will need to be generated
+ return IV_LENGTH_BYTES;
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
+ keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
+ }
+
+ protected final int getTagLengthBits() {
+ return mTagLengthBits;
+ }
+
+ public static final class NoPadding extends GCM {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+
+ @Override
+ protected final int engineGetOutputSize(int inputLen) {
+ int tagLengthBytes = (getTagLengthBits() + 7) / 8;
+ long result;
+ if (isEncrypting()) {
+ result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ + tagLengthBytes;
+ } else {
+ result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ - tagLengthBytes;
+ }
+ if (result < 0) {
+ return 0;
+ } else if (result > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) result;
+ }
+ }
+ }
+
+ private static final int BLOCK_SIZE_BYTES = 16;
+
+ private final int mKeymasterBlockMode;
+ private final int mKeymasterPadding;
+
+ private byte[] mIv;
+
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ AndroidKeyStoreAuthenticatedAESCipherSpi(
+ int keymasterBlockMode,
+ int keymasterPadding) {
+ mKeymasterBlockMode = keymasterBlockMode;
+ mKeymasterPadding = keymasterPadding;
+ }
+
+ @Override
+ protected void resetAll() {
+ mIv = null;
+ mIvHasBeenUsed = false;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+ if (!(key instanceof AndroidKeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException(
+ "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
+ KeyProperties.KEY_ALGORITHM_AES + " supported");
+ }
+ setKey((AndroidKeyStoreSecretKey) key);
+ }
+
+ @Override
+ protected void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ if ((isEncrypting()) && (mIvHasBeenUsed)) {
+ // IV is being reused for encryption: this violates security best practices.
+ throw new IllegalStateException(
+ "IV has already been used. Reusing IV in encryption mode violates security best"
+ + " practices.");
+ }
+
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ if (mIv != null) {
+ keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
+ if ((returnedIv != null) && (returnedIv.length == 0)) {
+ returnedIv = null;
+ }
+
+ if (mIv == null) {
+ mIv = returnedIv;
+ } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+ throw new ProviderException("IV in use differs from provided IV");
+ }
+ }
+
+ @Override
+ protected final int engineGetBlockSize() {
+ return BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ return ArrayUtils.cloneIfNotEmpty(mIv);
+ }
+
+ protected void setIv(byte[] iv) {
+ mIv = iv;
+ }
+
+ protected byte[] getIv() {
+ return mIv;
+ }
+
+ /**
+ * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
+ * which it returns all output in one go, provided {@code doFinal} succeeds.
+ */
+ private static class BufferAllOutputUntilDoFinalStreamer
+ implements KeyStoreCryptoOperationStreamer {
+
+ private final KeyStoreCryptoOperationStreamer mDelegate;
+ private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
+ private long mProducedOutputSizeBytes;
+
+ private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public byte[] update(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ byte[] output = mDelegate.update(input, inputOffset, inputLength);
+ if (output != null) {
+ try {
+ mBufferedOutput.write(output);
+ } catch (IOException e) {
+ throw new ProviderException("Failed to buffer output", e);
+ }
+ }
+ return EmptyArray.BYTE;
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
+ byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
+ additionalEntropy);
+ if (output != null) {
+ try {
+ mBufferedOutput.write(output);
+ } catch (IOException e) {
+ throw new ProviderException("Failed to buffer output", e);
+ }
+ }
+ byte[] result = mBufferedOutput.toByteArray();
+ mBufferedOutput.reset();
+ mProducedOutputSizeBytes += result.length;
+ return result;
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mDelegate.getConsumedInputSizeBytes();
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mProducedOutputSizeBytes;
+ }
+ }
+
+ /**
+ * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
+ * sends AAD into the KeyStore.
+ */
+ private static class AdditionalAuthenticationDataStream implements Stream {
+
+ private final KeyStore mKeyStore;
+ private final IBinder mOperationToken;
+
+ private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
+ mKeyStore = keyStore;
+ mOperationToken = operationToken;
+ }
+
+ @Override
+ public OperationResult update(byte[] input) {
+ KeymasterArguments keymasterArgs = new KeymasterArguments();
+ keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
+
+ // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
+ // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
+ // has been consumed if the method succeeds.
+ OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
+ if (result.resultCode == KeyStore.NO_ERROR) {
+ result = new OperationResult(
+ result.resultCode,
+ result.token,
+ result.operationHandle,
+ input.length, // inputConsumed
+ result.output,
+ result.outParams);
+ }
+ return result;
+ }
+
+ @Override
+ public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
+ if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
+ throw new ProviderException("AAD stream does not support additional entropy");
+ }
+ return new OperationResult(
+ KeyStore.NO_ERROR,
+ mOperationToken,
+ 0, // operation handle -- nobody cares about this being returned from finish
+ 0, // inputConsumed
+ EmptyArray.BYTE, // output
+ new KeymasterArguments() // additional params returned by finish
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
new file mode 100644
index 0000000..156f45f
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -0,0 +1,259 @@
+/*
+ * 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.keystore;
+
+import java.security.Provider;
+
+/**
+ * {@link Provider} of JCA crypto operations operating on Android KeyStore keys.
+ *
+ * <p>This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue
+ * that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android
+ * KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto
+ * operations for Android KeyStore keys unless Android KeyStore's own implementations are installed
+ * as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to
+ * offer crypto operations operating on Android KeyStore keys and to be installed at higher priority
+ * than the Bouncy Castle provider.
+ *
+ * <p>Once Bouncy Castle provider is fixed, this provider can be merged into the
+ * {@code AndroidKeyStoreProvider}.
+ *
+ * @hide
+ */
+class AndroidKeyStoreBCWorkaroundProvider extends Provider {
+
+ // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
+ // classes when this provider is instantiated and installed early on during each app's
+ // initialization process.
+
+ private static final String PACKAGE_NAME = "android.security.keystore";
+ private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".AndroidKeyStoreSecretKey";
+ private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".AndroidKeyStorePrivateKey";
+ private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".AndroidKeyStorePublicKey";
+
+ AndroidKeyStoreBCWorkaroundProvider() {
+ super("AndroidKeyStoreBCWorkaround",
+ 1.0,
+ "Android KeyStore security provider to work around Bouncy Castle");
+
+ // --------------------- javax.crypto.Mac
+ putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1");
+ put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1");
+ put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1");
+ put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1");
+
+ putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224");
+ put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA224");
+ put("Alg.Alias.Mac.HMAC-SHA224", "HmacSHA224");
+ put("Alg.Alias.Mac.HMAC/SHA224", "HmacSHA224");
+
+ putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256");
+ put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256");
+ put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256");
+ put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256");
+
+ putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384");
+ put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384");
+ put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384");
+ put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384");
+
+ putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512");
+ put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512");
+ put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512");
+ put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
+
+ // --------------------- javax.crypto.Cipher
+ putSymmetricCipherImpl("AES/ECB/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
+ putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CBC/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding");
+ putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CTR/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
+
+ putSymmetricCipherImpl("AES/GCM/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");
+
+ putAsymmetricCipherImpl("RSA/ECB/NoPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
+ put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
+ putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding");
+ put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPPadding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-384AndMGF1Padding");
+ putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding",
+ PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding");
+ put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding",
+ "RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
+
+ // --------------------- java.security.Signature
+ putSignatureImpl("NONEwithRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$NONEWithPKCS1Padding");
+
+ putSignatureImpl("MD5withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$MD5WithPKCS1Padding");
+ put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5withRSA");
+ put("Alg.Alias.Signature.MD5/RSA", "MD5withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5withRSA");
+
+ putSignatureImpl("SHA1withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPKCS1Padding");
+ put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1withRSA");
+ put("Alg.Alias.Signature.SHA1/RSA", "SHA1withRSA");
+ put("Alg.Alias.Signature.SHA-1/RSA", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1withRSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1withRSA");
+
+ putSignatureImpl("SHA224withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPKCS1Padding");
+ put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1",
+ "SHA224withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11",
+ "SHA224withRSA");
+
+ putSignatureImpl("SHA256withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPKCS1Padding");
+ put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1",
+ "SHA256withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11",
+ "SHA256withRSA");
+
+ putSignatureImpl("SHA384withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPKCS1Padding");
+ put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1",
+ "SHA384withRSA");
+
+ putSignatureImpl("SHA512withRSA",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPKCS1Padding");
+ put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512withRSA");
+ put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512withRSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1",
+ "SHA512withRSA");
+
+ putSignatureImpl("SHA1withRSA/PSS",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding");
+ putSignatureImpl("SHA224withRSA/PSS",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPSSPadding");
+ putSignatureImpl("SHA256withRSA/PSS",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPSSPadding");
+ putSignatureImpl("SHA384withRSA/PSS",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPSSPadding");
+ putSignatureImpl("SHA512withRSA/PSS",
+ PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPSSPadding");
+
+ putSignatureImpl("NONEwithECDSA",
+ PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE");
+
+ putSignatureImpl("ECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1");
+ put("Alg.Alias.Signature.SHA1withECDSA", "ECDSA");
+ put("Alg.Alias.Signature.ECDSAwithSHA1", "ECDSA");
+ // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1)
+ put("Alg.Alias.Signature.1.2.840.10045.4.1", "ECDSA");
+ put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "ECDSA");
+
+ // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
+ putSignatureImpl("SHA224withECDSA",
+ PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA224");
+ // ecdsa-with-SHA224(1)
+ put("Alg.Alias.Signature.1.2.840.10045.4.3.1", "SHA224withECDSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.10045.2.1", "SHA224withECDSA");
+
+ // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
+ putSignatureImpl("SHA256withECDSA",
+ PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA256");
+ // ecdsa-with-SHA256(2)
+ put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA");
+
+ putSignatureImpl("SHA384withECDSA",
+ PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA384");
+ // ecdsa-with-SHA384(3)
+ put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA");
+
+ putSignatureImpl("SHA512withECDSA",
+ PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA512");
+ // ecdsa-with-SHA512(4)
+ put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA");
+ }
+
+ private void putMacImpl(String algorithm, String implClass) {
+ put("Mac." + algorithm, implClass);
+ put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ private void putSymmetricCipherImpl(String transformation, String implClass) {
+ put("Cipher." + transformation, implClass);
+ put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ private void putAsymmetricCipherImpl(String transformation, String implClass) {
+ put("Cipher." + transformation, implClass);
+ put("Cipher." + transformation + " SupportedKeyClasses",
+ KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
+ }
+
+ private void putSignatureImpl(String algorithm, String implClass) {
+ put("Signature." + algorithm, implClass);
+ put("Signature." + algorithm + " SupportedKeyClasses",
+ KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
+ }
+
+ public static String[] getSupportedEcdsaSignatureDigests() {
+ return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
+ }
+
+ public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() {
+ return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
new file mode 100644
index 0000000..38cacd0
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -0,0 +1,856 @@
+/*
+ * 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.keystore;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import libcore.util.EmptyArray;
+
+import java.nio.ByteBuffer;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+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.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
+ private final KeyStore mKeyStore;
+
+ // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
+ // doFinal finishes.
+ private boolean mEncrypting;
+ private int mKeymasterPurposeOverride = -1;
+ private AndroidKeyStoreKey mKey;
+ private SecureRandom mRng;
+
+ /**
+ * Token referencing this operation inside keystore service. It is initialized by
+ * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error
+ * conditions in between.
+ */
+ private IBinder mOperationToken;
+ private long mOperationHandle;
+ private KeyStoreCryptoOperationStreamer mMainDataStreamer;
+ private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
+ private boolean mAdditionalAuthenticationDataStreamerClosed;
+
+ /**
+ * Encountered exception which could not be immediately thrown because it was encountered inside
+ * a method that does not throw checked exception. This exception will be thrown from
+ * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
+ * {@code engineDoFinal} start ignoring input data.
+ */
+ private Exception mCachedException;
+
+ AndroidKeyStoreCipherSpiBase() {
+ mKeyStore = KeyStore.getInstance();
+ }
+
+ @Override
+ protected final void engineInit(int opmode, Key key, SecureRandom random)
+ throws InvalidKeyException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters();
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new InvalidKeyException(e);
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ @Override
+ protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
+ SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ @Override
+ protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ switch (opmode) {
+ case Cipher.ENCRYPT_MODE:
+ case Cipher.WRAP_MODE:
+ mEncrypting = true;
+ break;
+ case Cipher.DECRYPT_MODE:
+ case Cipher.UNWRAP_MODE:
+ mEncrypting = false;
+ break;
+ default:
+ throw new InvalidParameterException("Unsupported opmode: " + opmode);
+ }
+ initKey(opmode, key);
+ if (mKey == null) {
+ throw new ProviderException("initKey did not initialize the key");
+ }
+ mRng = random;
+ }
+
+ /**
+ * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
+ * cipher instance.
+ *
+ * <p>Subclasses storing additional state should override this method, reset the additional
+ * state, and then chain to superclass.
+ */
+ @CallSuper
+ protected void resetAll() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ mEncrypting = false;
+ mKeymasterPurposeOverride = -1;
+ mKey = null;
+ mRng = null;
+ mOperationToken = null;
+ mOperationHandle = 0;
+ mMainDataStreamer = null;
+ mAdditionalAuthenticationDataStreamer = null;
+ mAdditionalAuthenticationDataStreamerClosed = false;
+ mCachedException = null;
+ }
+
+ /**
+ * Resets this cipher while preserving the initialized state. This must be equivalent to
+ * rolling back the cipher's state to just after the most recent {@code engineInit} completed
+ * successfully.
+ *
+ * <p>Subclasses storing additional post-init state should override this method, reset the
+ * additional state, and then chain to superclass.
+ */
+ @CallSuper
+ protected void resetWhilePreservingInitState() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ mOperationToken = null;
+ mOperationHandle = 0;
+ mMainDataStreamer = null;
+ mAdditionalAuthenticationDataStreamer = null;
+ mAdditionalAuthenticationDataStreamerClosed = false;
+ mCachedException = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
+ InvalidAlgorithmParameterException {
+ if (mMainDataStreamer != null) {
+ return;
+ }
+ if (mCachedException != null) {
+ return;
+ }
+ if (mKey == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ KeymasterArguments keymasterInputArgs = new KeymasterArguments();
+ addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
+ byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, getAdditionalEntropyAmountForBegin());
+
+ int purpose;
+ if (mKeymasterPurposeOverride != -1) {
+ purpose = mKeymasterPurposeOverride;
+ } else {
+ purpose = mEncrypting
+ ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
+ }
+ OperationResult opResult = mKeyStore.begin(
+ mKey.getAlias(),
+ purpose,
+ true, // permit aborting this operation if keystore runs out of resources
+ keymasterInputArgs,
+ additionalEntropy);
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ }
+
+ // Store operation token and handle regardless of the error code returned by KeyStore to
+ // ensure that the operation gets aborted immediately if the code below throws an exception.
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
+
+ // If necessary, throw an exception due to KeyStore operation having failed.
+ GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
+ mKeyStore, mKey, opResult.resultCode);
+ if (e != null) {
+ if (e instanceof InvalidKeyException) {
+ throw (InvalidKeyException) e;
+ } else if (e instanceof InvalidAlgorithmParameterException) {
+ throw (InvalidAlgorithmParameterException) e;
+ } else {
+ throw new ProviderException("Unexpected exception type", e);
+ }
+ }
+
+ if (mOperationToken == null) {
+ throw new ProviderException("Keystore returned null operation token");
+ }
+ if (mOperationHandle == 0) {
+ throw new ProviderException("Keystore returned invalid operation handle");
+ }
+
+ loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
+ mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
+ mAdditionalAuthenticationDataStreamer =
+ createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
+ mAdditionalAuthenticationDataStreamerClosed = false;
+ }
+
+ /**
+ * Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives
+ * the corresponding ciphertext/plaintext from the KeyStore.
+ *
+ * <p>This implementation returns a working streamer.
+ */
+ @NonNull
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ keyStore, operationToken));
+ }
+
+ /**
+ * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.
+ *
+ * <p>This implementation returns {@code null}.
+ *
+ * @returns stream or {@code null} if AAD is not supported by this cipher.
+ */
+ @Nullable
+ protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
+ @SuppressWarnings("unused") KeyStore keyStore,
+ @SuppressWarnings("unused") IBinder operationToken) {
+ return null;
+ }
+
+ @Override
+ protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+ if (mCachedException != null) {
+ return null;
+ }
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ mCachedException = e;
+ return null;
+ }
+
+ if (inputLen == 0) {
+ return null;
+ }
+
+ byte[] output;
+ try {
+ flushAAD();
+ output = mMainDataStreamer.update(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ mCachedException = e;
+ return null;
+ }
+
+ if (output.length == 0) {
+ return null;
+ }
+
+ return output;
+ }
+
+ private void flushAAD() throws KeyStoreException {
+ if ((mAdditionalAuthenticationDataStreamer != null)
+ && (!mAdditionalAuthenticationDataStreamerClosed)) {
+ byte[] output;
+ try {
+ output = mAdditionalAuthenticationDataStreamer.doFinal(
+ EmptyArray.BYTE, 0, 0,
+ null, // no signature
+ null // no additional entropy needed flushing AAD
+ );
+ } finally {
+ mAdditionalAuthenticationDataStreamerClosed = true;
+ }
+ if ((output != null) && (output.length > 0)) {
+ throw new ProviderException(
+ "AAD update unexpectedly returned data: " + output.length + " bytes");
+ }
+ }
+ }
+
+ @Override
+ protected final 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 final int engineUpdate(ByteBuffer input, ByteBuffer output)
+ throws ShortBufferException {
+ return super.engineUpdate(input, output);
+ }
+
+ @Override
+ protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
+ if (mCachedException != null) {
+ return;
+ }
+
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ mCachedException = e;
+ return;
+ }
+
+ if (mAdditionalAuthenticationDataStreamerClosed) {
+ throw new IllegalStateException(
+ "AAD can only be provided before Cipher.update is invoked");
+ }
+
+ if (mAdditionalAuthenticationDataStreamer == null) {
+ throw new IllegalStateException("This cipher does not support AAD");
+ }
+
+ byte[] output;
+ try {
+ output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ mCachedException = e;
+ return;
+ }
+
+ if ((output != null) && (output.length > 0)) {
+ throw new ProviderException("AAD update unexpectedly produced output: "
+ + output.length + " bytes");
+ }
+ }
+
+ @Override
+ protected final void engineUpdateAAD(ByteBuffer src) {
+ if (src == null) {
+ throw new IllegalArgumentException("src == null");
+ }
+ if (!src.hasRemaining()) {
+ return;
+ }
+
+ byte[] input;
+ int inputOffset;
+ int inputLen;
+ if (src.hasArray()) {
+ input = src.array();
+ inputOffset = src.arrayOffset() + src.position();
+ inputLen = src.remaining();
+ src.position(src.limit());
+ } else {
+ input = new byte[src.remaining()];
+ inputOffset = 0;
+ inputLen = input.length;
+ src.get(input);
+ }
+ engineUpdateAAD(input, inputOffset, inputLen);
+ }
+
+ @Override
+ protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
+ throws IllegalBlockSizeException, BadPaddingException {
+ if (mCachedException != null) {
+ throw (IllegalBlockSizeException)
+ new IllegalBlockSizeException().initCause(mCachedException);
+ }
+
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+ }
+
+ byte[] output;
+ try {
+ flushAAD();
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, getAdditionalEntropyAmountForFinish());
+ output = mMainDataStreamer.doFinal(
+ input, inputOffset, inputLen,
+ null, // no signature involved
+ additionalEntropy);
+ } catch (KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
+ throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+ case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
+ throw (BadPaddingException) new BadPaddingException().initCause(e);
+ case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
+ throw (AEADBadTagException) new AEADBadTagException().initCause(e);
+ default:
+ throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+ }
+ }
+
+ resetWhilePreservingInitState();
+ return output;
+ }
+
+ @Override
+ protected final 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 final int engineDoFinal(ByteBuffer input, ByteBuffer output)
+ throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+ return super.engineDoFinal(input, output);
+ }
+
+ @Override
+ protected final byte[] engineWrap(Key key)
+ throws IllegalBlockSizeException, InvalidKeyException {
+ if (mKey == null) {
+ throw new IllegalStateException("Not initilized");
+ }
+
+ if (!isEncrypting()) {
+ throw new IllegalStateException(
+ "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
+ }
+
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ byte[] encoded = null;
+ if (key instanceof SecretKey) {
+ if ("RAW".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
+ SecretKeySpec spec =
+ (SecretKeySpec) keyFactory.getKeySpec(
+ (SecretKey) key, SecretKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else if (key instanceof PrivateKey) {
+ if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
+ PKCS8EncodedKeySpec spec =
+ keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else if (key instanceof PublicKey) {
+ if ("X.509".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
+ X509EncodedKeySpec spec =
+ keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else {
+ throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
+ }
+
+ if (encoded == null) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material");
+ }
+
+ try {
+ return engineDoFinal(encoded, 0, encoded.length);
+ } catch (BadPaddingException e) {
+ throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+ }
+ }
+
+ @Override
+ protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
+ int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
+ if (mKey == null) {
+ throw new IllegalStateException("Not initilized");
+ }
+
+ if (isEncrypting()) {
+ throw new IllegalStateException(
+ "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
+ }
+
+ if (wrappedKey == null) {
+ throw new NullPointerException("wrappedKey == null");
+ }
+
+ byte[] encoded;
+ try {
+ encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new InvalidKeyException("Failed to unwrap key", e);
+ }
+
+ switch (wrappedKeyType) {
+ case Cipher.SECRET_KEY:
+ {
+ return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+ // break;
+ }
+ case Cipher.PRIVATE_KEY:
+ {
+ KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+ try {
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to create private key from its PKCS#8 encoded form", e);
+ }
+ // break;
+ }
+ case Cipher.PUBLIC_KEY:
+ {
+ KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+ try {
+ return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to create public key from its X.509 encoded form", e);
+ }
+ // break;
+ }
+ default:
+ throw new InvalidParameterException(
+ "Unsupported wrappedKeyType: " + wrappedKeyType);
+ }
+ }
+
+ @Override
+ protected final 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 final 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
+ protected final int engineGetKeySize(Key key) throws InvalidKeyException {
+ throw new UnsupportedOperationException();
+ }
+
+ @CallSuper
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public final long getOperationHandle() {
+ return mOperationHandle;
+ }
+
+ protected final void setKey(@NonNull AndroidKeyStoreKey key) {
+ mKey = key;
+ }
+
+ /**
+ * Overrides the default purpose/type of the crypto operation.
+ */
+ protected final void setKeymasterPurposeOverride(int keymasterPurpose) {
+ mKeymasterPurposeOverride = keymasterPurpose;
+ }
+
+ protected final int getKeymasterPurposeOverride() {
+ return mKeymasterPurposeOverride;
+ }
+
+ /**
+ * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this
+ * cipher is initialized for decryption.
+ */
+ protected final boolean isEncrypting() {
+ return mEncrypting;
+ }
+
+ @NonNull
+ protected final KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ protected final long getConsumedInputSizeBytes() {
+ if (mMainDataStreamer == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mMainDataStreamer.getConsumedInputSizeBytes();
+ }
+
+ protected final long getProducedOutputSizeBytes() {
+ if (mMainDataStreamer == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mMainDataStreamer.getProducedOutputSizeBytes();
+ }
+
+ static String opmodeToString(int opmode) {
+ switch (opmode) {
+ case Cipher.ENCRYPT_MODE:
+ return "ENCRYPT_MODE";
+ case Cipher.DECRYPT_MODE:
+ return "DECRYPT_MODE";
+ case Cipher.WRAP_MODE:
+ return "WRAP_MODE";
+ case Cipher.UNWRAP_MODE:
+ return "UNWRAP_MODE";
+ default:
+ return String.valueOf(opmode);
+ }
+ }
+
+ // The methods below need to be implemented by subclasses.
+
+ /**
+ * Initializes this cipher with the provided key.
+ *
+ * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the
+ * specified {@code opmode}.
+ *
+ * @see #setKey(AndroidKeyStoreKey)
+ */
+ protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
+
+ /**
+ * Returns algorithm-specific parameters used by this cipher or {@code null} if no
+ * algorithm-specific parameters are used.
+ */
+ @Nullable
+ @Override
+ protected abstract AlgorithmParameters engineGetParameters();
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional
+ * initialization parameters were provided.
+ *
+ * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided
+ * key and needs additional parameters to be provided to {@code Cipher.init}.
+ */
+ protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
+ * parameters were provided.
+ *
+ * @param params additional algorithm parameters or {@code null} if not specified.
+ *
+ * @throws InvalidAlgorithmParameterException if there is insufficient information to configure
+ * this cipher or if the provided parameters are not suitable for this cipher.
+ */
+ protected abstract void initAlgorithmSpecificParameters(
+ @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
+ * parameters were provided.
+ *
+ * @param params additional algorithm parameters or {@code null} if not specified.
+ *
+ * @throws InvalidAlgorithmParameterException if there is insufficient information to configure
+ * this cipher or if the provided parameters are not suitable for this cipher.
+ */
+ protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException;
+
+ /**
+ * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
+ * {@code begin} operation. This amount of entropy is typically what's consumed to generate
+ * random parameters, such as IV.
+ *
+ * <p>For decryption, the return value should be {@code 0} because decryption should not be
+ * consuming any entropy. For encryption, the value combined with
+ * {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon
+ * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
+ * explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC
+ * encryption with an explicitly provided IV the return value should be {@code 0}, whereas for
+ * the case where IV is generated by the KeyStore's {@code begin} operation it should be
+ * {@code 16}.
+ */
+ protected abstract int getAdditionalEntropyAmountForBegin();
+
+ /**
+ * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
+ * {@code finish} operation. This amount of entropy is typically what's consumed by encryption
+ * padding scheme.
+ *
+ * <p>For decryption, the return value should be {@code 0} because decryption should not be
+ * consuming any entropy. For encryption, the value combined with
+ * {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon
+ * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
+ * explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with
+ * OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding
+ * the return value should be the size of the padding string or could be raised (for simplicity)
+ * to the size of the modulus.
+ */
+ protected abstract int getAdditionalEntropyAmountForFinish();
+
+ /**
+ * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
+ *
+ * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected abstract void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs);
+
+ /**
+ * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
+ * {@code begin} operation.
+ *
+ * <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such
+ * parameters, if not provided, must be generated by KeyStore and returned to the user of
+ * {@code Cipher} and potentially reused after {@code doFinal}.
+ *
+ * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
+ * operation.
+ */
+ protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs);
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java
new file mode 100644
index 0000000..10aab7e
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java
@@ -0,0 +1,202 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidKeyException;
+import java.security.SignatureSpi;
+
+/**
+ * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
+
+ public final static class NONE extends AndroidKeyStoreECDSASignatureSpi {
+ public NONE() {
+ super(KeymasterDefs.KM_DIGEST_NONE);
+ }
+
+ @Override
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore,
+ IBinder operationToken) {
+ return new TruncateToFieldSizeMessageStreamer(
+ super.createMainDataStreamer(keyStore, operationToken),
+ getGroupSizeBits());
+ }
+
+ /**
+ * Streamer which buffers all input, then truncates it to field size, and then sends it into
+ * KeyStore via the provided delegate streamer.
+ */
+ private static class TruncateToFieldSizeMessageStreamer
+ implements KeyStoreCryptoOperationStreamer {
+
+ private final KeyStoreCryptoOperationStreamer mDelegate;
+ private final int mGroupSizeBits;
+ private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
+ private long mConsumedInputSizeBytes;
+
+ private TruncateToFieldSizeMessageStreamer(
+ KeyStoreCryptoOperationStreamer delegate,
+ int groupSizeBits) {
+ mDelegate = delegate;
+ mGroupSizeBits = groupSizeBits;
+ }
+
+ @Override
+ public byte[] update(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ if (inputLength > 0) {
+ mInputBuffer.write(input, inputOffset, inputLength);
+ mConsumedInputSizeBytes += inputLength;
+ }
+ return EmptyArray.BYTE;
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
+ byte[] additionalEntropy) throws KeyStoreException {
+ if (inputLength > 0) {
+ mConsumedInputSizeBytes += inputLength;
+ mInputBuffer.write(input, inputOffset, inputLength);
+ }
+
+ byte[] bufferedInput = mInputBuffer.toByteArray();
+ mInputBuffer.reset();
+ // Truncate input at field size (bytes)
+ return mDelegate.doFinal(bufferedInput,
+ 0,
+ Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
+ signature, additionalEntropy);
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mDelegate.getProducedOutputSizeBytes();
+ }
+ }
+ }
+
+ public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
+ public SHA1() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
+ public SHA224() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
+ public SHA256() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
+ public SHA384() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
+ public SHA512() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final int mKeymasterDigest;
+
+ private int mGroupSizeBits = -1;
+
+ AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
+ mKeymasterDigest = keymasterDigest;
+ }
+
+ @Override
+ protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
+ if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported");
+ }
+
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = getKeyStore().getKeyCharacteristics(
+ key.getAlias(), null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw getKeyStore().getInvalidKeyException(key.getAlias(), errorCode);
+ }
+ long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
+ if (keySizeBits == -1) {
+ throw new InvalidKeyException("Size of key not known");
+ } else if (keySizeBits > Integer.MAX_VALUE) {
+ throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
+ }
+ mGroupSizeBits = (int) keySizeBits;
+
+ super.initKey(key);
+ }
+
+ @Override
+ protected final void resetAll() {
+ mGroupSizeBits = -1;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForSign() {
+ return (mGroupSizeBits + 7) / 8;
+ }
+
+ protected final int getGroupSizeBits() {
+ if (mGroupSizeBits == -1) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mGroupSizeBits;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java
new file mode 100644
index 0000000..5dbcd68
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java
@@ -0,0 +1,40 @@
+/*
+ * 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.keystore;
+
+import java.security.PrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.spec.ECParameterSpec;
+
+/**
+ * EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey {
+ private final ECParameterSpec mParams;
+
+ public AndroidKeyStoreECPrivateKey(String alias, ECParameterSpec params) {
+ super(alias, KeyProperties.KEY_ALGORITHM_EC);
+ mParams = params;
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return mParams;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java
new file mode 100644
index 0000000..3ed396d
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPublicKey.java
@@ -0,0 +1,57 @@
+/*
+ * 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.keystore;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+
+/**
+ * {@link ECPublicKey} backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey implements ECPublicKey {
+
+ private final ECParameterSpec mParams;
+ private final ECPoint mW;
+
+ public AndroidKeyStoreECPublicKey(String alias, byte[] x509EncodedForm, ECParameterSpec params,
+ ECPoint w) {
+ super(alias, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm);
+ mParams = params;
+ mW = w;
+ }
+
+ public AndroidKeyStoreECPublicKey(String alias, ECPublicKey info) {
+ this(alias, info.getEncoded(), info.getParams(), info.getW());
+ if (!"X.509".equalsIgnoreCase(info.getFormat())) {
+ throw new IllegalArgumentException(
+ "Unsupported key export format: " + info.getFormat());
+ }
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return mParams;
+ }
+
+ @Override
+ public ECPoint getW() {
+ return mW;
+ }
+} \ No newline at end of file
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java
index 175369c..d20e3af 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
@@ -24,6 +26,7 @@ import android.security.keymaster.OperationResult;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
+import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.MacSpi;
@@ -33,33 +36,33 @@ import javax.crypto.MacSpi;
*
* @hide
*/
-public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
+public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
- public static class HmacSHA1 extends KeyStoreHmacSpi {
+ public static class HmacSHA1 extends AndroidKeyStoreHmacSpi {
public HmacSHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
- public static class HmacSHA224 extends KeyStoreHmacSpi {
+ public static class HmacSHA224 extends AndroidKeyStoreHmacSpi {
public HmacSHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
- public static class HmacSHA256 extends KeyStoreHmacSpi {
+ public static class HmacSHA256 extends AndroidKeyStoreHmacSpi {
public HmacSHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
- public static class HmacSHA384 extends KeyStoreHmacSpi {
+ public static class HmacSHA384 extends AndroidKeyStoreHmacSpi {
public HmacSHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
- public static class HmacSHA512 extends KeyStoreHmacSpi {
+ public static class HmacSHA512 extends AndroidKeyStoreHmacSpi {
public HmacSHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
@@ -67,24 +70,24 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
private final KeyStore mKeyStore = KeyStore.getInstance();
private final int mKeymasterDigest;
- private final int mMacSizeBytes;
+ private final int mMacSizeBits;
// Fields below are populated by engineInit and should be preserved after engineDoFinal.
- private KeyStoreSecretKey mKey;
+ private AndroidKeyStoreSecretKey mKey;
// Fields below are reset when engineDoFinal succeeds.
private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
private IBinder mOperationToken;
- private Long mOperationHandle;
+ private long mOperationHandle;
- protected KeyStoreHmacSpi(int keymasterDigest) {
+ protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest;
- mMacSizeBytes = KeymasterUtils.getDigestOutputSizeBytes(keymasterDigest);
+ mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
}
@Override
protected int engineGetMacLength() {
- return mMacSizeBytes;
+ return (mMacSizeBits + 7) / 8;
}
@Override
@@ -108,11 +111,11 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("key == null");
- } else if (!(key instanceof KeyStoreSecretKey)) {
+ } else if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Only Android KeyStore secret keys supported. Key: " + key);
}
- mKey = (KeyStoreSecretKey) key;
+ mKey = (AndroidKeyStoreSecretKey) key;
if (params != null) {
throw new InvalidAlgorithmParameterException(
@@ -125,20 +128,20 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
mKey = null;
IBinder operationToken = mOperationToken;
if (operationToken != null) {
- mOperationToken = null;
mKeyStore.abort(operationToken);
}
- mOperationHandle = null;
+ mOperationToken = null;
+ mOperationHandle = 0;
mChunkedStreamer = null;
}
private void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
- mOperationToken = null;
mKeyStore.abort(operationToken);
}
- mOperationHandle = null;
+ mOperationToken = null;
+ mOperationHandle = 0;
mChunkedStreamer = null;
}
@@ -156,26 +159,40 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
}
KeymasterArguments keymasterArgs = new KeymasterArguments();
- keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
- keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits);
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
KeymasterDefs.KM_PURPOSE_SIGN,
true,
keymasterArgs,
- null,
- new KeymasterArguments());
+ null); // no additional entropy needed for HMAC because it's deterministic
+
if (opResult == null) {
throw new KeyStoreConnectException();
- } else if (opResult.resultCode != KeyStore.NO_ERROR) {
- throw KeyStore.getInvalidKeyException(opResult.resultCode);
- }
- if (opResult.token == null) {
- throw new IllegalStateException("Keystore returned null operation token");
}
+
+ // Store operation token and handle regardless of the error code returned by KeyStore to
+ // ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
+
+ // If necessary, throw an exception due to KeyStore operation having failed.
+ InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
+ mKeyStore, mKey, opResult.resultCode);
+ if (e != null) {
+ throw e;
+ }
+
+ if (mOperationToken == null) {
+ throw new ProviderException("Keystore returned null operation token");
+ }
+ if (mOperationHandle == 0) {
+ throw new ProviderException("Keystore returned invalid operation handle");
+ }
+
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, mOperationToken));
@@ -191,17 +208,17 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
- throw new IllegalStateException("Failed to reinitialize MAC", e);
+ throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] output;
try {
output = mChunkedStreamer.update(input, offset, len);
} catch (KeyStoreException e) {
- throw new IllegalStateException("Keystore operation failed", e);
+ throw new ProviderException("Keystore operation failed", e);
}
if ((output != null) && (output.length != 0)) {
- throw new IllegalStateException("Update operation unexpectedly produced output");
+ throw new ProviderException("Update operation unexpectedly produced output");
}
}
@@ -210,14 +227,18 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
- throw new IllegalStateException("Failed to reinitialize MAC", e);
+ throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] result;
try {
- result = mChunkedStreamer.doFinal(null, 0, 0);
+ result = mChunkedStreamer.doFinal(
+ null, 0, 0,
+ null, // no signature provided -- this invocation will generate one
+ null // no additional entropy needed -- HMAC is deterministic
+ );
} catch (KeyStoreException e) {
- throw new IllegalStateException("Keystore operation failed", e);
+ throw new ProviderException("Keystore operation failed", e);
}
resetWhilePreservingInitState();
@@ -237,7 +258,7 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp
}
@Override
- public Long getOperationHandle() {
+ public long getOperationHandle() {
return mOperationHandle;
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
new file mode 100644
index 0000000..e76802f
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java
@@ -0,0 +1,93 @@
+/*
+ * 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.keystore;
+
+import java.security.Key;
+
+/**
+ * {@link Key} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreKey implements Key {
+ private final String mAlias;
+ private final String mAlgorithm;
+
+ public AndroidKeyStoreKey(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;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
+ result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj;
+ if (mAlgorithm == null) {
+ if (other.mAlgorithm != null) {
+ return false;
+ }
+ } else if (!mAlgorithm.equals(other.mAlgorithm)) {
+ return false;
+ }
+ if (mAlias == null) {
+ if (other.mAlias != null) {
+ return false;
+ }
+ } else if (!mAlias.equals(other.mAlias)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java
new file mode 100644
index 0000000..515be1d
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java
@@ -0,0 +1,145 @@
+/*
+ * 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.keystore;
+
+import android.security.Credentials;
+import android.security.KeyStore;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * {@link KeyFactorySpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ @Override
+ protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
+ throws InvalidKeySpecException {
+ if (key == null) {
+ throw new InvalidKeySpecException("key == null");
+ } else if ((!(key instanceof AndroidKeyStorePrivateKey))
+ && (!(key instanceof AndroidKeyStorePublicKey))) {
+ throw new InvalidKeySpecException(
+ "Unsupported key type: " + key.getClass().getName()
+ + ". This KeyFactory supports only Android Keystore asymmetric keys");
+ }
+
+ // key is an Android Keystore private or public key
+
+ if (keySpecClass == null) {
+ throw new InvalidKeySpecException("keySpecClass == null");
+ } else if (KeyInfo.class.equals(keySpecClass)) {
+ if (!(key instanceof AndroidKeyStorePrivateKey)) {
+ throw new InvalidKeySpecException(
+ "Unsupported key type: " + key.getClass().getName()
+ + ". KeyInfo can be obtained only for Android Keystore private keys");
+ }
+ String keyAliasInKeystore = ((AndroidKeyStorePrivateKey) key).getAlias();
+ String entryAlias;
+ if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
+ entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
+ } else {
+ throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
+ }
+ @SuppressWarnings("unchecked")
+ T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(
+ mKeyStore, entryAlias, keyAliasInKeystore);
+ return result;
+ } else if (X509EncodedKeySpec.class.equals(keySpecClass)) {
+ if (!(key instanceof AndroidKeyStorePublicKey)) {
+ throw new InvalidKeySpecException(
+ "Unsupported key type: " + key.getClass().getName()
+ + ". X509EncodedKeySpec can be obtained only for Android Keystore public"
+ + " keys");
+ }
+ @SuppressWarnings("unchecked")
+ T result = (T) new X509EncodedKeySpec(((AndroidKeyStorePublicKey) key).getEncoded());
+ return result;
+ } else if (PKCS8EncodedKeySpec.class.equals(keySpecClass)) {
+ if (key instanceof AndroidKeyStorePrivateKey) {
+ throw new InvalidKeySpecException(
+ "Key material export of Android Keystore private keys is not supported");
+ } else {
+ throw new InvalidKeySpecException(
+ "Cannot export key material of public key in PKCS#8 format."
+ + " Only X.509 format (X509EncodedKeySpec) supported for public keys.");
+ }
+ } else if (RSAPublicKeySpec.class.equals(keySpecClass)) {
+ if (key instanceof AndroidKeyStoreRSAPublicKey) {
+ AndroidKeyStoreRSAPublicKey rsaKey = (AndroidKeyStoreRSAPublicKey) key;
+ @SuppressWarnings("unchecked")
+ T result =
+ (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
+ return result;
+ } else {
+ throw new InvalidKeySpecException(
+ "Obtaining RSAPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ + " key");
+ }
+ } else if (ECPublicKeySpec.class.equals(keySpecClass)) {
+ if (key instanceof AndroidKeyStoreECPublicKey) {
+ AndroidKeyStoreECPublicKey ecKey = (AndroidKeyStoreECPublicKey) key;
+ @SuppressWarnings("unchecked")
+ T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams());
+ return result;
+ } else {
+ throw new InvalidKeySpecException(
+ "Obtaining ECPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ + " key");
+ }
+ } else {
+ throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
+ }
+ }
+
+ @Override
+ protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException {
+ throw new UnsupportedOperationException(
+ "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
+ + " " + KeyGenParameterSpec.class.getName());
+ }
+
+ @Override
+ protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException {
+ throw new UnsupportedOperationException(
+ "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
+ + " " + KeyGenParameterSpec.class.getName());
+ }
+
+ @Override
+ protected Key engineTranslateKey(Key arg0) throws InvalidKeyException {
+ throw new UnsupportedOperationException(
+ "To import a key into Android KeyStore, use KeyStore.setEntry with "
+ + KeyProtection.class.getName());
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
new file mode 100644
index 0000000..6a7930a
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -0,0 +1,334 @@
+/*
+ * 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.keystore;
+
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import libcore.util.EmptyArray;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.ProviderException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+import javax.crypto.KeyGeneratorSpi;
+import javax.crypto.SecretKey;
+
+/**
+ * {@link KeyGeneratorSpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
+
+ public static class AES extends AndroidKeyStoreKeyGeneratorSpi {
+ public AES() {
+ super(KeymasterDefs.KM_ALGORITHM_AES, 128);
+ }
+
+ @Override
+ protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ super.engineInit(params, random);
+ if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported key size: " + mKeySizeBits
+ + ". Supported: 128, 192, 256.");
+ }
+ }
+ }
+
+ protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
+ protected HmacBase(int keymasterDigest) {
+ super(KeymasterDefs.KM_ALGORITHM_HMAC,
+ keymasterDigest,
+ KeymasterUtils.getDigestOutputSizeBits(keymasterDigest));
+ }
+ }
+
+ public static class HmacSHA1 extends HmacBase {
+ public HmacSHA1() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends HmacBase {
+ public HmacSHA224() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static class HmacSHA256 extends HmacBase {
+ public HmacSHA256() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static class HmacSHA384 extends HmacBase {
+ public HmacSHA384() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static class HmacSHA512 extends HmacBase {
+ public HmacSHA512() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final int mKeymasterAlgorithm;
+ private final int mKeymasterDigest;
+ private final int mDefaultKeySizeBits;
+
+ private KeyGenParameterSpec mSpec;
+ private SecureRandom mRng;
+
+ protected int mKeySizeBits;
+ private int[] mKeymasterPurposes;
+ private int[] mKeymasterBlockModes;
+ private int[] mKeymasterPaddings;
+ private int[] mKeymasterDigests;
+
+ protected AndroidKeyStoreKeyGeneratorSpi(
+ int keymasterAlgorithm,
+ int defaultKeySizeBits) {
+ this(keymasterAlgorithm, -1, defaultKeySizeBits);
+ }
+
+ protected AndroidKeyStoreKeyGeneratorSpi(
+ int keymasterAlgorithm,
+ int keymasterDigest,
+ int defaultKeySizeBits) {
+ mKeymasterAlgorithm = keymasterAlgorithm;
+ mKeymasterDigest = keymasterDigest;
+ mDefaultKeySizeBits = defaultKeySizeBits;
+ if (mDefaultKeySizeBits <= 0) {
+ throw new IllegalArgumentException("Default key size must be positive");
+ }
+
+ if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) {
+ throw new IllegalArgumentException(
+ "Digest algorithm must be specified for HMAC key");
+ }
+ }
+
+ @Override
+ protected void engineInit(SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+
+ @Override
+ protected void engineInit(int keySize, SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+
+ @Override
+ protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if ((params == null) || (!(params instanceof KeyGenParameterSpec))) {
+ throw new InvalidAlgorithmParameterException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+ KeyGenParameterSpec spec = (KeyGenParameterSpec) params;
+ if (spec.getKeystoreAlias() == null) {
+ throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
+ }
+
+ mRng = random;
+ mSpec = spec;
+
+ mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits;
+ if (mKeySizeBits <= 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Key size must be positive: " + mKeySizeBits);
+ } else if ((mKeySizeBits % 8) != 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Key size in must be a multiple of 8: " + mKeySizeBits);
+ }
+
+ try {
+ mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
+ mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (spec.getSignaturePaddings().length > 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Signature paddings not supported for symmetric key algorithms");
+ }
+ mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterBlockMode : mKeymasterBlockModes) {
+ if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ keymasterBlockMode)) {
+ throw new InvalidAlgorithmParameterException(
+ "Randomized encryption (IND-CPA) required but may be violated"
+ + " by block mode: "
+ + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
+ + ". See " + KeyGenParameterSpec.class.getName()
+ + " documentation.");
+ }
+ }
+ }
+ if (spec.isDigestsSpecified()) {
+ // Digest(s) explicitly specified in the spec
+ mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
+ if (mKeymasterDigest != -1) {
+ // Key algorithm implies a digest -- ensure it's specified in the spec as
+ // first digest.
+ if (!com.android.internal.util.ArrayUtils.contains(
+ mKeymasterDigests, mKeymasterDigest)) {
+ throw new InvalidAlgorithmParameterException(
+ "Digests specified in algorithm parameters ("
+ + Arrays.asList(spec.getDigests()) + ") must include "
+ + " the digest "
+ + KeyProperties.Digest.fromKeymaster(mKeymasterDigest)
+ + " implied by key algorithm");
+ }
+ if (mKeymasterDigests[0] != mKeymasterDigest) {
+ // The first digest is not the one implied by the key algorithm.
+ // Swap the implied digest with the first one.
+ for (int i = 0; i < mKeymasterDigests.length; i++) {
+ if (mKeymasterDigests[i] == mKeymasterDigest) {
+ mKeymasterDigests[i] = mKeymasterDigests[0];
+ mKeymasterDigests[0] = mKeymasterDigest;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // No digest specified in the spec
+ if (mKeymasterDigest != -1) {
+ // Key algorithm implies a digest -- use that digest
+ mKeymasterDigests = new int[] {mKeymasterDigest};
+ } else {
+ mKeymasterDigests = EmptyArray.INT;
+ }
+ }
+ if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+ if (mKeymasterDigests.length == 0) {
+ throw new InvalidAlgorithmParameterException(
+ "At least one digest algorithm must be specified");
+ }
+ }
+
+ // Check that user authentication related parameters are acceptable. This method
+ // will throw an IllegalStateException if there are issues (e.g., secure lock screen
+ // not set up).
+ KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
+ spec.isUserAuthenticationRequired(),
+ spec.getUserAuthenticationValidityDurationSeconds());
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void resetAll() {
+ mSpec = null;
+ mRng = null;
+ mKeySizeBits = -1;
+ mKeymasterPurposes = null;
+ mKeymasterPaddings = null;
+ mKeymasterBlockModes = null;
+ }
+
+ @Override
+ protected SecretKey engineGenerateKey() {
+ KeyGenParameterSpec spec = mSpec;
+ if (spec == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ KeymasterArguments args = new KeymasterArguments();
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
+ args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes);
+ args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes);
+ args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings);
+ args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
+ KeymasterUtils.addUserAuthArgs(args,
+ spec.isUserAuthenticationRequired(),
+ spec.getUserAuthenticationValidityDurationSeconds());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ spec.getKeyValidityForOriginationEnd());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ spec.getKeyValidityForConsumptionEnd());
+
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
+ args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
+ }
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+ int flags = 0;
+ String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias();
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ boolean success = false;
+ try {
+ Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias());
+ int errorCode = mKeyStore.generateKey(
+ keyAliasInKeystore,
+ args,
+ additionalEntropy,
+ flags,
+ resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(
+ "Keystore operation failed", KeyStore.getKeyStoreException(errorCode));
+ }
+ @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
+ try {
+ keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
+ mKeymasterAlgorithm, mKeymasterDigest);
+ } catch (IllegalArgumentException e) {
+ throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
+ }
+ SecretKey result = new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA);
+ success = true;
+ return result;
+ } finally {
+ if (!success) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias());
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
new file mode 100644
index 0000000..6b36a58
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2012 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.keystore;
+
+import android.annotation.Nullable;
+import android.security.Credentials;
+import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.org.bouncycastle.asn1.ASN1Integer;
+import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.org.bouncycastle.asn1.DERBitString;
+import com.android.org.bouncycastle.asn1.DERInteger;
+import com.android.org.bouncycastle.asn1.DERNull;
+import com.android.org.bouncycastle.asn1.DERSequence;
+import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.org.bouncycastle.asn1.x509.Certificate;
+import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.org.bouncycastle.asn1.x509.Time;
+import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.org.bouncycastle.jce.X509Principal;
+import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
+import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+
+import libcore.util.EmptyArray;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyPairGeneratorSpi;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides a way to create instances of a KeyPair which will be placed in the
+ * Android keystore service usable only by the application that called it. This
+ * can be used in conjunction with
+ * {@link java.security.KeyStore#getInstance(String)} using the
+ * {@code "AndroidKeyStore"} type.
+ * <p>
+ * This class can not be directly instantiated and must instead be used via the
+ * {@link KeyPairGenerator#getInstance(String)
+ * KeyPairGenerator.getInstance("AndroidKeyStore")} API.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi {
+
+ public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi {
+ public RSA() {
+ super(KeymasterDefs.KM_ALGORITHM_RSA);
+ }
+ }
+
+ public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi {
+ public EC() {
+ super(KeymasterDefs.KM_ALGORITHM_EC);
+ }
+ }
+
+ /*
+ * These must be kept in sync with system/security/keystore/defaults.h
+ */
+
+ /* EC */
+ private static final int EC_DEFAULT_KEY_SIZE = 256;
+
+ /* RSA */
+ private static final int RSA_DEFAULT_KEY_SIZE = 2048;
+ private static final int RSA_MIN_KEY_SIZE = 512;
+ private static final int RSA_MAX_KEY_SIZE = 8192;
+
+ private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+ new HashMap<String, Integer>();
+ private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
+ private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
+ static {
+ // Aliases for NIST P-224
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+
+
+ // Aliases for NIST P-256
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+
+ // Aliases for NIST P-384
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+
+ // Aliases for NIST P-521
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+
+ SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+
+ SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
+ new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
+ }
+
+ private final int mOriginalKeymasterAlgorithm;
+
+ private KeyStore mKeyStore;
+
+ private KeyGenParameterSpec mSpec;
+
+ private String mEntryAlias;
+ private boolean mEncryptionAtRestRequired;
+ private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm;
+ private int mKeymasterAlgorithm = -1;
+ private int mKeySizeBits;
+ private SecureRandom mRng;
+
+ private int[] mKeymasterPurposes;
+ private int[] mKeymasterBlockModes;
+ private int[] mKeymasterEncryptionPaddings;
+ private int[] mKeymasterSignaturePaddings;
+ private int[] mKeymasterDigests;
+
+ private BigInteger mRSAPublicExponent;
+
+ protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) {
+ mOriginalKeymasterAlgorithm = keymasterAlgorithm;
+ }
+
+ @Override
+ public void initialize(int keysize, SecureRandom random) {
+ throw new IllegalArgumentException(
+ KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName()
+ + " required to initialize this KeyPairGenerator");
+ }
+
+ @Override
+ public void initialize(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if (params == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Must supply params of type " + KeyGenParameterSpec.class.getName()
+ + " or " + KeyPairGeneratorSpec.class.getName());
+ }
+
+ KeyGenParameterSpec spec;
+ boolean encryptionAtRestRequired = false;
+ int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
+ if (params instanceof KeyGenParameterSpec) {
+ spec = (KeyGenParameterSpec) params;
+ } else if (params instanceof KeyPairGeneratorSpec) {
+ // Legacy/deprecated spec
+ KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
+ try {
+ KeyGenParameterSpec.Builder specBuilder;
+ String specKeyAlgorithm = legacySpec.getKeyType();
+ if (specKeyAlgorithm != null) {
+ // Spec overrides the generator's default key algorithm
+ try {
+ keymasterAlgorithm =
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ specKeyAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid key type in parameters", e);
+ }
+ }
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_ENCRYPT
+ | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
+ // Authorized to be used with any encryption and signature padding
+ // scheme (including no padding).
+ specBuilder.setEncryptionPaddings(
+ KeyProperties.ENCRYPTION_PADDING_NONE);
+ // Disable randomized encryption requirement to support encryption
+ // padding NONE above.
+ specBuilder.setRandomizedEncryptionRequired(false);
+ break;
+ default:
+ throw new ProviderException(
+ "Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+
+ if (legacySpec.getKeySize() != -1) {
+ specBuilder.setKeySize(legacySpec.getKeySize());
+ }
+ if (legacySpec.getAlgorithmParameterSpec() != null) {
+ specBuilder.setAlgorithmParameterSpec(
+ legacySpec.getAlgorithmParameterSpec());
+ }
+ specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+ specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+ specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+ specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+ encryptionAtRestRequired = legacySpec.isEncryptionRequired();
+ specBuilder.setUserAuthenticationRequired(false);
+
+ spec = specBuilder.build();
+ } catch (NullPointerException | IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+ } else {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported params class: " + params.getClass().getName()
+ + ". Supported: " + KeyGenParameterSpec.class.getName()
+ + ", " + KeyPairGeneratorSpec.class.getName());
+ }
+
+ mEntryAlias = spec.getKeystoreAlias();
+ mSpec = spec;
+ mKeymasterAlgorithm = keymasterAlgorithm;
+ mEncryptionAtRestRequired = encryptionAtRestRequired;
+ mKeySizeBits = spec.getKeySize();
+ initAlgorithmSpecificParameters();
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
+ }
+ checkValidKeySize(keymasterAlgorithm, mKeySizeBits);
+
+ if (spec.getKeystoreAlias() == null) {
+ throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
+ }
+
+ String jcaKeyAlgorithm;
+ try {
+ jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
+ keymasterAlgorithm);
+ mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
+ mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
+ mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterPadding : mKeymasterEncryptionPaddings) {
+ if (!KeymasterUtils
+ .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ keymasterPadding)) {
+ throw new InvalidAlgorithmParameterException(
+ "Randomized encryption (IND-CPA) required but may be violated"
+ + " by padding scheme: "
+ + KeyProperties.EncryptionPadding.fromKeymaster(
+ keymasterPadding)
+ + ". See " + KeyGenParameterSpec.class.getName()
+ + " documentation.");
+ }
+ }
+ }
+ mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster(
+ spec.getSignaturePaddings());
+ if (spec.isDigestsSpecified()) {
+ mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
+ } else {
+ mKeymasterDigests = EmptyArray.INT;
+ }
+
+ // Check that user authentication related parameters are acceptable. This method
+ // will throw an IllegalStateException if there are issues (e.g., secure lock screen
+ // not set up).
+ KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
+ mSpec.isUserAuthenticationRequired(),
+ mSpec.getUserAuthenticationValidityDurationSeconds());
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+
+ mJcaKeyAlgorithm = jcaKeyAlgorithm;
+ mRng = random;
+ mKeyStore = KeyStore.getInstance();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void resetAll() {
+ mEntryAlias = null;
+ mJcaKeyAlgorithm = null;
+ mKeymasterAlgorithm = -1;
+ mKeymasterPurposes = null;
+ mKeymasterBlockModes = null;
+ mKeymasterEncryptionPaddings = null;
+ mKeymasterSignaturePaddings = null;
+ mKeymasterDigests = null;
+ mKeySizeBits = 0;
+ mSpec = null;
+ mRSAPublicExponent = null;
+ mEncryptionAtRestRequired = false;
+ mRng = null;
+ mKeyStore = null;
+ }
+
+ private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
+ AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec();
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ BigInteger publicExponent = null;
+ if (algSpecificSpec instanceof RSAKeyGenParameterSpec) {
+ RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec;
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = rsaSpec.getKeysize();
+ } else if (mKeySizeBits != rsaSpec.getKeysize()) {
+ throw new InvalidAlgorithmParameterException("RSA key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize());
+ }
+ publicExponent = rsaSpec.getPublicExponent();
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA may only use RSAKeyGenParameterSpec");
+ }
+ if (publicExponent == null) {
+ publicExponent = RSAKeyGenParameterSpec.F4;
+ }
+ if (publicExponent.compareTo(BigInteger.ZERO) < 1) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA public exponent must be positive: " + publicExponent);
+ }
+ if (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported RSA public exponent: " + publicExponent
+ + ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE);
+ }
+ mRSAPublicExponent = publicExponent;
+ break;
+ }
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (algSpecificSpec instanceof ECGenParameterSpec) {
+ ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
+ String curveName = ecSpec.getName();
+ Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
+ curveName.toLowerCase(Locale.US));
+ if (ecSpecKeySizeBits == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported EC curve name: " + curveName
+ + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+ }
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = ecSpecKeySizeBits;
+ } else if (mKeySizeBits != ecSpecKeySizeBits) {
+ throw new InvalidAlgorithmParameterException("EC key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits);
+ }
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "EC may only use ECGenParameterSpec");
+ }
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+ }
+
+ @Override
+ public KeyPair generateKeyPair() {
+ if (mKeyStore == null || mSpec == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ final int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0;
+ if (((flags & KeyStore.FLAG_ENCRYPTED) != 0)
+ && (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
+ throw new IllegalStateException(
+ "Encryption at rest using secure lock screen credential requested for key pair"
+ + ", but the user has not yet entered the credential");
+ }
+
+ KeymasterArguments args = new KeymasterArguments();
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
+ args.addEnums(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes);
+ args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes);
+ args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings);
+ args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings);
+ args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
+
+ KeymasterUtils.addUserAuthArgs(args,
+ mSpec.isUserAuthenticationRequired(),
+ mSpec.getUserAuthenticationValidityDurationSeconds());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ mSpec.getKeyValidityForOriginationEnd());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ mSpec.getKeyValidityForConsumptionEnd());
+ addAlgorithmSpecificParameters(args);
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
+ boolean success = false;
+ try {
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias);
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.generateKey(
+ privateKeyAlias,
+ args,
+ additionalEntropy,
+ flags,
+ resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(
+ "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+ }
+
+ KeyPair result;
+ try {
+ result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ mKeyStore, privateKeyAlias);
+ } catch (UnrecoverableKeyException e) {
+ throw new ProviderException("Failed to load generated key pair from keystore", e);
+ }
+
+ if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
+ throw new ProviderException(
+ "Generated key pair algorithm does not match requested algorithm: "
+ + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
+ }
+
+ final X509Certificate cert;
+ try {
+ cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
+ } catch (Exception e) {
+ throw new ProviderException("Failed to generate self-signed certificate", e);
+ }
+
+ byte[] certBytes;
+ try {
+ certBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new ProviderException(
+ "Failed to obtain encoded form of self-signed certificate", e);
+ }
+
+ int insertErrorCode = mKeyStore.insert(
+ Credentials.USER_CERTIFICATE + mEntryAlias,
+ certBytes,
+ KeyStore.UID_SELF,
+ flags);
+ if (insertErrorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException("Failed to store self-signed certificate",
+ KeyStore.getKeyStoreException(insertErrorCode));
+ }
+
+ success = true;
+ return result;
+ } finally {
+ if (!success) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias);
+ }
+ }
+ }
+
+ private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) {
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ keymasterArgs.addUnsignedLong(
+ KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent);
+ break;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+ }
+
+ private X509Certificate generateSelfSignedCertificate(
+ PrivateKey privateKey, PublicKey publicKey) throws Exception {
+ String signatureAlgorithm =
+ getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec);
+ if (signatureAlgorithm == null) {
+ // Key cannot be used to sign a certificate
+ return generateSelfSignedCertificateWithFakeSignature(publicKey);
+ } else {
+ // Key can be used to sign a certificate
+ try {
+ return generateSelfSignedCertificateWithValidSignature(
+ privateKey, publicKey, signatureAlgorithm);
+ } catch (Exception e) {
+ // Failed to generate the self-signed certificate with valid signature. Fall back
+ // to generating a self-signed certificate with a fake signature. This is done for
+ // all exception types because we prefer key pair generation to succeed and end up
+ // producing a self-signed certificate with an invalid signature to key pair
+ // generation failing.
+ return generateSelfSignedCertificateWithFakeSignature(publicKey);
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private X509Certificate generateSelfSignedCertificateWithValidSignature(
+ PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) throws Exception {
+ final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
+ certGen.setPublicKey(publicKey);
+ certGen.setSerialNumber(mSpec.getCertificateSerialNumber());
+ certGen.setSubjectDN(mSpec.getCertificateSubject());
+ certGen.setIssuerDN(mSpec.getCertificateSubject());
+ certGen.setNotBefore(mSpec.getCertificateNotBefore());
+ certGen.setNotAfter(mSpec.getCertificateNotAfter());
+ certGen.setSignatureAlgorithm(signatureAlgorithm);
+ return certGen.generate(privateKey);
+ }
+
+ @SuppressWarnings("deprecation")
+ private X509Certificate generateSelfSignedCertificateWithFakeSignature(
+ PublicKey publicKey) throws Exception {
+ V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
+ ASN1ObjectIdentifier sigAlgOid;
+ AlgorithmIdentifier sigAlgId;
+ byte[] signature;
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256;
+ sigAlgId = new AlgorithmIdentifier(sigAlgOid);
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new DERInteger(0));
+ v.add(new DERInteger(0));
+ signature = new DERSequence().getEncoded();
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption;
+ sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE);
+ signature = new byte[1];
+ break;
+ default:
+ throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm);
+ }
+
+ try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) {
+ tbsGenerator.setSubjectPublicKeyInfo(
+ SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject()));
+ }
+ tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber()));
+ X509Principal subject =
+ new X509Principal(mSpec.getCertificateSubject().getEncoded());
+ tbsGenerator.setSubject(subject);
+ tbsGenerator.setIssuer(subject);
+ tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore()));
+ tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter()));
+ tbsGenerator.setSignature(sigAlgId);
+ TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate();
+
+ ASN1EncodableVector result = new ASN1EncodableVector();
+ result.add(tbsCertificate);
+ result.add(sigAlgId);
+ result.add(new DERBitString(signature));
+ return new X509CertificateObject(Certificate.getInstance(new DERSequence(result)));
+ }
+
+ private static int getDefaultKeySize(int keymasterAlgorithm) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC_DEFAULT_KEY_SIZE;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA_DEFAULT_KEY_SIZE;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ private static void checkValidKeySize(int keymasterAlgorithm, int keySize)
+ throws InvalidAlgorithmParameterException {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+ throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
+ + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
+ }
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
+ throw new InvalidAlgorithmParameterException("RSA key size must be >= "
+ + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
+ }
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ /**
+ * Returns the {@code Signature} algorithm to be used for signing a certificate using the
+ * specified key or {@code null} if the key cannot be used for signing a certificate.
+ */
+ @Nullable
+ private static String getCertificateSignatureAlgorithm(
+ int keymasterAlgorithm,
+ int keySizeBits,
+ KeyGenParameterSpec spec) {
+ // Constraints:
+ // 1. Key must be authorized for signing without user authentication.
+ // 2. Signature digest must be one of key's authorized digests.
+ // 3. For RSA keys, the digest output size must not exceed modulus size minus space overhead
+ // of RSA PKCS#1 signature padding scheme (about 30 bytes).
+ // 4. For EC keys, the there is no point in using a digest whose output size is longer than
+ // key/field size because the digest will be truncated to that size.
+
+ if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) {
+ // Key not authorized for signing
+ return null;
+ }
+ if (spec.isUserAuthenticationRequired()) {
+ // Key not authorized for use without user authentication
+ return null;
+ }
+ if (!spec.isDigestsSpecified()) {
+ // Key not authorized for any digests -- can't sign
+ return null;
+ }
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ {
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits == keySizeBits) {
+ // Perfect match -- use this digest
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ break;
+ }
+ // Not a perfect match -- check against the best digest so far
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // Prefer output size to be as close to key size as possible, with output
+ // sizes larger than key size preferred to those smaller than key size.
+ if (bestDigestOutputSizeBits < keySizeBits) {
+ // Output size of the best digest so far is smaller than key size.
+ // Anything larger is a win.
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ } else {
+ // Output size of the best digest so far is larger than key size.
+ // Anything smaller is a win, as long as it's not smaller than key size.
+ if ((outputSizeBits < bestDigestOutputSizeBits)
+ && (outputSizeBits >= keySizeBits)) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
+ }
+ }
+ if (bestKeymasterDigest == -1) {
+ return null;
+ }
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithECDSA";
+ }
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ // Check whether this key is authorized for PKCS#1 signature padding.
+ // We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle
+ // only supports RSA certificates signed using PKCS#1 padding scheme. The key needs
+ // to be authorized for PKCS#1 padding or padding NONE which means any padding.
+ boolean pkcs1SignaturePaddingSupported = false;
+ for (int keymasterPadding : KeyProperties.SignaturePadding.allToKeymaster(
+ spec.getSignaturePaddings())) {
+ if ((keymasterPadding == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN)
+ || (keymasterPadding == KeymasterDefs.KM_PAD_NONE)) {
+ pkcs1SignaturePaddingSupported = true;
+ break;
+ }
+ }
+ if (!pkcs1SignaturePaddingSupported) {
+ // Keymaster doesn't distinguish between encryption padding NONE and signature
+ // padding NONE. In the Android Keystore API only encryption padding NONE is
+ // exposed.
+ for (int keymasterPadding : KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings())) {
+ if (keymasterPadding == KeymasterDefs.KM_PAD_NONE) {
+ pkcs1SignaturePaddingSupported = true;
+ break;
+ }
+ }
+ }
+ if (!pkcs1SignaturePaddingSupported) {
+ // Key not authorized for PKCS#1 signature padding -- can't sign
+ return null;
+ }
+
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ // The amount of space available for the digest is less than modulus size by about
+ // 30 bytes because padding must be at least 11 bytes long (00 || 01 || PS || 00,
+ // where PS must be at least 8 bytes long), and then there's also the 15--19 bytes
+ // overhead (depending the on chosen digest) for encoding digest OID and digest
+ // value in DER.
+ int maxDigestOutputSizeBits = keySizeBits - 30 * 8;
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits > maxDigestOutputSizeBits) {
+ // Digest too long (signature generation will fail) -- skip
+ continue;
+ }
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // The longer the better
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
+ }
+ if (bestKeymasterDigest == -1) {
+ return null;
+ }
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithRSA";
+ }
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ private static Set<Integer> getAvailableKeymasterSignatureDigests(
+ @KeyProperties.DigestEnum String[] authorizedKeyDigests,
+ @KeyProperties.DigestEnum String[] supportedSignatureDigests) {
+ Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>();
+ for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) {
+ authorizedKeymasterKeyDigests.add(keymasterDigest);
+ }
+ Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>();
+ for (int keymasterDigest
+ : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) {
+ supportedKeymasterSignatureDigests.add(keymasterDigest);
+ }
+ if (authorizedKeymasterKeyDigests.contains(KeymasterDefs.KM_DIGEST_NONE)) {
+ // Key is authorized to be used with any digest
+ return supportedKeymasterSignatureDigests;
+ } else {
+ // Key is authorized to be used only with specific digests.
+ Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests);
+ result.retainAll(authorizedKeymasterKeyDigests);
+ return result;
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.java
new file mode 100644
index 0000000..b586ad4
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStorePrivateKey.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.keystore;
+
+import java.security.PrivateKey;
+
+/**
+ * {@link PrivateKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey {
+
+ public AndroidKeyStorePrivateKey(String alias, String algorithm) {
+ super(alias, algorithm);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
new file mode 100644
index 0000000..ba39ba7
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2012 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.keystore;
+
+import android.annotation.NonNull;
+import android.security.KeyStore;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.UnrecoverableKeyException;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * A provider focused on providing JCA interfaces for the Android KeyStore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreProvider extends Provider {
+ public static final String PROVIDER_NAME = "AndroidKeyStore";
+
+ // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
+ // classes when this provider is instantiated and installed early on during each app's
+ // initialization process.
+ //
+ // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider.
+ // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc
+ // for details.
+
+ private static final String PACKAGE_NAME = "android.security.keystore";
+
+ public AndroidKeyStoreProvider() {
+ super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
+
+ // java.security.KeyStore
+ put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
+
+ // java.security.KeyPairGenerator
+ put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
+ put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+
+ // java.security.KeyFactory
+ putKeyFactoryImpl("EC");
+ putKeyFactoryImpl("RSA");
+
+ // javax.crypto.KeyGenerator
+ put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
+ put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
+ put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224");
+ put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256");
+ put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384");
+ put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512");
+
+ // java.security.SecretKeyFactory
+ putSecretKeyFactoryImpl("AES");
+ putSecretKeyFactoryImpl("HmacSHA1");
+ putSecretKeyFactoryImpl("HmacSHA224");
+ putSecretKeyFactoryImpl("HmacSHA256");
+ putSecretKeyFactoryImpl("HmacSHA384");
+ putSecretKeyFactoryImpl("HmacSHA512");
+ }
+
+ /**
+ * Installs a new instance of this provider (and the
+ * {@link AndroidKeyStoreBCWorkaroundProvider}).
+ */
+ public static void install() {
+ Provider[] providers = Security.getProviders();
+ int bcProviderPosition = -1;
+ for (int position = 0; position < providers.length; position++) {
+ Provider provider = providers[position];
+ if ("BC".equals(provider.getName())) {
+ bcProviderPosition = position;
+ break;
+ }
+ }
+
+ Security.addProvider(new AndroidKeyStoreProvider());
+ Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
+ if (bcProviderPosition != -1) {
+ // Bouncy Castle provider found -- install the workaround provider above it.
+ Security.insertProviderAt(workaroundProvider, bcProviderPosition);
+ } else {
+ // Bouncy Castle provider not found -- install the workaround provider at lowest
+ // priority.
+ Security.addProvider(workaroundProvider);
+ }
+ }
+
+ private void putSecretKeyFactoryImpl(String algorithm) {
+ put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
+ }
+
+ private void putKeyFactoryImpl(String algorithm) {
+ put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
+ }
+
+ /**
+ * 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 0} 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.
+ * @throws IllegalStateException if the provided primitive is not initialized.
+ */
+ public static long getKeyStoreOperationHandle(Object cryptoPrimitive) {
+ if (cryptoPrimitive == null) {
+ throw new NullPointerException();
+ }
+ Object spi;
+ if (cryptoPrimitive instanceof Signature) {
+ spi = ((Signature) cryptoPrimitive).getCurrentSpi();
+ } else if (cryptoPrimitive instanceof Mac) {
+ spi = ((Mac) cryptoPrimitive).getCurrentSpi();
+ } else if (cryptoPrimitive instanceof Cipher) {
+ spi = ((Cipher) cryptoPrimitive).getCurrentSpi();
+ } else {
+ throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive
+ + ". Supported: Signature, Mac, Cipher");
+ }
+ if (spi == null) {
+ throw new IllegalStateException("Crypto primitive not initialized");
+ } else if (!(spi instanceof KeyStoreCryptoOperation)) {
+ throw new IllegalArgumentException(
+ "Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive
+ + ", spi: " + spi);
+ }
+ return ((KeyStoreCryptoOperation) spi).getOperationHandle();
+ }
+
+ @NonNull
+ public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey(
+ @NonNull String alias,
+ @NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm,
+ @NonNull byte[] x509EncodedForm) {
+ PublicKey publicKey;
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
+ publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm));
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain " + keyAlgorithm + " KeyFactory", e);
+ } catch (InvalidKeySpecException e) {
+ throw new ProviderException("Invalid X.509 encoding of public key", e);
+ }
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreECPublicKey(alias, (ECPublicKey) publicKey);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreRSAPublicKey(alias, (RSAPublicKey) publicKey);
+ } else {
+ throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ + keyAlgorithm);
+ }
+ }
+
+ @NonNull
+ public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
+ @NonNull AndroidKeyStorePublicKey publicKey) {
+ String keyAlgorithm = publicKey.getAlgorithm();
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreECPrivateKey(
+ publicKey.getAlias(), ((ECKey) publicKey).getParams());
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return new AndroidKeyStoreRSAPrivateKey(
+ publicKey.getAlias(), ((RSAKey) publicKey).getModulus());
+ } else {
+ throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ + keyAlgorithm);
+ }
+ }
+
+ @NonNull
+ public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = keyStore.getKeyCharacteristics(
+ privateKeyAlias, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain information about private key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+ ExportResult exportResult = keyStore.exportKey(
+ privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
+ if (exportResult.resultCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain X.509 form of public key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+ final byte[] x509EncodedPublicKey = exportResult.exportData;
+
+ Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
+ if (keymasterAlgorithm == null) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+
+ String jcaKeyAlgorithm;
+ try {
+ jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
+ keymasterAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to load private key")
+ .initCause(e);
+ }
+
+ return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ privateKeyAlias, jcaKeyAlgorithm, x509EncodedPublicKey);
+ }
+
+ @NonNull
+ public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ AndroidKeyStorePublicKey publicKey =
+ loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias);
+ AndroidKeyStorePrivateKey privateKey =
+ AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
+ return new KeyPair(publicKey, privateKey);
+ }
+
+ @NonNull
+ public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias);
+ return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
+ }
+
+ @NonNull
+ public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String secretKeyAlias)
+ throws UnrecoverableKeyException {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = keyStore.getKeyCharacteristics(
+ secretKeyAlias, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain information about key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
+ }
+
+ Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
+ if (keymasterAlgorithm == null) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+
+ List<Integer> keymasterDigests = keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST);
+ int keymasterDigest;
+ if (keymasterDigests.isEmpty()) {
+ keymasterDigest = -1;
+ } else {
+ // More than one digest can be permitted for this key. Use the first one to form the
+ // JCA key algorithm name.
+ keymasterDigest = keymasterDigests.get(0);
+ }
+
+ @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
+ try {
+ keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
+ keymasterAlgorithm, keymasterDigest);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
+ }
+
+ return new AndroidKeyStoreSecretKey(secretKeyAlias, keyAlgorithmString);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
new file mode 100644
index 0000000..9fea30d
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java
@@ -0,0 +1,71 @@
+/*
+ * 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.keystore;
+
+import java.security.PublicKey;
+import java.util.Arrays;
+
+/**
+ * {@link PublicKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
+
+ private final byte[] mEncoded;
+
+ public AndroidKeyStorePublicKey(String alias, String algorithm, byte[] x509EncodedForm) {
+ super(alias, algorithm);
+ mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm);
+ }
+
+ @Override
+ public String getFormat() {
+ return "X.509";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return ArrayUtils.cloneIfNotEmpty(mEncoded);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(mEncoded);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj;
+ if (!Arrays.equals(mEncoded, other.mEncoded)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
new file mode 100644
index 0000000..94ed8b4
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
@@ -0,0 +1,604 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.MGF1ParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+/**
+ * Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ /**
+ * Raw RSA cipher without any padding.
+ */
+ public static final class NoPadding extends AndroidKeyStoreRSACipherSpi {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+
+ @Override
+ protected boolean adjustConfigForEncryptingWithPrivateKey() {
+ // RSA encryption with no padding using private key is a way to implement raw RSA
+ // signatures which JCA does not expose via Signature. We thus have to support this.
+ setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
+ return true;
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ return null;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
+ @NonNull
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ if (isEncrypting()) {
+ // KeyStore's RSA encryption without padding expects the input to be of the same
+ // length as the modulus. We thus have to buffer all input to pad it with leading
+ // zeros.
+ return new ZeroPaddingEncryptionStreamer(
+ super.createMainDataStreamer(keyStore, operationToken),
+ getModulusSizeBytes());
+ } else {
+ return super.createMainDataStreamer(keyStore, operationToken);
+ }
+ }
+
+ /**
+ * Streamer which buffers all plaintext input, then pads it with leading zeros to match
+ * modulus size, and then sends it into KeyStore to obtain ciphertext.
+ */
+ private static class ZeroPaddingEncryptionStreamer
+ implements KeyStoreCryptoOperationStreamer {
+
+ private final KeyStoreCryptoOperationStreamer mDelegate;
+ private final int mModulusSizeBytes;
+ private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
+ private long mConsumedInputSizeBytes;
+
+ private ZeroPaddingEncryptionStreamer(
+ KeyStoreCryptoOperationStreamer delegate,
+ int modulusSizeBytes) {
+ mDelegate = delegate;
+ mModulusSizeBytes = modulusSizeBytes;
+ }
+
+ @Override
+ public byte[] update(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ if (inputLength > 0) {
+ mInputBuffer.write(input, inputOffset, inputLength);
+ mConsumedInputSizeBytes += inputLength;
+ }
+ return EmptyArray.BYTE;
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
+ if (inputLength > 0) {
+ mConsumedInputSizeBytes += inputLength;
+ mInputBuffer.write(input, inputOffset, inputLength);
+ }
+ byte[] bufferedInput = mInputBuffer.toByteArray();
+ mInputBuffer.reset();
+ byte[] paddedInput;
+ if (bufferedInput.length < mModulusSizeBytes) {
+ // Pad input with leading zeros
+ paddedInput = new byte[mModulusSizeBytes];
+ System.arraycopy(
+ bufferedInput, 0,
+ paddedInput,
+ paddedInput.length - bufferedInput.length,
+ bufferedInput.length);
+ } else {
+ // RI throws BadPaddingException in this scenario. INVALID_ARGUMENT below will
+ // be translated into BadPaddingException.
+ throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_ARGUMENT,
+ "Message size (" + bufferedInput.length + " bytes) must be smaller than"
+ + " modulus (" + mModulusSizeBytes + " bytes)");
+ }
+ return mDelegate.doFinal(paddedInput, 0, paddedInput.length, signature,
+ additionalEntropy);
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mDelegate.getProducedOutputSizeBytes();
+ }
+ }
+ }
+
+ /**
+ * RSA cipher with PKCS#1 v1.5 encryption padding.
+ */
+ public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi {
+ public PKCS1Padding() {
+ super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
+ }
+
+ @Override
+ protected boolean adjustConfigForEncryptingWithPrivateKey() {
+ // RSA encryption with PCKS#1 padding using private key is a way to implement RSA
+ // signatures with PKCS#1 padding. We have to support this for legacy reasons.
+ setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
+ setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
+ return true;
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unexpected parameters: " + params + ". No parameters supported");
+ }
+ }
+
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ return null;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return (isEncrypting()) ? getModulusSizeBytes() : 0;
+ }
+ }
+
+ /**
+ * RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF.
+ */
+ abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
+
+ private static final String MGF_ALGORITGM_MGF1 = "MGF1";
+
+ private int mKeymasterDigest = -1;
+ private int mDigestOutputSizeBytes;
+
+ OAEPWithMGF1Padding(int keymasterDigest) {
+ super(KeymasterDefs.KM_PAD_RSA_OAEP);
+ mKeymasterDigest = keymasterDigest;
+ mDigestOutputSizeBytes =
+ (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {}
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(
+ @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ return;
+ }
+
+ if (!(params instanceof OAEPParameterSpec)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported parameter spec: " + params
+ + ". Only OAEPParameterSpec supported");
+ }
+ OAEPParameterSpec spec = (OAEPParameterSpec) params;
+ if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported MGF: " + spec.getMGFAlgorithm()
+ + ". Only " + MGF_ALGORITGM_MGF1 + " supported");
+ }
+ String jcaDigest = spec.getDigestAlgorithm();
+ int keymasterDigest;
+ try {
+ keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported digest: " + jcaDigest, e);
+ }
+ switch (keymasterDigest) {
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ // Permitted.
+ break;
+ default:
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported digest: " + jcaDigest);
+ }
+ AlgorithmParameterSpec mgfParams = spec.getMGFParameters();
+ if (mgfParams == null) {
+ throw new InvalidAlgorithmParameterException("MGF parameters must be provided");
+ }
+ // Check whether MGF parameters match the OAEPParameterSpec
+ if (!(mgfParams instanceof MGF1ParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Unsupported MGF parameters"
+ + ": " + mgfParams + ". Only MGF1ParameterSpec supported");
+ }
+ MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams;
+ String mgf1JcaDigest = mgfSpec.getDigestAlgorithm();
+ if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported MGF1 digest: " + mgf1JcaDigest
+ + ". Only " + KeyProperties.DIGEST_SHA1 + " supported");
+ }
+ PSource pSource = spec.getPSource();
+ if (!(pSource instanceof PSource.PSpecified)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported source of encoding input P: " + pSource
+ + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
+ }
+ PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource;
+ byte[] pSourceValue = pSourceSpecified.getValue();
+ if ((pSourceValue != null) && (pSourceValue.length > 0)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported source of encoding input P: " + pSource
+ + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
+ }
+ mKeymasterDigest = keymasterDigest;
+ mDigestOutputSizeBytes =
+ (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ return;
+ }
+
+ OAEPParameterSpec spec;
+ try {
+ spec = params.getParameterSpec(OAEPParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ + ", but not found in parameters: " + params, e);
+ }
+ if (spec == null) {
+ throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ + ", but not provided in parameters: " + params);
+ }
+ initAlgorithmSpecificParameters(spec);
+ }
+
+ @Override
+ protected final AlgorithmParameters engineGetParameters() {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec(
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
+ MGF_ALGORITGM_MGF1,
+ MGF1ParameterSpec.SHA1,
+ PSource.PSpecified.DEFAULT);
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
+ params.init(spec);
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain OAEP AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize OAEP AlgorithmParameters with an IV",
+ e);
+ }
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ KeymasterArguments keymasterArgs) {
+ super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs);
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
+ }
+ }
+
+ public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA1AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA224AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA256AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA384AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding {
+ public OAEPWithSHA512AndMGF1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final int mKeymasterPadding;
+ private int mKeymasterPaddingOverride;
+
+ private int mModulusSizeBytes = -1;
+
+ AndroidKeyStoreRSACipherSpi(int keymasterPadding) {
+ mKeymasterPadding = keymasterPadding;
+ }
+
+ @Override
+ protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Unsupported key: null");
+ }
+ if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+ }
+ AndroidKeyStoreKey keystoreKey;
+ if (key instanceof AndroidKeyStorePrivateKey) {
+ keystoreKey = (AndroidKeyStoreKey) key;
+ } else if (key instanceof AndroidKeyStorePublicKey) {
+ keystoreKey = (AndroidKeyStoreKey) key;
+ } else {
+ throw new InvalidKeyException("Unsupported key type: " + key);
+ }
+
+ if (keystoreKey instanceof PrivateKey) {
+ // Private key
+ switch (opmode) {
+ case Cipher.DECRYPT_MODE:
+ case Cipher.UNWRAP_MODE:
+ // Permitted
+ break;
+ case Cipher.ENCRYPT_MODE:
+ case Cipher.WRAP_MODE:
+ if (!adjustConfigForEncryptingWithPrivateKey()) {
+ throw new InvalidKeyException(
+ "RSA private keys cannot be used with " + opmodeToString(opmode)
+ + " and padding "
+ + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ + ". Only RSA public keys supported for this mode");
+ }
+ break;
+ default:
+ throw new InvalidKeyException(
+ "RSA private keys cannot be used with opmode: " + opmode);
+ }
+ } else {
+ // Public key
+ switch (opmode) {
+ case Cipher.ENCRYPT_MODE:
+ case Cipher.WRAP_MODE:
+ // Permitted
+ break;
+ case Cipher.DECRYPT_MODE:
+ case Cipher.UNWRAP_MODE:
+ throw new InvalidKeyException(
+ "RSA public keys cannot be used with " + opmodeToString(opmode)
+ + " and padding "
+ + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ + ". Only RSA private keys supported for this opmode.");
+ // break;
+ default:
+ throw new InvalidKeyException(
+ "RSA public keys cannot be used with " + opmodeToString(opmode));
+ }
+ }
+
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode = getKeyStore().getKeyCharacteristics(
+ keystoreKey.getAlias(), null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw getKeyStore().getInvalidKeyException(keystoreKey.getAlias(), errorCode);
+ }
+ long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
+ if (keySizeBits == -1) {
+ throw new InvalidKeyException("Size of key not known");
+ } else if (keySizeBits > Integer.MAX_VALUE) {
+ throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
+ }
+ mModulusSizeBytes = (int) ((keySizeBits + 7) / 8);
+
+ setKey(keystoreKey);
+ }
+
+ /**
+ * Adjusts the configuration of this cipher for encrypting using the private key.
+ *
+ * <p>The default implementation does nothing and refuses to adjust the configuration.
+ *
+ * @return {@code true} if the configuration has been adjusted, {@code false} if encrypting
+ * using private key is not permitted for this cipher.
+ */
+ protected boolean adjustConfigForEncryptingWithPrivateKey() {
+ return false;
+ }
+
+ @Override
+ protected final void resetAll() {
+ mModulusSizeBytes = -1;
+ mKeymasterPaddingOverride = -1;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ int keymasterPadding = getKeymasterPaddingOverride();
+ if (keymasterPadding == -1) {
+ keymasterPadding = mKeymasterPadding;
+ }
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
+ int purposeOverride = getKeymasterPurposeOverride();
+ if ((purposeOverride != -1)
+ && ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN)
+ || (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) {
+ // Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE.
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
+ }
+ }
+
+ @Override
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ }
+
+ @Override
+ protected final int engineGetBlockSize() {
+ // Not a block cipher, according to the RI
+ return 0;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ // IV never used
+ return null;
+ }
+
+ @Override
+ protected final int engineGetOutputSize(int inputLen) {
+ return getModulusSizeBytes();
+ }
+
+ protected final int getModulusSizeBytes() {
+ if (mModulusSizeBytes == -1) {
+ throw new IllegalStateException("Not initialized");
+ }
+ return mModulusSizeBytes;
+ }
+
+ /**
+ * Overrides the default padding of the crypto operation.
+ */
+ protected final void setKeymasterPaddingOverride(int keymasterPadding) {
+ mKeymasterPaddingOverride = keymasterPadding;
+ }
+
+ protected final int getKeymasterPaddingOverride() {
+ return mKeymasterPaddingOverride;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java
new file mode 100644
index 0000000..179ffd8
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java
@@ -0,0 +1,41 @@
+/*
+ * 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.keystore;
+
+import java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAKey;
+
+/**
+ * RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey {
+
+ private final BigInteger mModulus;
+
+ public AndroidKeyStoreRSAPrivateKey(String alias, BigInteger modulus) {
+ super(alias, KeyProperties.KEY_ALGORITHM_RSA);
+ mModulus = modulus;
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java
new file mode 100644
index 0000000..08a173e
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPublicKey.java
@@ -0,0 +1,55 @@
+/*
+ * 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.keystore;
+
+import java.math.BigInteger;
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * {@link RSAPublicKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey {
+ private final BigInteger mModulus;
+ private final BigInteger mPublicExponent;
+
+ public AndroidKeyStoreRSAPublicKey(String alias, byte[] x509EncodedForm, BigInteger modulus,
+ BigInteger publicExponent) {
+ super(alias, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm);
+ mModulus = modulus;
+ mPublicExponent = publicExponent;
+ }
+
+ public AndroidKeyStoreRSAPublicKey(String alias, RSAPublicKey info) {
+ this(alias, info.getEncoded(), info.getModulus(), info.getPublicExponent());
+ if (!"X.509".equalsIgnoreCase(info.getFormat())) {
+ throw new IllegalArgumentException(
+ "Unsupported key export format: " + info.getFormat());
+ }
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+
+ @Override
+ public BigInteger getPublicExponent() {
+ return mPublicExponent;
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java
new file mode 100644
index 0000000..ecfc97e
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java
@@ -0,0 +1,164 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.InvalidKeyException;
+import java.security.SignatureSpi;
+
+/**
+ * Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
+
+ abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi {
+ PKCS1Padding(int keymasterDigest) {
+ super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForSign() {
+ // No entropy required for this deterministic signature scheme.
+ return 0;
+ }
+ }
+
+ public static final class NONEWithPKCS1Padding extends PKCS1Padding {
+ public NONEWithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_NONE);
+ }
+ }
+
+ public static final class MD5WithPKCS1Padding extends PKCS1Padding {
+ public MD5WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_MD5);
+ }
+ }
+
+ public static final class SHA1WithPKCS1Padding extends PKCS1Padding {
+ public SHA1WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static final class SHA224WithPKCS1Padding extends PKCS1Padding {
+ public SHA224WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static final class SHA256WithPKCS1Padding extends PKCS1Padding {
+ public SHA256WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static final class SHA384WithPKCS1Padding extends PKCS1Padding {
+ public SHA384WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static final class SHA512WithPKCS1Padding extends PKCS1Padding {
+ public SHA512WithPKCS1Padding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi {
+ private static final int SALT_LENGTH_BYTES = 20;
+
+ PSSPadding(int keymasterDigest) {
+ super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PSS);
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForSign() {
+ return SALT_LENGTH_BYTES;
+ }
+ }
+
+ public static final class SHA1WithPSSPadding extends PSSPadding {
+ public SHA1WithPSSPadding() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static final class SHA224WithPSSPadding extends PSSPadding {
+ public SHA224WithPSSPadding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static final class SHA256WithPSSPadding extends PSSPadding {
+ public SHA256WithPSSPadding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static final class SHA384WithPSSPadding extends PSSPadding {
+ public SHA384WithPSSPadding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static final class SHA512WithPSSPadding extends PSSPadding {
+ public SHA512WithPSSPadding() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final int mKeymasterDigest;
+ private final int mKeymasterPadding;
+
+ AndroidKeyStoreRSASignatureSpi(int keymasterDigest, int keymasterPadding) {
+ mKeymasterDigest = keymasterDigest;
+ mKeymasterPadding = keymasterPadding;
+ }
+
+ @Override
+ protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
+ if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+ }
+ super.initKey(key);
+ }
+
+ @Override
+ protected final void resetAll() {
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java
new file mode 100644
index 0000000..af354ab
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.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.keystore;
+
+import javax.crypto.SecretKey;
+
+/**
+ * {@link SecretKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey {
+
+ public AndroidKeyStoreSecretKey(String alias, String algorithm) {
+ super(alias, algorithm);
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
new file mode 100644
index 0000000..9a2f908
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -0,0 +1,199 @@
+/*
+ * 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.keystore;
+
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.InvalidKeyException;
+import java.security.ProviderException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactorySpi;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link SecretKeyFactorySpi} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreSecretKeyFactorySpi 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 AndroidKeyStoreSecretKey)) {
+ 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 (!KeyInfo.class.equals(keySpecClass)) {
+ throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
+ }
+ String keyAliasInKeystore = ((AndroidKeyStoreKey) 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);
+ }
+
+ return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore);
+ }
+
+ static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore) {
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode =
+ keyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException("Failed to obtain information about key."
+ + " Keystore error: " + errorCode);
+ }
+
+ boolean insideSecureHardware;
+ @KeyProperties.OriginEnum int origin;
+ int keySize;
+ @KeyProperties.PurposeEnum int purposes;
+ String[] encryptionPaddings;
+ String[] signaturePaddings;
+ @KeyProperties.DigestEnum String[] digests;
+ @KeyProperties.BlockModeEnum String[] blockModes;
+ int keymasterSwEnforcedUserAuthenticators;
+ int keymasterHwEnforcedUserAuthenticators;
+ try {
+ if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ insideSecureHardware = true;
+ origin = KeyProperties.Origin.fromKeymaster(
+ keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ insideSecureHardware = false;
+ origin = KeyProperties.Origin.fromKeymaster(
+ keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else {
+ throw new ProviderException("Key origin not available");
+ }
+ long keySizeUnsigned =
+ keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
+ if (keySizeUnsigned == -1) {
+ throw new ProviderException("Key size not available");
+ } else if (keySizeUnsigned > Integer.MAX_VALUE) {
+ throw new ProviderException("Key too large: " + keySizeUnsigned + " bits");
+ }
+ keySize = (int) keySizeUnsigned;
+ purposes = KeyProperties.Purpose.allFromKeymaster(
+ keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PURPOSE));
+
+ List<String> encryptionPaddingsList = new ArrayList<String>();
+ List<String> signaturePaddingsList = new ArrayList<String>();
+ // Keymaster stores both types of paddings in the same array -- we split it into two.
+ for (int keymasterPadding : keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PADDING)) {
+ try {
+ @KeyProperties.EncryptionPaddingEnum String jcaPadding =
+ KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
+ encryptionPaddingsList.add(jcaPadding);
+ } catch (IllegalArgumentException e) {
+ try {
+ @KeyProperties.SignaturePaddingEnum String padding =
+ KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding);
+ signaturePaddingsList.add(padding);
+ } catch (IllegalArgumentException e2) {
+ throw new ProviderException(
+ "Unsupported encryption padding: " + keymasterPadding);
+ }
+ }
+
+ }
+ encryptionPaddings =
+ encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]);
+ signaturePaddings =
+ signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]);
+
+ digests = KeyProperties.Digest.allFromKeymaster(
+ keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST));
+ blockModes = KeyProperties.BlockMode.allFromKeymaster(
+ keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_BLOCK_MODE));
+ keymasterSwEnforcedUserAuthenticators =
+ keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ keymasterHwEnforcedUserAuthenticators =
+ keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ } catch (IllegalArgumentException e) {
+ throw new ProviderException("Unsupported key characteristic", e);
+ }
+
+ Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
+ Date keyValidityForOriginationEnd =
+ keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME);
+ Date keyValidityForConsumptionEnd =
+ keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME);
+ boolean userAuthenticationRequired =
+ !keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+ long userAuthenticationValidityDurationSeconds =
+ keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, -1);
+ if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) {
+ throw new ProviderException("User authentication timeout validity too long: "
+ + userAuthenticationValidityDurationSeconds + " seconds");
+ }
+ boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
+ && (keymasterHwEnforcedUserAuthenticators != 0)
+ && (keymasterSwEnforcedUserAuthenticators == 0);
+
+ return new KeyInfo(entryAlias,
+ insideSecureHardware,
+ origin,
+ keySize,
+ keyValidityStart,
+ keyValidityForOriginationEnd,
+ keyValidityForConsumptionEnd,
+ purposes,
+ encryptionPaddings,
+ signaturePaddings,
+ digests,
+ blockModes,
+ userAuthenticationRequired,
+ (int) userAuthenticationValidityDurationSeconds,
+ userAuthenticationRequirementEnforcedBySecureHardware);
+ }
+
+ @Override
+ protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
+ throw new UnsupportedOperationException(
+ "To generate secret key in Android KeyStore, use KeyGenerator initialized with "
+ + KeyGenParameterSpec.class.getName());
+ }
+
+ @Override
+ protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
+ throw new UnsupportedOperationException(
+ "To import a secret key into Android KeyStore, use KeyStore.setEntry with "
+ + KeyProtection.class.getName());
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java
new file mode 100644
index 0000000..76240dd
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java
@@ -0,0 +1,431 @@
+/*
+ * 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.keystore;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import libcore.util.EmptyArray;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+
+/**
+ * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
+ implements KeyStoreCryptoOperation {
+ private final KeyStore mKeyStore;
+
+ // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin
+ // and should be preserved after SignatureSpi.engineSign/engineVerify finishes.
+ private boolean mSigning;
+ private AndroidKeyStoreKey mKey;
+
+ /**
+ * Token referencing this operation inside keystore service. It is initialized by
+ * {@code engineInitSign}/{@code engineInitVerify} and is invalidated when
+ * {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between.
+ */
+ private IBinder mOperationToken;
+ private long mOperationHandle;
+ private KeyStoreCryptoOperationStreamer mMessageStreamer;
+
+ /**
+ * Encountered exception which could not be immediately thrown because it was encountered inside
+ * a method that does not throw checked exception. This exception will be thrown from
+ * {@code engineSign} or {@code engineVerify}. Once such an exception is encountered,
+ * {@code engineUpdate} starts ignoring input data.
+ */
+ private Exception mCachedException;
+
+ AndroidKeyStoreSignatureSpiBase() {
+ mKeyStore = KeyStore.getInstance();
+ }
+
+ @Override
+ protected final void engineInitSign(PrivateKey key) throws InvalidKeyException {
+ engineInitSign(key, null);
+ }
+
+ @Override
+ protected final void engineInitSign(PrivateKey privateKey, SecureRandom random)
+ throws InvalidKeyException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if (privateKey == null) {
+ throw new InvalidKeyException("Unsupported key: null");
+ }
+ AndroidKeyStoreKey keystoreKey;
+ if (privateKey instanceof AndroidKeyStorePrivateKey) {
+ keystoreKey = (AndroidKeyStoreKey) privateKey;
+ } else {
+ throw new InvalidKeyException("Unsupported private key type: " + privateKey);
+ }
+ mSigning = true;
+ initKey(keystoreKey);
+ appRandom = random;
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ @Override
+ protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if (publicKey == null) {
+ throw new InvalidKeyException("Unsupported key: null");
+ }
+ AndroidKeyStoreKey keystoreKey;
+ if (publicKey instanceof AndroidKeyStorePublicKey) {
+ keystoreKey = (AndroidKeyStorePublicKey) publicKey;
+ } else {
+ throw new InvalidKeyException("Unsupported public key type: " + publicKey);
+ }
+ mSigning = false;
+ initKey(keystoreKey);
+ appRandom = null;
+ ensureKeystoreOperationInitialized();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ /**
+ * Configures this signature instance to use the provided key.
+ *
+ * @throws InvalidKeyException if the {@code key} is not suitable.
+ */
+ @CallSuper
+ protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
+ mKey = key;
+ }
+
+ /**
+ * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
+ * cipher instance.
+ *
+ * <p>Subclasses storing additional state should override this method, reset the additional
+ * state, and then chain to superclass.
+ */
+ @CallSuper
+ protected void resetAll() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mSigning = false;
+ mKey = null;
+ appRandom = null;
+ mOperationToken = null;
+ mOperationHandle = 0;
+ mMessageStreamer = null;
+ mCachedException = null;
+ }
+
+ /**
+ * Resets this cipher while preserving the initialized state. This must be equivalent to
+ * rolling back the cipher's state to just after the most recent {@code engineInit} completed
+ * successfully.
+ *
+ * <p>Subclasses storing additional post-init state should override this method, reset the
+ * additional state, and then chain to superclass.
+ */
+ @CallSuper
+ protected void resetWhilePreservingInitState() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mOperationHandle = 0;
+ mMessageStreamer = null;
+ mCachedException = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
+ if (mMessageStreamer != null) {
+ return;
+ }
+ if (mCachedException != null) {
+ return;
+ }
+ if (mKey == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ KeymasterArguments keymasterInputArgs = new KeymasterArguments();
+ addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
+
+ OperationResult opResult = mKeyStore.begin(
+ mKey.getAlias(),
+ mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY,
+ true, // permit aborting this operation if keystore runs out of resources
+ keymasterInputArgs,
+ null // no additional entropy for begin -- only finish might need some
+ );
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ }
+
+ // Store operation token and handle regardless of the error code returned by KeyStore to
+ // ensure that the operation gets aborted immediately if the code below throws an exception.
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
+
+ // If necessary, throw an exception due to KeyStore operation having failed.
+ InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
+ mKeyStore, mKey, opResult.resultCode);
+ if (e != null) {
+ throw e;
+ }
+
+ if (mOperationToken == null) {
+ throw new ProviderException("Keystore returned null operation token");
+ }
+ if (mOperationHandle == 0) {
+ throw new ProviderException("Keystore returned invalid operation handle");
+ }
+
+ mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token);
+ }
+
+ /**
+ * Creates a streamer which sends the message to be signed/verified into the provided KeyStore
+ *
+ * <p>This implementation returns a working streamer.
+ */
+ @NonNull
+ protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
+ KeyStore keyStore, IBinder operationToken) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ keyStore, operationToken));
+ }
+
+ @Override
+ public final long getOperationHandle() {
+ return mOperationHandle;
+ }
+
+ @Override
+ protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
+ if (mCachedException != null) {
+ throw new SignatureException(mCachedException);
+ }
+
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException e) {
+ throw new SignatureException(e);
+ }
+
+ if (len == 0) {
+ return;
+ }
+
+ byte[] output;
+ try {
+ output = mMessageStreamer.update(b, off, len);
+ } catch (KeyStoreException e) {
+ throw new SignatureException(e);
+ }
+
+ if (output.length != 0) {
+ throw new ProviderException(
+ "Update operation unexpectedly produced output: " + output.length + " bytes");
+ }
+ }
+
+ @Override
+ protected final void engineUpdate(byte b) throws SignatureException {
+ engineUpdate(new byte[] {b}, 0, 1);
+ }
+
+ @Override
+ protected final void engineUpdate(ByteBuffer input) {
+ byte[] b;
+ int off;
+ int len = input.remaining();
+ if (input.hasArray()) {
+ b = input.array();
+ off = input.arrayOffset() + input.position();
+ input.position(input.limit());
+ } else {
+ b = new byte[len];
+ off = 0;
+ input.get(b);
+ }
+
+ try {
+ engineUpdate(b, off, len);
+ } catch (SignatureException e) {
+ mCachedException = e;
+ }
+ }
+
+ @Override
+ protected final int engineSign(byte[] out, int outOffset, int outLen)
+ throws SignatureException {
+ return super.engineSign(out, outOffset, outLen);
+ }
+
+ @Override
+ protected final byte[] engineSign() throws SignatureException {
+ if (mCachedException != null) {
+ throw new SignatureException(mCachedException);
+ }
+
+ byte[] signature;
+ try {
+ ensureKeystoreOperationInitialized();
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ appRandom, getAdditionalEntropyAmountForSign());
+ signature = mMessageStreamer.doFinal(
+ EmptyArray.BYTE, 0, 0,
+ null, // no signature provided -- it'll be generated by this invocation
+ additionalEntropy);
+ } catch (InvalidKeyException | KeyStoreException e) {
+ throw new SignatureException(e);
+ }
+
+ resetWhilePreservingInitState();
+ return signature;
+ }
+
+ @Override
+ protected final boolean engineVerify(byte[] signature) throws SignatureException {
+ if (mCachedException != null) {
+ throw new SignatureException(mCachedException);
+ }
+
+ try {
+ ensureKeystoreOperationInitialized();
+ } catch (InvalidKeyException e) {
+ throw new SignatureException(e);
+ }
+
+ boolean verified;
+ try {
+ byte[] output = mMessageStreamer.doFinal(
+ EmptyArray.BYTE, 0, 0,
+ signature,
+ null // no additional entropy needed -- verification is deterministic
+ );
+ if (output.length != 0) {
+ throw new ProviderException(
+ "Signature verification unexpected produced output: " + output.length
+ + " bytes");
+ }
+ verified = true;
+ } catch (KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
+ verified = false;
+ break;
+ default:
+ throw new SignatureException(e);
+ }
+ }
+
+ resetWhilePreservingInitState();
+ return verified;
+ }
+
+ @Override
+ protected final boolean engineVerify(byte[] sigBytes, int offset, int len)
+ throws SignatureException {
+ return engineVerify(ArrayUtils.subarray(sigBytes, offset, len));
+ }
+
+ @Deprecated
+ @Override
+ protected final Object engineGetParameter(String param) throws InvalidParameterException {
+ throw new InvalidParameterException();
+ }
+
+ @Deprecated
+ @Override
+ protected final void engineSetParameter(String param, Object value)
+ throws InvalidParameterException {
+ throw new InvalidParameterException();
+ }
+
+ protected final KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ /**
+ * Returns {@code true} if this signature is initialized for signing, {@code false} if this
+ * signature is initialized for verification.
+ */
+ protected final boolean isSigning() {
+ return mSigning;
+ }
+
+ // The methods below need to be implemented by subclasses.
+
+ /**
+ * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
+ * {@code finish} operation when generating a signature.
+ *
+ * <p>This value should match (or exceed) the amount of Shannon entropy of the produced
+ * signature assuming the key and the message are known. For example, for ECDSA signature this
+ * should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this
+ * should be {@code 0}.
+ */
+ protected abstract int getAdditionalEntropyAmountForSign();
+
+ /**
+ * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
+ *
+ * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected abstract void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs);
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
new file mode 100644
index 0000000..e9f19cd
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2012 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.keystore;
+
+import libcore.util.EmptyArray;
+
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.KeyStoreParameter;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore.Entry;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.KeyStore.ProtectionParameter;
+import java.security.KeyStore.SecretKeyEntry;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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)
+ * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
+ * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
+ * <p>
+ * This is built on top of Android's keystore daemon. The convention of alias
+ * use is:
+ * <p>
+ * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
+ * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
+ * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
+ * entry which will have the rest of the chain concatenated in BER format.
+ * <p>
+ * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
+ * with a single certificate.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreSpi extends KeyStoreSpi {
+ public static final String NAME = "AndroidKeyStore";
+
+ private KeyStore mKeyStore;
+
+ @Override
+ public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
+ UnrecoverableKeyException {
+ if (isPrivateKeyEntry(alias)) {
+ String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+ return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ mKeyStore, privateKeyAlias);
+ } else if (isSecretKeyEntry(alias)) {
+ String secretKeyAlias = Credentials.USER_SECRET_KEY + alias;
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(
+ mKeyStore, secretKeyAlias);
+ } else {
+ // Key not found
+ return null;
+ }
+ }
+
+ @Override
+ public Certificate[] engineGetCertificateChain(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
+ if (leaf == null) {
+ return null;
+ }
+
+ final Certificate[] caList;
+
+ final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
+ if (caBytes != null) {
+ final Collection<X509Certificate> caChain = toCertificates(caBytes);
+
+ caList = new Certificate[caChain.size() + 1];
+
+ final Iterator<X509Certificate> it = caChain.iterator();
+ int i = 1;
+ while (it.hasNext()) {
+ caList[i++] = it.next();
+ }
+ } else {
+ caList = new Certificate[1];
+ }
+
+ caList[0] = leaf;
+
+ return caList;
+ }
+
+ @Override
+ public Certificate engineGetCertificate(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
+ if (encodedCert != null) {
+ return getCertificateForPrivateKeyEntry(alias, encodedCert);
+ }
+
+ encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
+ if (encodedCert != null) {
+ return getCertificateForTrustedCertificateEntry(encodedCert);
+ }
+
+ // This entry/alias does not contain a certificate.
+ return null;
+ }
+
+ private Certificate getCertificateForTrustedCertificateEntry(byte[] encodedCert) {
+ // For this certificate there shouldn't be a private key in this KeyStore entry. Thus,
+ // there's no need to wrap this certificate as opposed to the certificate associated with
+ // a private key entry.
+ return toCertificate(encodedCert);
+ }
+
+ private Certificate getCertificateForPrivateKeyEntry(String alias, byte[] encodedCert) {
+ // All crypto algorithms offered by Android Keystore for its private keys must also
+ // be offered for the corresponding public keys stored in the Android Keystore. The
+ // complication is that the underlying keystore service operates only on full key pairs,
+ // rather than just public keys or private keys. As a result, Android Keystore-backed
+ // crypto can only be offered for public keys for which keystore contains the
+ // corresponding private key. This is not the case for certificate-only entries (e.g.,
+ // trusted certificates).
+ //
+ // getCertificate().getPublicKey() is the only way to obtain the public key
+ // corresponding to the private key stored in the KeyStore. Thus, we need to make sure
+ // that the returned public key points to the underlying key pair / private key
+ // when available.
+
+ X509Certificate cert = toCertificate(encodedCert);
+ if (cert == null) {
+ // Failed to parse the certificate.
+ return null;
+ }
+
+ String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+ if (mKeyStore.contains(privateKeyAlias)) {
+ // As expected, keystore contains the private key corresponding to this public key. Wrap
+ // the certificate so that its getPublicKey method returns an Android Keystore
+ // PublicKey. This key will delegate crypto operations involving this public key to
+ // Android Keystore when higher-priority providers do not offer these crypto
+ // operations for this key.
+ return wrapIntoKeyStoreCertificate(privateKeyAlias, cert);
+ } else {
+ // This KeyStore entry/alias is supposed to contain the private key corresponding to
+ // the public key in this certificate, but it does not for some reason. It's probably a
+ // bug. Let other providers handle crypto operations involving the public key returned
+ // by this certificate's getPublicKey.
+ return cert;
+ }
+ }
+
+ /**
+ * Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key
+ * returned by the certificate contains information about the alias of the private key in
+ * keystore. This is needed so that Android Keystore crypto operations using public keys can
+ * find out which key alias to use. These operations cannot work without an alias.
+ */
+ private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate(
+ String privateKeyAlias, X509Certificate certificate) {
+ return (certificate != null)
+ ? new KeyStoreX509Certificate(privateKeyAlias, certificate) : null;
+ }
+
+ private static X509Certificate toCertificate(byte[] bytes) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ Log.w(NAME, "Couldn't parse certificate in keystore", e);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Collection<X509Certificate> toCertificates(byte[] bytes) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (Collection<X509Certificate>) certFactory.generateCertificates(
+ new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ Log.w(NAME, "Couldn't parse certificates in keystore", e);
+ return new ArrayList<X509Certificate>();
+ }
+ }
+
+ private Date getModificationDate(String alias) {
+ final long epochMillis = mKeyStore.getmtime(alias);
+ if (epochMillis == -1L) {
+ return null;
+ }
+
+ return new Date(epochMillis);
+ }
+
+ @Override
+ public Date engineGetCreationDate(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
+ if (d != null) {
+ 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;
+ }
+
+ return getModificationDate(Credentials.CA_CERTIFICATE + alias);
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
+ throws KeyStoreException {
+ if ((password != null) && (password.length > 0)) {
+ throw new KeyStoreException("entries cannot be protected with passwords");
+ }
+
+ 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 PrivateKey and SecretKey are supported");
+ }
+ }
+
+ private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key)
+ throws KeyStoreException {
+ String keyAlgorithm = key.getAlgorithm();
+ KeyProtection.Builder specBuilder;
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT
+ | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(KeyProperties.DIGEST_NONE);
+ // Authorized to be used with any encryption and signature padding scheme (including no
+ // padding).
+ specBuilder.setEncryptionPaddings(
+ KeyProperties.ENCRYPTION_PADDING_NONE);
+ // Disable randomized encryption requirement to support encryption padding NONE
+ // above.
+ specBuilder.setRandomizedEncryptionRequired(false);
+ } else {
+ throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ specBuilder.setUserAuthenticationRequired(false);
+
+ return specBuilder.build();
+ }
+
+ private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
+ java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
+ int flags = 0;
+ KeyProtection spec;
+ if (param == null) {
+ spec = getLegacyKeyProtectionParameter(key);
+ } else if (param instanceof KeyStoreParameter) {
+ spec = getLegacyKeyProtectionParameter(key);
+ KeyStoreParameter legacySpec = (KeyStoreParameter) param;
+ if (legacySpec.isEncryptionRequired()) {
+ flags = KeyStore.FLAG_ENCRYPTED;
+ }
+ } else if (param instanceof KeyProtection) {
+ spec = (KeyProtection) param;
+ } else {
+ throw new KeyStoreException(
+ "Unsupported protection parameter class:" + param.getClass().getName()
+ + ". Supported: " + KeyProtection.class.getName() + ", "
+ + KeyStoreParameter.class.getName());
+ }
+
+ // Make sure the chain exists since this is a PrivateKey
+ if ((chain == null) || (chain.length == 0)) {
+ throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
+ }
+
+ // Do chain type checking.
+ X509Certificate[] x509chain = new X509Certificate[chain.length];
+ for (int i = 0; i < chain.length; i++) {
+ if (!"X.509".equals(chain[i].getType())) {
+ throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
+ + i);
+ }
+
+ if (!(chain[i] instanceof X509Certificate)) {
+ throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
+ + i);
+ }
+
+ x509chain[i] = (X509Certificate) chain[i];
+ }
+
+ final byte[] userCertBytes;
+ try {
+ userCertBytes = x509chain[0].getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException("Failed to encode certificate #0", e);
+ }
+
+ /*
+ * If we have a chain, store it in the CA certificate slot for this
+ * alias as concatenated DER-encoded certificates. These can be
+ * deserialized by {@link CertificateFactory#generateCertificates}.
+ */
+ final byte[] chainBytes;
+ if (chain.length > 1) {
+ /*
+ * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
+ * so we only need the certificates starting at index 1.
+ */
+ final byte[][] certsBytes = new byte[x509chain.length - 1][];
+ int totalCertLength = 0;
+ for (int i = 0; i < certsBytes.length; i++) {
+ try {
+ certsBytes[i] = x509chain[i + 1].getEncoded();
+ totalCertLength += certsBytes[i].length;
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException("Failed to encode certificate #" + i, e);
+ }
+ }
+
+ /*
+ * Serialize this into one byte array so we can later call
+ * CertificateFactory#generateCertificates to recover them.
+ */
+ chainBytes = new byte[totalCertLength];
+ int outputOffset = 0;
+ for (int i = 0; i < certsBytes.length; i++) {
+ final int certLength = certsBytes[i].length;
+ System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
+ outputOffset += certLength;
+ certsBytes[i] = null;
+ }
+ } else {
+ chainBytes = null;
+ }
+
+ final String pkeyAlias;
+ if (key instanceof AndroidKeyStorePrivateKey) {
+ pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
+ } else {
+ pkeyAlias = null;
+ }
+
+ byte[] pkcs8EncodedPrivateKeyBytes;
+ KeymasterArguments importArgs;
+ final boolean shouldReplacePrivateKey;
+ if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
+ final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
+ if (!alias.equals(keySubalias)) {
+ throw new KeyStoreException("Can only replace keys with same alias: " + alias
+ + " != " + keySubalias);
+ }
+ shouldReplacePrivateKey = false;
+ importArgs = null;
+ pkcs8EncodedPrivateKeyBytes = null;
+ } else {
+ shouldReplacePrivateKey = true;
+ // Make sure the PrivateKey format is the one we support.
+ final String keyFormat = key.getFormat();
+ if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
+ throw new KeyStoreException(
+ "Unsupported private key export format: " + keyFormat
+ + ". Only private keys which export their key material in PKCS#8 format are"
+ + " supported.");
+ }
+
+ // Make sure we can actually encode the key.
+ pkcs8EncodedPrivateKeyBytes = key.getEncoded();
+ if (pkcs8EncodedPrivateKeyBytes == null) {
+ throw new KeyStoreException("Private key did not export any key material");
+ }
+
+ importArgs = new KeymasterArguments();
+ try {
+ importArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM,
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ key.getAlgorithm()));
+ @KeyProperties.PurposeEnum int purposes = spec.getPurposes();
+ importArgs.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
+ KeyProperties.Purpose.allToKeymaster(purposes));
+ if (spec.isDigestsSpecified()) {
+ importArgs.addEnums(KeymasterDefs.KM_TAG_DIGEST,
+ KeyProperties.Digest.allToKeymaster(spec.getDigests()));
+ }
+
+ importArgs.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE,
+ KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()));
+ int[] keymasterEncryptionPaddings =
+ KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterPadding : keymasterEncryptionPaddings) {
+ if (!KeymasterUtils
+ .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ keymasterPadding)) {
+ throw new KeyStoreException(
+ "Randomized encryption (IND-CPA) required but is violated by"
+ + " encryption padding mode: "
+ + KeyProperties.EncryptionPadding.fromKeymaster(
+ keymasterPadding)
+ + ". See KeyProtection documentation.");
+ }
+ }
+ }
+ importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings);
+ importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING,
+ KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
+ KeymasterUtils.addUserAuthArgs(importArgs,
+ spec.isUserAuthenticationRequired(),
+ spec.getUserAuthenticationValidityDurationSeconds());
+ importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ spec.getKeyValidityStart());
+ importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ spec.getKeyValidityForOriginationEnd());
+ importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ spec.getKeyValidityForConsumptionEnd());
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new KeyStoreException(e);
+ }
+ }
+
+
+ boolean success = false;
+ try {
+ // Store the private key, if necessary
+ if (shouldReplacePrivateKey) {
+ // Delete the stored private key and any related entries before importing the
+ // provided key
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.importKey(
+ Credentials.USER_PRIVATE_KEY + alias,
+ importArgs,
+ KeymasterDefs.KM_KEY_FORMAT_PKCS8,
+ pkcs8EncodedPrivateKeyBytes,
+ flags,
+ resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store private key",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ } else {
+ // Keep the stored private key around -- delete all other entry types
+ Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
+ }
+
+ // Store the leaf certificate
+ int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes,
+ KeyStore.UID_SELF, flags);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store certificate #0",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Store the certificate chain
+ errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes,
+ KeyStore.UID_SELF, flags);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store certificate chain",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ if (shouldReplacePrivateKey) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ } else {
+ Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
+ }
+ }
+ }
+ }
+
+ private void setSecretKeyEntry(String entryAlias, SecretKey key,
+ java.security.KeyStore.ProtectionParameter param)
+ throws KeyStoreException {
+ if ((param != null) && (!(param instanceof KeyProtection))) {
+ throw new KeyStoreException(
+ "Unsupported protection parameter class: " + param.getClass().getName()
+ + ". Supported: " + KeyProtection.class.getName());
+ }
+ KeyProtection params = (KeyProtection) param;
+
+ if (key instanceof AndroidKeyStoreSecretKey) {
+ // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
+ // overwrite its own entry.
+ String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) 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");
+ }
+
+ KeymasterArguments args = new KeymasterArguments();
+ try {
+ int keymasterAlgorithm =
+ KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(key.getAlgorithm());
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
+
+ int[] keymasterDigests;
+ int keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm());
+ if (params.isDigestsSpecified()) {
+ // Digest(s) specified in parameters
+ keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests());
+ if (keymasterDigest != -1) {
+ // Digest also specified in the JCA key algorithm name.
+ if (!com.android.internal.util.ArrayUtils.contains(
+ keymasterDigests, keymasterDigest)) {
+ throw new KeyStoreException("Digest specified in key algorithm "
+ + key.getAlgorithm() + " not specified in protection parameters: "
+ + Arrays.asList(params.getDigests()));
+ }
+ // When the key is read back from keystore we reconstruct the JCA key algorithm
+ // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to
+ // ensure that the digest reflected in the JCA key algorithm name is the first
+ // KM_TAG_DIGEST tag.
+ if (keymasterDigests[0] != keymasterDigest) {
+ // The first digest is not the one implied by the JCA key algorithm name.
+ // Swap the implied digest with the first one.
+ for (int i = 0; i < keymasterDigests.length; i++) {
+ if (keymasterDigests[i] == keymasterDigest) {
+ keymasterDigests[i] = keymasterDigests[0];
+ keymasterDigests[0] = keymasterDigest;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // No digest specified in parameters
+ if (keymasterDigest != -1) {
+ // Digest specified in the JCA key algorithm name.
+ keymasterDigests = new int[] {keymasterDigest};
+ } else {
+ keymasterDigests = EmptyArray.INT;
+ }
+ }
+ args.addEnums(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
+ if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+ if (keymasterDigests.length == 0) {
+ throw new KeyStoreException("At least one digest algorithm must be specified"
+ + " for key algorithm " + key.getAlgorithm());
+ }
+ }
+
+ @KeyProperties.PurposeEnum int purposes = params.getPurposes();
+ int[] keymasterBlockModes =
+ KeyProperties.BlockMode.allToKeymaster(params.getBlockModes());
+ if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (params.isRandomizedEncryptionRequired())) {
+ for (int keymasterBlockMode : keymasterBlockModes) {
+ if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ keymasterBlockMode)) {
+ throw new KeyStoreException(
+ "Randomized encryption (IND-CPA) required but may be violated by"
+ + " block mode: "
+ + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
+ + ". See KeyProtection documentation.");
+ }
+ }
+ }
+ args.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
+ KeyProperties.Purpose.allToKeymaster(purposes));
+ args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
+ if (params.getSignaturePaddings().length > 0) {
+ throw new KeyStoreException("Signature paddings not supported for symmetric keys");
+ }
+ int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
+ params.getEncryptionPaddings());
+ args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
+ KeymasterUtils.addUserAuthArgs(args,
+ params.isUserAuthenticationRequired(),
+ params.getUserAuthenticationValidityDurationSeconds());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ params.getKeyValidityStart());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ params.getKeyValidityForOriginationEnd());
+ args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ params.getKeyValidityForConsumptionEnd());
+
+ if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (!params.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
+ args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
+ }
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new KeyStoreException(e);
+ }
+
+ Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
+ String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
+ int errorCode = mKeyStore.importKey(
+ keyAliasInKeystore,
+ args,
+ KeymasterDefs.KM_KEY_FORMAT_RAW,
+ keyMaterial,
+ 0, // flags
+ new KeyCharacteristics());
+ if (errorCode != 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 {
+ throw new KeyStoreException("Operation not supported because key encoding is unknown");
+ }
+
+ @Override
+ public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
+ if (isKeyEntry(alias)) {
+ throw new KeyStoreException("Entry exists and is not a trusted certificate");
+ }
+
+ // We can't set something to null.
+ if (cert == null) {
+ throw new NullPointerException("cert == null");
+ }
+
+ final byte[] encoded;
+ try {
+ encoded = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException(e);
+ }
+
+ if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
+ KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
+ throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
+ }
+ }
+
+ @Override
+ public void engineDeleteEntry(String alias) throws KeyStoreException {
+ if (!engineContainsAlias(alias)) {
+ return;
+ }
+ // At least one entry corresponding to this alias exists in keystore
+
+ if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
+ throw new KeyStoreException("Failed to delete entry: " + alias);
+ }
+ }
+
+ private Set<String> getUniqueAliases() {
+ final String[] rawAliases = mKeyStore.list("");
+ if (rawAliases == null) {
+ return new HashSet<String>();
+ }
+
+ final Set<String> aliases = new HashSet<String>(rawAliases.length);
+ for (String alias : rawAliases) {
+ final int idx = alias.indexOf('_');
+ if ((idx == -1) || (alias.length() <= idx)) {
+ Log.e(NAME, "invalid alias: " + alias);
+ continue;
+ }
+
+ aliases.add(new String(alias.substring(idx + 1)));
+ }
+
+ return aliases;
+ }
+
+ @Override
+ public Enumeration<String> engineAliases() {
+ return Collections.enumeration(getUniqueAliases());
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ 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);
+ }
+
+ @Override
+ public int engineSize() {
+ return getUniqueAliases().size();
+ }
+
+ @Override
+ public boolean engineIsKeyEntry(String alias) {
+ return isKeyEntry(alias);
+ }
+
+ private boolean isKeyEntry(String alias) {
+ return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
+ }
+
+ private boolean isPrivateKeyEntry(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ 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");
+ }
+
+ return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
+ }
+
+ @Override
+ public boolean engineIsCertificateEntry(String alias) {
+ return !isKeyEntry(alias) && isCertificateEntry(alias);
+ }
+
+ @Override
+ public String engineGetCertificateAlias(Certificate cert) {
+ if (cert == null) {
+ return null;
+ }
+ if (!"X.509".equalsIgnoreCase(cert.getType())) {
+ // Only X.509 certificates supported
+ return null;
+ }
+ byte[] targetCertBytes;
+ try {
+ targetCertBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ return null;
+ }
+ if (targetCertBytes == null) {
+ return null;
+ }
+
+ final Set<String> nonCaEntries = new HashSet<String>();
+
+ /*
+ * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
+ * says to only compare the first certificate in the chain which is
+ * equivalent to the USER_CERTIFICATE prefix for the Android keystore
+ * convention.
+ */
+ final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE);
+ if (certAliases != null) {
+ for (String alias : certAliases) {
+ final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
+ if (certBytes == null) {
+ continue;
+ }
+
+ nonCaEntries.add(alias);
+
+ if (Arrays.equals(certBytes, targetCertBytes)) {
+ return alias;
+ }
+ }
+ }
+
+ /*
+ * Look at all the TrustedCertificateEntry types. Skip all the
+ * PrivateKeyEntry we looked at above.
+ */
+ final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE);
+ if (certAliases != null) {
+ for (String alias : caAliases) {
+ if (nonCaEntries.contains(alias)) {
+ continue;
+ }
+
+ final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
+ if (certBytes == null) {
+ continue;
+ }
+
+ if (Arrays.equals(certBytes, targetCertBytes)) {
+ return alias;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void engineStore(OutputStream stream, char[] password) throws IOException,
+ NoSuchAlgorithmException, CertificateException {
+ throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
+ }
+
+ @Override
+ public void engineLoad(InputStream stream, char[] password) throws IOException,
+ NoSuchAlgorithmException, CertificateException {
+ if (stream != null) {
+ throw new IllegalArgumentException("InputStream not supported");
+ }
+
+ if (password != null) {
+ throw new IllegalArgumentException("password not supported");
+ }
+
+ // Unfortunate name collision.
+ mKeyStore = KeyStore.getInstance();
+ }
+
+ @Override
+ public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
+ throws KeyStoreException {
+ if (entry == null) {
+ throw new KeyStoreException("entry == null");
+ }
+
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+
+ if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) {
+ java.security.KeyStore.TrustedCertificateEntry trE =
+ (java.security.KeyStore.TrustedCertificateEntry) entry;
+ engineSetCertificateEntry(alias, trE.getTrustedCertificate());
+ return;
+ }
+
+ if (entry instanceof PrivateKeyEntry) {
+ PrivateKeyEntry prE = (PrivateKeyEntry) entry;
+ setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param);
+ } else if (entry instanceof SecretKeyEntry) {
+ SecretKeyEntry secE = (SecretKeyEntry) entry;
+ setSecretKeyEntry(alias, secE.getSecretKey(), param);
+ } else {
+ throw new KeyStoreException(
+ "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
+ + "; was " + entry);
+ }
+ }
+
+ /**
+ * {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from
+ * {@link #getPublicKey()}. This is so that crypto operations on these public keys contain
+ * can find out which keystore private key entry to use. This is needed so that Android Keystore
+ * crypto operations using public keys can find out which key alias to use. These operations
+ * require an alias.
+ */
+ static class KeyStoreX509Certificate extends DelegatingX509Certificate {
+ private final String mPrivateKeyAlias;
+ KeyStoreX509Certificate(String privateKeyAlias, X509Certificate delegate) {
+ super(delegate);
+ mPrivateKeyAlias = privateKeyAlias;
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ PublicKey original = super.getPublicKey();
+ return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ mPrivateKeyAlias, original.getAlgorithm(), original.getEncoded());
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
new file mode 100644
index 0000000..486519c
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
@@ -0,0 +1,313 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations.
+ *
+ * @hide
+ */
+class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+ abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+ protected ECB(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
+ }
+
+ public static class NoPadding extends ECB {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends ECB {
+ public PKCS7Padding() {
+ super(KeymasterDefs.KM_PAD_PKCS7);
+ }
+ }
+ }
+
+ abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+ protected CBC(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
+ }
+
+ public static class NoPadding extends CBC {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends CBC {
+ public PKCS7Padding() {
+ super(KeymasterDefs.KM_PAD_PKCS7);
+ }
+ }
+ }
+
+ abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+ protected CTR(int keymasterPadding) {
+ super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
+ }
+
+ public static class NoPadding extends CTR {
+ public NoPadding() {
+ super(KeymasterDefs.KM_PAD_NONE);
+ }
+ }
+ }
+
+ private static final int BLOCK_SIZE_BYTES = 16;
+
+ private final int mKeymasterBlockMode;
+ private final int mKeymasterPadding;
+ /** Whether this transformation requires an IV. */
+ private final boolean mIvRequired;
+
+ private byte[] mIv;
+
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ AndroidKeyStoreUnauthenticatedAESCipherSpi(
+ int keymasterBlockMode,
+ int keymasterPadding,
+ boolean ivRequired) {
+ mKeymasterBlockMode = keymasterBlockMode;
+ mKeymasterPadding = keymasterPadding;
+ mIvRequired = ivRequired;
+ }
+
+ @Override
+ protected final void resetAll() {
+ mIv = null;
+ mIvHasBeenUsed = false;
+ super.resetAll();
+ }
+
+ @Override
+ protected final void resetWhilePreservingInitState() {
+ super.resetWhilePreservingInitState();
+ }
+
+ @Override
+ protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+ if (!(key instanceof AndroidKeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
+ throw new InvalidKeyException(
+ "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
+ KeyProperties.KEY_ALGORITHM_AES + " supported");
+ }
+ setKey((AndroidKeyStoreSecretKey) key);
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!mIvRequired) {
+ return;
+ }
+
+ // IV is used
+ if (!isEncrypting()) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // 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");
+ }
+ }
+
+ @Override
+ protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!isEncrypting()) {
+ // 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 (!isEncrypting()) {
+ // 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");
+ }
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForBegin() {
+ if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
+ // IV will need to be generated
+ return BLOCK_SIZE_BYTES;
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected final int getAdditionalEntropyAmountForFinish() {
+ return 0;
+ }
+
+ @Override
+ protected final void addAlgorithmSpecificParametersToBegin(
+ @NonNull KeymasterArguments keymasterArgs) {
+ if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
+ // IV is being reused for encryption: this violates security best practices.
+ throw new IllegalStateException(
+ "IV has already been used. Reusing IV in encryption mode violates security best"
+ + " practices.");
+ }
+
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
+ keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+ if ((mIvRequired) && (mIv != null)) {
+ keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ @NonNull KeymasterArguments keymasterArgs) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
+ if ((returnedIv != null) && (returnedIv.length == 0)) {
+ returnedIv = null;
+ }
+
+ if (mIvRequired) {
+ if (mIv == null) {
+ mIv = returnedIv;
+ } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+ throw new ProviderException("IV in use differs from provided IV");
+ }
+ } else {
+ if (returnedIv != null) {
+ throw new ProviderException(
+ "IV in use despite IV not being used by this transformation");
+ }
+ }
+ }
+
+ @Override
+ protected final int engineGetBlockSize() {
+ return BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected final int engineGetOutputSize(int inputLen) {
+ return inputLen + 3 * BLOCK_SIZE_BYTES;
+ }
+
+ @Override
+ protected final byte[] engineGetIV() {
+ return ArrayUtils.cloneIfNotEmpty(mIv);
+ }
+
+ @Nullable
+ @Override
+ protected final AlgorithmParameters engineGetParameters() {
+ if (!mIvRequired) {
+ 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 ProviderException(
+ "Failed to obtain AES AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException(
+ "Failed to initialize AES AlgorithmParameters with an IV",
+ e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/keystore/java/android/security/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index 2047d3f..26172d2 100644
--- a/keystore/java/android/security/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -1,11 +1,27 @@
-package android.security;
+/*
+ * 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.keystore;
import libcore.util.EmptyArray;
/**
* @hide
*/
-abstract class ArrayUtils {
+public abstract class ArrayUtils {
private ArrayUtils() {}
public static String[] nullToEmpty(String[] array) {
@@ -16,6 +32,10 @@ abstract class ArrayUtils {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
+ public static byte[] cloneIfNotEmpty(byte[] array) {
+ return ((array != null) && (array.length > 0)) ? array.clone() : array;
+ }
+
public static byte[] concat(byte[] arr1, byte[] arr2) {
return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
arr2, 0, (arr2 != null) ? arr2.length : 0);
diff --git a/keystore/java/android/security/keystore/DelegatingX509Certificate.java b/keystore/java/android/security/keystore/DelegatingX509Certificate.java
new file mode 100644
index 0000000..03d202f
--- /dev/null
+++ b/keystore/java/android/security/keystore/DelegatingX509Certificate.java
@@ -0,0 +1,212 @@
+/*
+ * 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.keystore;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+class DelegatingX509Certificate extends X509Certificate {
+ private final X509Certificate mDelegate;
+
+ DelegatingX509Certificate(X509Certificate delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return mDelegate.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return mDelegate.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return mDelegate.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return mDelegate.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity() throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ mDelegate.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date) throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ mDelegate.checkValidity(date);
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return mDelegate.getBasicConstraints();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return mDelegate.getIssuerDN();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return mDelegate.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return mDelegate.getKeyUsage();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return mDelegate.getNotAfter();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return mDelegate.getNotBefore();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return mDelegate.getSerialNumber();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return mDelegate.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return mDelegate.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return mDelegate.getSigAlgParams();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return mDelegate.getSignature();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return mDelegate.getSubjectDN();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return mDelegate.getSubjectUniqueID();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return mDelegate.getTBSCertificate();
+ }
+
+ @Override
+ public int getVersion() {
+ return mDelegate.getVersion();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mDelegate.getEncoded();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return mDelegate.getPublicKey();
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ @Override
+ public void verify(PublicKey key)
+ throws CertificateException,
+ NoSuchAlgorithmException,
+ InvalidKeyException,
+ NoSuchProviderException,
+ SignatureException {
+ mDelegate.verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException,
+ NoSuchAlgorithmException,
+ InvalidKeyException,
+ NoSuchProviderException,
+ SignatureException {
+ mDelegate.verify(key, sigProvider);
+ }
+
+ @Override
+ public List<String> getExtendedKeyUsage() throws CertificateParsingException {
+ return mDelegate.getExtendedKeyUsage();
+ }
+
+ @Override
+ public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
+ return mDelegate.getIssuerAlternativeNames();
+ }
+
+ @Override
+ public X500Principal getIssuerX500Principal() {
+ return mDelegate.getIssuerX500Principal();
+ }
+
+ @Override
+ public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+ return mDelegate.getSubjectAlternativeNames();
+ }
+
+ @Override
+ public X500Principal getSubjectX500Principal() {
+ return mDelegate.getSubjectX500Principal();
+ }
+}
diff --git a/keystore/java/android/security/KeyExpiredException.java b/keystore/java/android/security/keystore/KeyExpiredException.java
index e64bffa..15b8d67 100644
--- a/keystore/java/android/security/KeyExpiredException.java
+++ b/keystore/java/android/security/keystore/KeyExpiredException.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import java.security.InvalidKeyException;
/**
* Indicates that a cryptographic operation failed because the employed key's validity end date
* is in the past.
- *
- * @hide
*/
public class KeyExpiredException extends InvalidKeyException {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
new file mode 100644
index 0000000..919dd48
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2012 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.keystore;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.text.TextUtils;
+
+import java.math.BigInteger;
+import java.security.KeyPairGenerator;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * {@link AlgorithmParameterSpec} for initializing a {@link KeyPairGenerator} or a
+ * {@link KeyGenerator} of the <a href="{@docRoot}training/articles/keystore.html">Android Keystore
+ * system</a>. The spec determines whether user authentication is required for using the key, what
+ * uses the key is authorized for (e.g., only for signing -- decryption not permitted), the key's
+ * validity start and end dates.
+ *
+ * <p>To generate an asymmetric key pair or a symmetric key, create an instance of this class using
+ * the {@link Builder}, initialize a {@code KeyPairGenerator} or a {@code KeyGenerator} of the
+ * desired key type (e.g., {@code EC} or {@code AES} -- see
+ * {@link KeyProperties}.{@code KEY_ALGORITHM} constants) from the {@code AndroidKeyStore} provider
+ * with the {@code KeyPairGeneratorSpec} instance, and then generate a key or key pair using
+ * {@link KeyPairGenerator#generateKeyPair()}.
+ *
+ * <p>The generated key pair or key will be returned by the generator and also stored in the Android
+ * Keystore system under the alias specified in this spec. To obtain the secret or private key from
+ * the Android KeyStore use {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)}
+ * or {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}.
+ * To obtain the public key from the Android Keystore system use
+ * {@link java.security.KeyStore#getCertificate(String)} and then
+ * {@link Certificate#getPublicKey()}.
+ *
+ * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
+ * the Android KeyStore. This is because the {@link java.security.KeyStore} abstraction does not
+ * support storing key pairs without a certificate. The subject, serial number, and validity dates
+ * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
+ * a later time by a certificate signed by a Certificate Authority (CA).
+ *
+ * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
+ * certificate will be created with an invalid signature which will not verify. Such a certificate
+ * is still useful because it provides access to the public key. To generate a valid
+ * signature for the certificate the key needs to be authorized for all of the following:
+ * <ul>
+ * <li>{@link KeyProperties#PURPOSE_SIGN},</li>
+ * <li>operation without requiring the user to be authenticated (see
+ * {@link Builder#setUserAuthenticationRequired(boolean)}),</li>
+ * <li>signing/origination at this moment in time (see {@link Builder#setKeyValidityStart(Date)}
+ * and {@link Builder#setKeyValidityForOriginationEnd(Date)}),</li>
+ * <li>suitable digest or {@link KeyProperties#DIGEST_NONE},</li>
+ * <li>(RSA keys only) padding scheme {@link KeyProperties#SIGNATURE_PADDING_RSA_PKCS1} or
+ * {@link KeyProperties#ENCRYPTION_PADDING_NONE}.</li>
+ * </ul>
+ *
+ * <p>NOTE: The key material of the generated symmetric and private keys is not accessible. The key
+ * material of the public keys is accessible.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * <p><h3>Example: Asymmetric key pair</h3>
+ * The following example illustrates how to generate an EC key pair in the Android KeyStore system
+ * under alias {@code key1} authorized to be used only for signing using SHA-256, SHA-384,
+ * or SHA-512 digest and only if the user has been authenticated within the last five minutes.
+ * <pre> {@code
+ * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+ * KeyProperties.KEY_ALGORITHM_EC,
+ * "AndroidKeyStore");
+ * keyPairGenerator.initialize(
+ * new KeyGenParameterSpec.Builder(
+ * "key1",
+ * KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ * .setDigests(KeyProperties.DIGEST_SHA256,
+ * KeyProperties.DIGEST_SHA384,
+ * KeyProperties.DIGEST_SHA512)
+ * // Only permit this key to be used if the user authenticated
+ * // within the last five minutes.
+ * .setUserAuthenticationRequired(true)
+ * .setUserAuthenticationValidityDurationSeconds(5 * 60)
+ * .build());
+ * KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ *
+ * // The key pair can also be obtained from the Android Keystore any time as follows:
+ * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ * keyStore.load(null);
+ * PrivateKey privateKey = (PrivateKey) keyStore.getKey("key1", null);
+ * PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();
+ * }</pre>
+ *
+ * <p><h3>Example: Symmetric key</h3>
+ * The following example illustrates how to generate an AES key in the Android KeyStore system under
+ * alias {@code key2} authorized to be used only for encryption/decryption in GCM mode with no
+ * padding.
+ * <pre> {@code
+ * KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ * KeyProperties.KEY_ALGORITHM_AES,
+ * "AndroidKeyStore");
+ * keyGenerator.initialize(
+ * new KeyGenParameterSpec.Builder("key2",
+ * KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ * .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ * .build());
+ * SecretKey key = keyGenerator.generateKey();
+ *
+ * // The key can also be obtained from the Android Keystore any time as follows:
+ * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ * keyStore.load(null);
+ * key = (SecretKey) keyStore.getKey("key2", null);
+ * }</pre>
+ */
+public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
+
+ private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
+ private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
+ private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
+ private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
+
+ private final String mKeystoreAlias;
+ private final int mKeySize;
+ private final AlgorithmParameterSpec mSpec;
+ private final X500Principal mCertificateSubject;
+ private final BigInteger mCertificateSerialNumber;
+ private final Date mCertificateNotBefore;
+ private final Date mCertificateNotAfter;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyProperties.PurposeEnum int mPurposes;
+ private final @KeyProperties.DigestEnum String[] mDigests;
+ private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
+ private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
+ private final @KeyProperties.BlockModeEnum String[] mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+ private final boolean mUserAuthenticationRequired;
+ private final int mUserAuthenticationValidityDurationSeconds;
+
+ /**
+ * @hide should be built with Builder
+ */
+ public KeyGenParameterSpec(
+ String keyStoreAlias,
+ int keySize,
+ AlgorithmParameterSpec spec,
+ X500Principal certificateSubject,
+ BigInteger certificateSerialNumber,
+ Date certificateNotBefore,
+ Date certificateNotAfter,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyProperties.PurposeEnum int purposes,
+ @KeyProperties.DigestEnum String[] digests,
+ @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings,
+ @KeyProperties.SignaturePaddingEnum String[] signaturePaddings,
+ @KeyProperties.BlockModeEnum String[] blockModes,
+ boolean randomizedEncryptionRequired,
+ boolean userAuthenticationRequired,
+ int userAuthenticationValidityDurationSeconds) {
+ if (TextUtils.isEmpty(keyStoreAlias)) {
+ throw new IllegalArgumentException("keyStoreAlias must not be empty");
+ }
+
+ if (certificateSubject == null) {
+ certificateSubject = DEFAULT_CERT_SUBJECT;
+ }
+ if (certificateNotBefore == null) {
+ certificateNotBefore = DEFAULT_CERT_NOT_BEFORE;
+ }
+ if (certificateNotAfter == null) {
+ certificateNotAfter = DEFAULT_CERT_NOT_AFTER;
+ }
+ if (certificateSerialNumber == null) {
+ certificateSerialNumber = DEFAULT_CERT_SERIAL_NUMBER;
+ }
+
+ if (certificateNotAfter.before(certificateNotBefore)) {
+ throw new IllegalArgumentException("certificateNotAfter < certificateNotBefore");
+ }
+
+ mKeystoreAlias = keyStoreAlias;
+ mKeySize = keySize;
+ mSpec = spec;
+ mCertificateSubject = certificateSubject;
+ mCertificateSerialNumber = certificateSerialNumber;
+ mCertificateNotBefore = Utils.cloneIfNotNull(certificateNotBefore);
+ mCertificateNotAfter = Utils.cloneIfNotNull(certificateNotAfter);
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
+ mPurposes = purposes;
+ mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ mEncryptionPaddings =
+ ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
+ mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
+ mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
+ mUserAuthenticationRequired = userAuthenticationRequired;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Returns the alias that will be used in the {@code java.security.KeyStore}
+ * in conjunction with the {@code AndroidKeyStore}.
+ */
+ @NonNull
+ public String getKeystoreAlias() {
+ return mKeystoreAlias;
+ }
+
+ /**
+ * Returns the requested key size. If {@code -1}, the size should be looked up from
+ * {@link #getAlgorithmParameterSpec()}, if provided, otherwise an algorithm-specific default
+ * size should be used.
+ */
+ public int getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Returns the key algorithm-specific {@link AlgorithmParameterSpec} that will be used for
+ * creation of the key or {@code null} if algorithm-specific defaults should be used.
+ */
+ @Nullable
+ public AlgorithmParameterSpec getAlgorithmParameterSpec() {
+ return mSpec;
+ }
+
+ /**
+ * Returns the subject distinguished name to be used on the X.509 certificate that will be put
+ * in the {@link java.security.KeyStore}.
+ */
+ @NonNull
+ public X500Principal getCertificateSubject() {
+ return mCertificateSubject;
+ }
+
+ /**
+ * Returns the serial number to be used on the X.509 certificate that will be put in the
+ * {@link java.security.KeyStore}.
+ */
+ @NonNull
+ public BigInteger getCertificateSerialNumber() {
+ return mCertificateSerialNumber;
+ }
+
+ /**
+ * Returns the start date to be used on the X.509 certificate that will be put in the
+ * {@link java.security.KeyStore}.
+ */
+ @NonNull
+ public Date getCertificateNotBefore() {
+ return Utils.cloneIfNotNull(mCertificateNotBefore);
+ }
+
+ /**
+ * Returns the end date to be used on the X.509 certificate that will be put in the
+ * {@link java.security.KeyStore}.
+ */
+ @NonNull
+ public Date getCertificateNotAfter() {
+ return Utils.cloneIfNotNull(mCertificateNotAfter);
+ }
+
+ /**
+ * Returns the time instant before which the key is not yet valid or {@code null} if not
+ * restricted.
+ */
+ @Nullable
+ public Date getKeyValidityStart() {
+ return Utils.cloneIfNotNull(mKeyValidityStart);
+ }
+
+ /**
+ * Returns the time instant after which the key is no longer valid for decryption and
+ * verification or {@code null} if not restricted.
+ */
+ @Nullable
+ public Date getKeyValidityForConsumptionEnd() {
+ return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd);
+ }
+
+ /**
+ * Returns the time instant after which the key is no longer valid for encryption and signing
+ * or {@code null} if not restricted.
+ */
+ @Nullable
+ public Date getKeyValidityForOriginationEnd() {
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
+ }
+
+ /**
+ * Returns the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used.
+ * Attempts to use the key for any other purpose will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code PURPOSE} flags.
+ */
+ public @KeyProperties.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Returns the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384} with which the
+ * key can be used or {@code null} if not specified.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ *
+ * @throws IllegalStateException if this set has not been specified.
+ *
+ * @see #isDigestsSpecified()
+ */
+ @NonNull
+ public @KeyProperties.DigestEnum String[] getDigests() {
+ if (mDigests == null) {
+ throw new IllegalStateException("Digests not specified");
+ }
+ return ArrayUtils.cloneIfNotEmpty(mDigests);
+ }
+
+ /**
+ * Returns {@code true} if the set of digest algorithms with which the key can be used has been
+ * specified.
+ *
+ * @see #getDigests()
+ */
+ @NonNull
+ public boolean isDigestsSpecified() {
+ return mDigests != null;
+ }
+
+ /**
+ * Returns the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OEAPPadding},
+ * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when
+ * encrypting/decrypting. Attempts to use the key with any other padding scheme will be
+ * rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
+ }
+
+ /**
+ * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key
+ * can be used when signing/verifying. Attempts to use the key with any other padding scheme
+ * will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
+ }
+
+ /**
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
+ * when encrypting/decrypting. Attempts to use the key with any other block modes will be
+ * rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
+ */
+ @NonNull
+ public @KeyProperties.BlockModeEnum String[] getBlockModes() {
+ return ArrayUtils.cloneIfNotEmpty(mBlockModes);
+ }
+
+ /**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationRequired(boolean)
+ */
+ public boolean isUserAuthenticationRequired() {
+ return mUserAuthenticationRequired;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return duration in seconds or {@code -1} if authentication is required for every use of the
+ * key.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationValidityDurationSeconds(int)
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Builder of {@link KeyGenParameterSpec} instances.
+ */
+ public final static class Builder {
+ private final String mKeystoreAlias;
+ private @KeyProperties.PurposeEnum int mPurposes;
+
+ private int mKeySize = -1;
+ private AlgorithmParameterSpec mSpec;
+ private X500Principal mCertificateSubject;
+ private BigInteger mCertificateSerialNumber;
+ private Date mCertificateNotBefore;
+ private Date mCertificateNotAfter;
+ private Date mKeyValidityStart;
+ private Date mKeyValidityForOriginationEnd;
+ private Date mKeyValidityForConsumptionEnd;
+ private @KeyProperties.DigestEnum String[] mDigests;
+ private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
+ private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
+ private @KeyProperties.BlockModeEnum String[] mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+ private boolean mUserAuthenticationRequired;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+
+ /**
+ * Creates a new instance of the {@code Builder}.
+ *
+ * @param keystoreAlias alias of the entry in which the generated key will appear in
+ * Android KeyStore. Must not be empty.
+ * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
+ * used. Attempts to use the key for any other purpose will be rejected.
+ *
+ * <p>If the set of purposes for which the key can be used does not contain
+ * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
+ * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
+ * invalid signature. This is OK if the certificate is only used for obtaining the
+ * public key from Android KeyStore.
+ *
+ * <p>See {@link KeyProperties}.{@code PURPOSE} flags.
+ */
+ public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
+ if (keystoreAlias == null) {
+ throw new NullPointerException("keystoreAlias == null");
+ } else if (keystoreAlias.isEmpty()) {
+ throw new IllegalArgumentException("keystoreAlias must not be empty");
+ }
+ mKeystoreAlias = keystoreAlias;
+ mPurposes = purposes;
+ }
+
+ /**
+ * Sets the size (in bits) of the key to be generated. For instance, for RSA keys this sets
+ * the modulus size, for EC keys this selects a curve with a matching field size, and for
+ * symmetric keys this sets the size of the bitstring which is their key material.
+ *
+ * <p>The default key size is specific to each key algorithm. If key size is not set
+ * via this method, it should be looked up from the algorithm-specific parameters (if any)
+ * provided via
+ * {@link #setAlgorithmParameterSpec(AlgorithmParameterSpec) setAlgorithmParameterSpec}.
+ */
+ @NonNull
+ public Builder setKeySize(int keySize) {
+ if (keySize < 0) {
+ throw new IllegalArgumentException("keySize < 0");
+ }
+ mKeySize = keySize;
+ return this;
+ }
+
+ /**
+ * Sets the algorithm-specific key generation parameters. For example, for RSA keys this may
+ * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec} whereas for EC keys
+ * this may be an instance of {@link java.security.spec.ECGenParameterSpec}.
+ *
+ * <p>These key generation parameters must match other explicitly set parameters (if any),
+ * such as key size.
+ */
+ public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) {
+ if (spec == null) {
+ throw new NullPointerException("spec == null");
+ }
+ mSpec = spec;
+ return this;
+ }
+
+ /**
+ * Sets the subject used for the self-signed certificate of the generated key pair.
+ *
+ * <p>By default, the subject is {@code CN=fake}.
+ */
+ @NonNull
+ public Builder setCertificateSubject(@NonNull X500Principal subject) {
+ if (subject == null) {
+ throw new NullPointerException("subject == null");
+ }
+ mCertificateSubject = subject;
+ return this;
+ }
+
+ /**
+ * Sets the serial number used for the self-signed certificate of the generated key pair.
+ *
+ * <p>By default, the serial number is {@code 1}.
+ */
+ @NonNull
+ public Builder setCertificateSerialNumber(@NonNull BigInteger serialNumber) {
+ if (serialNumber == null) {
+ throw new NullPointerException("serialNumber == null");
+ }
+ mCertificateSerialNumber = serialNumber;
+ return this;
+ }
+
+ /**
+ * Sets the start of the validity period for the self-signed certificate of the generated
+ * key pair.
+ *
+ * <p>By default, this date is {@code Jan 1 1970}.
+ */
+ @NonNull
+ public Builder setCertificateNotBefore(@NonNull Date date) {
+ if (date == null) {
+ throw new NullPointerException("date == null");
+ }
+ mCertificateNotBefore = Utils.cloneIfNotNull(date);
+ return this;
+ }
+
+ /**
+ * Sets the end of the validity period for the self-signed certificate of the generated key
+ * pair.
+ *
+ * <p>By default, this date is {@code Jan 1 2048}.
+ */
+ @NonNull
+ public Builder setCertificateNotAfter(@NonNull Date date) {
+ if (date == null) {
+ throw new NullPointerException("date == null");
+ }
+ mCertificateNotAfter = Utils.cloneIfNotNull(date);
+ return this;
+ }
+
+ /**
+ * Sets the time instant before which the key is not yet valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityStart(Date startDate) {
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityStart(Date)
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ @NonNull
+ 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.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityForOriginationEnd(Date endDate) {
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for decryption and
+ * verification.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityForConsumptionEnd(Date endDate) {
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the set of digests algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which
+ * the key can be used. Attempts to use the key with any other digest algorithm will be
+ * rejected.
+ *
+ * <p>This must be specified for signing/verification keys and RSA encryption/decryption
+ * keys used with RSA OAEP padding scheme because these operations involve a digest. For
+ * HMAC keys, the default is the digest associated with the key algorithm (e.g.,
+ * {@code SHA-256} for key algorithm {@code HmacSHA256}).
+ *
+ * <p>For private keys used for TLS/SSL client or server authentication it is usually
+ * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is
+ * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use
+ * a private key to sign it.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ */
+ @NonNull
+ public Builder setDigests(@KeyProperties.DigestEnum String... digests) {
+ mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ return this;
+ }
+
+ /**
+ * Sets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OAEPPadding},
+ * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when
+ * encrypting/decrypting. Attempts to use the key with any other padding scheme will be
+ * rejected.
+ *
+ * <p>This must be specified for keys which are used for encryption/decryption.
+ *
+ * <p>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it
+ * is usually necessary to authorize the use of no/any padding
+ * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is
+ * required by some cipher suites, and some stacks request decryption using no padding
+ * whereas others request PKCS#1 padding.
+ *
+ * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
+ */
+ @NonNull
+ public Builder setEncryptionPaddings(
+ @KeyProperties.EncryptionPaddingEnum String... paddings) {
+ mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings);
+ return this;
+ }
+
+ /**
+ * Sets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key
+ * can be used when signing/verifying. Attempts to use the key with any other padding scheme
+ * will be rejected.
+ *
+ * <p>This must be specified for RSA keys which are used for signing/verification.
+ *
+ * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
+ */
+ @NonNull
+ public Builder setSignaturePaddings(
+ @KeyProperties.SignaturePaddingEnum String... paddings) {
+ mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings);
+ return this;
+ }
+
+ /**
+ * Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be
+ * used when encrypting/decrypting. Attempts to use the key with any other block modes will
+ * be rejected.
+ *
+ * <p>This must be specified for symmetric encryption/decryption keys.
+ *
+ * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
+ */
+ @NonNull
+ public Builder setBlockModes(@KeyProperties.BlockModeEnum String... blockModes) {
+ mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes);
+ return this;
+ }
+
+ /**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>encryption/decryption transformation which do not offer {@code IND-CPA}, such as
+ * {@code ECB} with a symmetric encryption algorithm, or RSA encryption/decryption without
+ * padding, are prohibited;</li>
+ * <li>in block modes which use an IV, such as {@code GCM}, {@code CBC}, and {@code CTR},
+ * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
+ * used.</li>
+ * </ul>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to encryption
+ * padding schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ */
+ @NonNull
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets whether this key is authorized to be used only if the user has been authenticated.
+ *
+ * <p>By default, the key is authorized to be used regardless of whether the user has been
+ * authenticated.
+ *
+ * <p>When user authentication is required:
+ * <ul>
+ * <li>The key can only be generated if secure lock screen is set up (see
+ * {@link KeyguardManager#isDeviceSecure()}). Additionally, if the key requires that user
+ * authentication takes place for every use of the key (see
+ * {@link #setUserAuthenticationValidityDurationSeconds(int)}), at least one fingerprint
+ * must be enrolled (see {@link FingerprintManager#hasEnrolledFingerprints()}).</li>
+ * <li>The use of the key must be authorized by the user by authenticating to this Android
+ * device using a subset of their secure lock screen credentials such as
+ * password/PIN/pattern or fingerprint.
+ * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More
+ * information</a>.
+ * <li>The key will become <em>irreversibly invalidated</em> once the secure lock screen is
+ * disabled (reconfigured to None, Swipe or other mode which does not authenticate the user)
+ * or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
+ * Additionally, if the key requires that user authentication takes place for every use of
+ * the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
+ * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
+ * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
+ * </ul>
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see #setUserAuthenticationValidityDurationSeconds(int)
+ * @see KeyguardManager#isDeviceSecure()
+ * @see FingerprintManager#hasEnrolledFingerprints()
+ */
+ @NonNull
+ public Builder setUserAuthenticationRequired(boolean required) {
+ mUserAuthenticationRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect if the key requires user
+ * authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>Cryptographic operations involving keys which require user authentication to take
+ * place for every operation can only use fingerprint authentication. This is achieved by
+ * initializing a cryptographic operation ({@link Signature}, {@link Cipher}, {@link Mac})
+ * with the key, wrapping it into a {@link FingerprintManager.CryptoObject}, invoking
+ * {@code FingerprintManager.authenticate} with {@code CryptoObject}, and proceeding with
+ * the cryptographic operation only if the authentication flow succeeds.
+ *
+ * <p>Cryptographic operations involving keys which are authorized to be used for a duration
+ * of time after a successful user authentication event can only use secure lock screen
+ * authentication. These cryptographic operations will throw
+ * {@link UserNotAuthenticatedException} during initialization if the user needs to be
+ * authenticated to proceed. This situation can be resolved by the user unlocking the secure
+ * lock screen of the Android or by going through the confirm credential flow initiated by
+ * {@link KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}.
+ * Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param seconds duration in seconds or {@code -1} if user authentication must take place
+ * for every use of the key.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see FingerprintManager
+ * @see FingerprintManager.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationValidityDurationSeconds(
+ @IntRange(from = -1) int seconds) {
+ if (seconds < -1) {
+ throw new IllegalArgumentException("seconds must be -1 or larger");
+ }
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@code KeyGenParameterSpec}.
+ */
+ @NonNull
+ public KeyGenParameterSpec build() {
+ return new KeyGenParameterSpec(
+ mKeystoreAlias,
+ mKeySize,
+ mSpec,
+ mCertificateSubject,
+ mCertificateSerialNumber,
+ mCertificateNotBefore,
+ mCertificateNotAfter,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mDigests,
+ mEncryptionPaddings,
+ mSignaturePaddings,
+ mBlockModes,
+ mRandomizedEncryptionRequired,
+ mUserAuthenticationRequired,
+ mUserAuthenticationValidityDurationSeconds);
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
new file mode 100644
index 0000000..785ec15
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -0,0 +1,282 @@
+/*
+ * 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.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.security.PrivateKey;
+import java.security.spec.KeySpec;
+import java.util.Date;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android
+ * Keystore system</a>. This class describes whether the key material is available in
+ * plaintext outside of secure hardware, whether user authentication is required for using the key
+ * and whether this requirement is enforced by secure hardware, the key's origin, what uses the key
+ * is authorized for (e.g., only in {@code GCM} mode, or signing only), whether the key should be
+ * encrypted at rest, the key's and validity start and end dates.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * <p><h3>Example: Symmetric Key</h3>
+ * The following example illustrates how to obtain a {@code KeyInfo} describing the provided Android
+ * Keystore {@link SecretKey}.
+ * <pre> {@code
+ * SecretKey key = ...; // Android Keystore key
+ *
+ * SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
+ * KeyInfo keyInfo;
+ * try &#123;
+ * keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class);
+ * &#125; catch (InvalidKeySpecException e) &#123;
+ * // Not an Android KeyStore key.
+ * &#125;
+ * }</pre>
+ *
+ * <p><h3>Example: Private Key</h3>
+ * The following example illustrates how to obtain a {@code KeyInfo} describing the provided
+ * Android KeyStore {@link PrivateKey}.
+ * <pre> {@code
+ * PrivateKey key = ...; // Android KeyStore key
+ *
+ * KeyFactory factory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
+ * KeyInfo keyInfo;
+ * try &#123;
+ * keyInfo = factory.getKeySpec(key, KeyInfo.class);
+ * &#125; catch (InvalidKeySpecException e) &#123;
+ * // Not an Android KeyStore key.
+ * &#125;
+ * }</pre>
+ */
+public class KeyInfo implements KeySpec {
+ private final String mKeystoreAlias;
+ private final int mKeySize;
+ private final boolean mInsideSecureHardware;
+ private final @KeyProperties.OriginEnum int mOrigin;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyProperties.PurposeEnum int mPurposes;
+ private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
+ private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
+ private final @KeyProperties.DigestEnum String[] mDigests;
+ private final @KeyProperties.BlockModeEnum String[] mBlockModes;
+ private final boolean mUserAuthenticationRequired;
+ private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
+
+ /**
+ * @hide
+ */
+ public KeyInfo(String keystoreKeyAlias,
+ boolean insideSecureHardware,
+ @KeyProperties.OriginEnum int origin,
+ int keySize,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyProperties.PurposeEnum int purposes,
+ @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings,
+ @KeyProperties.SignaturePaddingEnum String[] signaturePaddings,
+ @KeyProperties.DigestEnum String[] digests,
+ @KeyProperties.BlockModeEnum String[] blockModes,
+ boolean userAuthenticationRequired,
+ int userAuthenticationValidityDurationSeconds,
+ boolean userAuthenticationRequirementEnforcedBySecureHardware) {
+ mKeystoreAlias = keystoreKeyAlias;
+ mInsideSecureHardware = insideSecureHardware;
+ mOrigin = origin;
+ mKeySize = keySize;
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
+ mPurposes = purposes;
+ mEncryptionPaddings =
+ ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
+ mSignaturePaddings =
+ ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
+ mDigests = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(digests));
+ mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
+ mUserAuthenticationRequired = userAuthenticationRequired;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationRequirementEnforcedBySecureHardware =
+ userAuthenticationRequirementEnforcedBySecureHardware;
+ }
+
+ /**
+ * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}.
+ */
+ public String getKeystoreAlias() {
+ return mKeystoreAlias;
+ }
+
+ /**
+ * Returns {@code true} if the key resides inside secure hardware (e.g., Trusted Execution
+ * Environment (TEE) or Secure Element (SE)). Key material of such keys is available in
+ * plaintext only inside the secure hardware and is not exposed outside of it.
+ */
+ public boolean isInsideSecureHardware() {
+ return mInsideSecureHardware;
+ }
+
+ /**
+ * Gets the origin of the key. See {@link KeyProperties}.{@code ORIGIN} constants.
+ */
+ public @KeyProperties.OriginEnum int getOrigin() {
+ return mOrigin;
+ }
+
+ /**
+ * Gets the size of the key 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.
+ */
+ @Nullable
+ public Date getKeyValidityStart() {
+ return Utils.cloneIfNotNull(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.
+ */
+ @Nullable
+ public Date getKeyValidityForConsumptionEnd() {
+ return Utils.cloneIfNotNull(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.
+ */
+ @Nullable
+ public Date getKeyValidityForOriginationEnd() {
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
+ }
+
+ /**
+ * Gets the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used.
+ * Attempts to use the key for any other purpose will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code PURPOSE} flags.
+ */
+ public @KeyProperties.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
+ * when encrypting/decrypting. Attempts to use the key with any other block modes will be
+ * rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
+ */
+ @NonNull
+ public @KeyProperties.BlockModeEnum String[] getBlockModes() {
+ return ArrayUtils.cloneIfNotEmpty(mBlockModes);
+ }
+
+ /**
+ * Gets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code PKCS1Padding},
+ * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to use
+ * the key with any other padding scheme will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
+ }
+
+ /**
+ * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key
+ * can be used when signing/verifying. Attempts to use the key with any other padding scheme
+ * will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
+ }
+
+ /**
+ * Gets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the key
+ * can be used.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ */
+ @NonNull
+ public @KeyProperties.DigestEnum String[] getDigests() {
+ return ArrayUtils.cloneIfNotEmpty(mDigests);
+ }
+
+ /**
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)
+ * @see KeyProtection.Builder#setUserAuthenticationRequired(boolean)
+ */
+ public boolean isUserAuthenticationRequired() {
+ return mUserAuthenticationRequired;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return duration in seconds or {@code -1} if authentication is required for every use of the
+ * key.
+ *
+ * @see #isUserAuthenticationRequired()
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Returns {@code true} if the requirement that this key can only be used if the user has been
+ * authenticated if enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or
+ * Secure Element (SE)).
+ *
+ * @see #isUserAuthenticationRequired()
+ */
+ public boolean isUserAuthenticationRequirementEnforcedBySecureHardware() {
+ return mUserAuthenticationRequirementEnforcedBySecureHardware;
+ }
+}
diff --git a/keystore/java/android/security/KeyNotYetValidException.java b/keystore/java/android/security/keystore/KeyNotYetValidException.java
index d36d80c..2cec77d 100644
--- a/keystore/java/android/security/KeyNotYetValidException.java
+++ b/keystore/java/android/security/keystore/KeyNotYetValidException.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import java.security.InvalidKeyException;
/**
* Indicates that a cryptographic operation failed because the employed key's validity start date
* is in the future.
- *
- * @hide
*/
public class KeyNotYetValidException extends InvalidKeyException {
diff --git a/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java
new file mode 100644
index 0000000..9e82fc0
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java
@@ -0,0 +1,56 @@
+/*
+ * 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.keystore;
+
+import java.security.InvalidKeyException;
+
+/**
+ * Indicates that the key can no longer be used because it has been permanently invalidated.
+ *
+ * <p>This only occurs for keys which are authorized to be used only if the user has been
+ * authenticated. Such keys are permanently and irreversibly invalidated once the secure lock screen
+ * is disabled (i.e., reconfigured to None, Swipe or other mode which does not authenticate the
+ * user) or when the secure lock screen is forcibly reset (e.g., by Device Admin). Additionally,
+ * keys configured to require user authentication to take place for every of the keys, are also
+ * permanently invalidated once a new fingerprint is enrolled or once no more fingerprints are
+ * enrolled.
+ */
+public class KeyPermanentlyInvalidatedException extends InvalidKeyException {
+
+ /**
+ * Constructs a new {@code KeyPermanentlyInvalidatedException} without detail message and cause.
+ */
+ public KeyPermanentlyInvalidatedException() {
+ super("Key permanently invalidated");
+ }
+
+ /**
+ * Constructs a new {@code KeyPermanentlyInvalidatedException} with the provided detail message
+ * and no cause.
+ */
+ public KeyPermanentlyInvalidatedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code KeyPermanentlyInvalidatedException} with the provided detail message
+ * and cause.
+ */
+ public KeyPermanentlyInvalidatedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
new file mode 100644
index 0000000..f9fe176
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -0,0 +1,730 @@
+/*
+ * 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.keystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.security.keymaster.KeymasterDefs;
+
+import libcore.util.EmptyArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Locale;
+
+/**
+ * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys.
+ */
+public abstract class KeyProperties {
+ private KeyProperties() {}
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ PURPOSE_ENCRYPT,
+ PURPOSE_DECRYPT,
+ PURPOSE_SIGN,
+ PURPOSE_VERIFY,
+ })
+ public @interface PurposeEnum {}
+
+ /**
+ * Purpose of key: encryption.
+ */
+ public static final int PURPOSE_ENCRYPT = 1 << 0;
+
+ /**
+ * Purpose of key: decryption.
+ */
+ public static final int PURPOSE_DECRYPT = 1 << 1;
+
+ /**
+ * Purpose of key: signing or generating a Message Authentication Code (MAC).
+ */
+ public static final int PURPOSE_SIGN = 1 << 2;
+
+ /**
+ * Purpose of key: signature or Message Authentication Code (MAC) verification.
+ */
+ public static final int PURPOSE_VERIFY = 1 << 3;
+
+ /**
+ * @hide
+ */
+ public static abstract class Purpose {
+ private Purpose() {}
+
+ public static int toKeymaster(@PurposeEnum int purpose) {
+ switch (purpose) {
+ case PURPOSE_ENCRYPT:
+ return KeymasterDefs.KM_PURPOSE_ENCRYPT;
+ case PURPOSE_DECRYPT:
+ return KeymasterDefs.KM_PURPOSE_DECRYPT;
+ case PURPOSE_SIGN:
+ return KeymasterDefs.KM_PURPOSE_SIGN;
+ case PURPOSE_VERIFY:
+ return KeymasterDefs.KM_PURPOSE_VERIFY;
+ default:
+ throw new IllegalArgumentException("Unknown purpose: " + purpose);
+ }
+ }
+
+ public static @PurposeEnum int fromKeymaster(int purpose) {
+ switch (purpose) {
+ case KeymasterDefs.KM_PURPOSE_ENCRYPT:
+ return PURPOSE_ENCRYPT;
+ case KeymasterDefs.KM_PURPOSE_DECRYPT:
+ return PURPOSE_DECRYPT;
+ case KeymasterDefs.KM_PURPOSE_SIGN:
+ return PURPOSE_SIGN;
+ case KeymasterDefs.KM_PURPOSE_VERIFY:
+ return PURPOSE_VERIFY;
+ default:
+ throw new IllegalArgumentException("Unknown purpose: " + purpose);
+ }
+ }
+
+ @NonNull
+ public static int[] allToKeymaster(@PurposeEnum int purposes) {
+ int[] result = getSetFlags(purposes);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ public static @PurposeEnum int allFromKeymaster(@NonNull Collection<Integer> purposes) {
+ @PurposeEnum int result = 0;
+ for (int keymasterPurpose : purposes) {
+ result |= fromKeymaster(keymasterPurpose);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ KEY_ALGORITHM_RSA,
+ KEY_ALGORITHM_EC,
+ KEY_ALGORITHM_AES,
+ KEY_ALGORITHM_HMAC_SHA1,
+ KEY_ALGORITHM_HMAC_SHA224,
+ KEY_ALGORITHM_HMAC_SHA256,
+ KEY_ALGORITHM_HMAC_SHA384,
+ KEY_ALGORITHM_HMAC_SHA512,
+ })
+ public @interface KeyAlgorithmEnum {}
+
+ /** Rivest Shamir Adleman (RSA) key. */
+ public static final String KEY_ALGORITHM_RSA = "RSA";
+
+ /** Elliptic Curve (EC) Cryptography key. */
+ public static final String KEY_ALGORITHM_EC = "EC";
+
+ /** Advanced Encryption Standard (AES) key. */
+ public static final String KEY_ALGORITHM_AES = "AES";
+
+ /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */
+ public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";
+
+ /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */
+ public static final String KEY_ALGORITHM_HMAC_SHA224 = "HmacSHA224";
+
+ /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */
+ public static final String KEY_ALGORITHM_HMAC_SHA256 = "HmacSHA256";
+
+ /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */
+ public static final String KEY_ALGORITHM_HMAC_SHA384 = "HmacSHA384";
+
+ /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */
+ public static final String KEY_ALGORITHM_HMAC_SHA512 = "HmacSHA512";
+
+ /**
+ * @hide
+ */
+ public static abstract class KeyAlgorithm {
+ private KeyAlgorithm() {}
+
+ public static int toKeymasterAsymmetricKeyAlgorithm(
+ @NonNull @KeyAlgorithmEnum String algorithm) {
+ if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_EC;
+ } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
+ }
+ }
+
+ @NonNull
+ public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm(
+ int keymasterAlgorithm) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return KEY_ALGORITHM_EC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return KEY_ALGORITHM_RSA;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported key algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ public static int toKeymasterSecretKeyAlgorithm(
+ @NonNull @KeyAlgorithmEnum String algorithm) {
+ if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_AES;
+ } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) {
+ return KeymasterDefs.KM_ALGORITHM_HMAC;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported secret key algorithm: " + algorithm);
+ }
+ }
+
+ @NonNull
+ public static @KeyAlgorithmEnum String fromKeymasterSecretKeyAlgorithm(
+ int keymasterAlgorithm, int keymasterDigest) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_AES:
+ if (keymasterDigest != -1) {
+ throw new IllegalArgumentException("Digest not supported for AES key: "
+ + Digest.fromKeymaster(keymasterDigest));
+ }
+ return KEY_ALGORITHM_AES;
+ case KeymasterDefs.KM_ALGORITHM_HMAC:
+ switch (keymasterDigest) {
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return KEY_ALGORITHM_HMAC_SHA1;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return KEY_ALGORITHM_HMAC_SHA224;
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return KEY_ALGORITHM_HMAC_SHA256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return KEY_ALGORITHM_HMAC_SHA384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return KEY_ALGORITHM_HMAC_SHA512;
+ default:
+ throw new IllegalArgumentException("Unsupported HMAC digest: "
+ + Digest.fromKeymaster(keymasterDigest));
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported key algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ /**
+ * @hide
+ *
+ * @return keymaster digest or {@code -1} if the algorithm does not involve a digest.
+ */
+ public static int toKeymasterDigest(@NonNull @KeyAlgorithmEnum String algorithm) {
+ String algorithmUpper = algorithm.toUpperCase(Locale.US);
+ if (algorithmUpper.startsWith("HMAC")) {
+ String digestUpper = algorithmUpper.substring("HMAC".length());
+ switch (digestUpper) {
+ case "SHA1":
+ return KeymasterDefs.KM_DIGEST_SHA1;
+ case "SHA224":
+ return KeymasterDefs.KM_DIGEST_SHA_2_224;
+ case "SHA256":
+ return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ case "SHA384":
+ return KeymasterDefs.KM_DIGEST_SHA_2_384;
+ case "SHA512":
+ return KeymasterDefs.KM_DIGEST_SHA_2_512;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported HMAC digest: " + digestUpper);
+ }
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ BLOCK_MODE_ECB,
+ BLOCK_MODE_CBC,
+ BLOCK_MODE_CTR,
+ BLOCK_MODE_GCM,
+ })
+ public @interface BlockModeEnum {}
+
+ /** Electronic Codebook (ECB) block mode. */
+ public static final String BLOCK_MODE_ECB = "ECB";
+
+ /** Cipher Block Chaining (CBC) block mode. */
+ public static final String BLOCK_MODE_CBC = "CBC";
+
+ /** Counter (CTR) block mode. */
+ public static final String BLOCK_MODE_CTR = "CTR";
+
+ /** Galois/Counter Mode (GCM) block mode. */
+ public static final String BLOCK_MODE_GCM = "GCM";
+
+ /**
+ * @hide
+ */
+ public static abstract class BlockMode {
+ private BlockMode() {}
+
+ public static int toKeymaster(@NonNull @BlockModeEnum String blockMode) {
+ if (BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
+ return KeymasterDefs.KM_MODE_ECB;
+ } else if (BLOCK_MODE_CBC.equalsIgnoreCase(blockMode)) {
+ return KeymasterDefs.KM_MODE_CBC;
+ } else if (BLOCK_MODE_CTR.equalsIgnoreCase(blockMode)) {
+ return KeymasterDefs.KM_MODE_CTR;
+ } else if (BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) {
+ return KeymasterDefs.KM_MODE_GCM;
+ } else {
+ throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
+ }
+ }
+
+ @NonNull
+ public static @BlockModeEnum String fromKeymaster(int blockMode) {
+ switch (blockMode) {
+ case KeymasterDefs.KM_MODE_ECB:
+ return BLOCK_MODE_ECB;
+ case KeymasterDefs.KM_MODE_CBC:
+ return BLOCK_MODE_CBC;
+ case KeymasterDefs.KM_MODE_CTR:
+ return BLOCK_MODE_CTR;
+ case KeymasterDefs.KM_MODE_GCM:
+ return BLOCK_MODE_GCM;
+ default:
+ throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
+ }
+ }
+
+ @NonNull
+ public static @BlockModeEnum String[] allFromKeymaster(
+ @NonNull Collection<Integer> blockModes) {
+ if ((blockModes == null) || (blockModes.isEmpty())) {
+ return EmptyArray.STRING;
+ }
+ @BlockModeEnum String[] result = new String[blockModes.size()];
+ int offset = 0;
+ for (int blockMode : blockModes) {
+ result[offset] = fromKeymaster(blockMode);
+ offset++;
+ }
+ return result;
+ }
+
+ public static int[] allToKeymaster(@Nullable @BlockModeEnum String[] blockModes) {
+ if ((blockModes == null) || (blockModes.length == 0)) {
+ return EmptyArray.INT;
+ }
+ int[] result = new int[blockModes.length];
+ for (int i = 0; i < blockModes.length; i++) {
+ result[i] = toKeymaster(blockModes[i]);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ ENCRYPTION_PADDING_NONE,
+ ENCRYPTION_PADDING_PKCS7,
+ ENCRYPTION_PADDING_RSA_PKCS1,
+ ENCRYPTION_PADDING_RSA_OAEP,
+ })
+ public @interface EncryptionPaddingEnum {}
+
+ /**
+ * No encryption padding.
+ *
+ * <p><b>NOTE</b>: If a key is authorized to be used with no padding, then it can be used with
+ * any padding scheme, both for encryption and signing.
+ */
+ public static final String ENCRYPTION_PADDING_NONE = "NoPadding";
+
+ /**
+ * PKCS#7 encryption padding scheme.
+ */
+ public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";
+
+ /**
+ * RSA PKCS#1 v1.5 padding scheme for encryption.
+ */
+ public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";
+
+ /**
+ * RSA Optimal Asymmetric Encryption Padding (OAEP) scheme.
+ */
+ public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding";
+
+ /**
+ * @hide
+ */
+ public static abstract class EncryptionPadding {
+ private EncryptionPadding() {}
+
+ public static int toKeymaster(@NonNull @EncryptionPaddingEnum String padding) {
+ if (ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) {
+ return KeymasterDefs.KM_PAD_NONE;
+ } else if (ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) {
+ return KeymasterDefs.KM_PAD_PKCS7;
+ } else if (ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) {
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ } else if (ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) {
+ return KeymasterDefs.KM_PAD_RSA_OAEP;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported encryption padding scheme: " + padding);
+ }
+ }
+
+ @NonNull
+ public static @EncryptionPaddingEnum String fromKeymaster(int padding) {
+ switch (padding) {
+ case KeymasterDefs.KM_PAD_NONE:
+ return ENCRYPTION_PADDING_NONE;
+ case KeymasterDefs.KM_PAD_PKCS7:
+ return ENCRYPTION_PADDING_PKCS7;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return ENCRYPTION_PADDING_RSA_PKCS1;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ return ENCRYPTION_PADDING_RSA_OAEP;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported encryption padding: " + padding);
+ }
+ }
+
+ @NonNull
+ public static int[] allToKeymaster(@Nullable @EncryptionPaddingEnum String[] paddings) {
+ if ((paddings == null) || (paddings.length == 0)) {
+ return EmptyArray.INT;
+ }
+ int[] result = new int[paddings.length];
+ for (int i = 0; i < paddings.length; i++) {
+ result[i] = toKeymaster(paddings[i]);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ SIGNATURE_PADDING_RSA_PKCS1,
+ SIGNATURE_PADDING_RSA_PSS,
+ })
+ public @interface SignaturePaddingEnum {}
+
+ /**
+ * RSA PKCS#1 v1.5 padding for signatures.
+ */
+ public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
+
+ /**
+ * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
+ */
+ public static final String SIGNATURE_PADDING_RSA_PSS = "PSS";
+
+ static abstract class SignaturePadding {
+ private SignaturePadding() {}
+
+ static int toKeymaster(@NonNull @SignaturePaddingEnum String padding) {
+ switch (padding.toUpperCase(Locale.US)) {
+ case SIGNATURE_PADDING_RSA_PKCS1:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
+ case SIGNATURE_PADDING_RSA_PSS:
+ return KeymasterDefs.KM_PAD_RSA_PSS;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported signature padding scheme: " + padding);
+ }
+ }
+
+ @NonNull
+ static @SignaturePaddingEnum String fromKeymaster(int padding) {
+ switch (padding) {
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
+ return SIGNATURE_PADDING_RSA_PKCS1;
+ case KeymasterDefs.KM_PAD_RSA_PSS:
+ return SIGNATURE_PADDING_RSA_PSS;
+ default:
+ throw new IllegalArgumentException("Unsupported signature padding: " + padding);
+ }
+ }
+
+ @NonNull
+ static int[] allToKeymaster(@Nullable @SignaturePaddingEnum String[] paddings) {
+ if ((paddings == null) || (paddings.length == 0)) {
+ return EmptyArray.INT;
+ }
+ int[] result = new int[paddings.length];
+ for (int i = 0; i < paddings.length; i++) {
+ result[i] = toKeymaster(paddings[i]);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ DIGEST_NONE,
+ DIGEST_MD5,
+ DIGEST_SHA1,
+ DIGEST_SHA224,
+ DIGEST_SHA256,
+ DIGEST_SHA384,
+ DIGEST_SHA512,
+ })
+ public @interface DigestEnum {}
+
+ /**
+ * No digest: sign/authenticate the raw message.
+ *
+ * <p><b>NOTE</b>: If a key is authorized to be used with no digest, then it can be used with
+ * any digest.
+ */
+ public static final String DIGEST_NONE = "NONE";
+
+ /**
+ * MD5 digest.
+ */
+ public static final String DIGEST_MD5 = "MD5";
+
+ /**
+ * SHA-1 digest.
+ */
+ public static final String DIGEST_SHA1 = "SHA-1";
+
+ /**
+ * SHA-2 224 (aka SHA-224) digest.
+ */
+ public static final String DIGEST_SHA224 = "SHA-224";
+
+ /**
+ * SHA-2 256 (aka SHA-256) digest.
+ */
+ public static final String DIGEST_SHA256 = "SHA-256";
+
+ /**
+ * SHA-2 384 (aka SHA-384) digest.
+ */
+ public static final String DIGEST_SHA384 = "SHA-384";
+
+ /**
+ * SHA-2 512 (aka SHA-512) digest.
+ */
+ public static final String DIGEST_SHA512 = "SHA-512";
+
+ /**
+ * @hide
+ */
+ public static abstract class Digest {
+ private Digest() {}
+
+ public static int toKeymaster(@NonNull @DigestEnum String digest) {
+ switch (digest.toUpperCase(Locale.US)) {
+ case DIGEST_SHA1:
+ return KeymasterDefs.KM_DIGEST_SHA1;
+ case DIGEST_SHA224:
+ return KeymasterDefs.KM_DIGEST_SHA_2_224;
+ case DIGEST_SHA256:
+ return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ case DIGEST_SHA384:
+ return KeymasterDefs.KM_DIGEST_SHA_2_384;
+ case DIGEST_SHA512:
+ return KeymasterDefs.KM_DIGEST_SHA_2_512;
+ case DIGEST_NONE:
+ return KeymasterDefs.KM_DIGEST_NONE;
+ case DIGEST_MD5:
+ return KeymasterDefs.KM_DIGEST_MD5;
+ default:
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
+ }
+ }
+
+ @NonNull
+ public static @DigestEnum String fromKeymaster(int digest) {
+ switch (digest) {
+ case KeymasterDefs.KM_DIGEST_NONE:
+ return DIGEST_NONE;
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return DIGEST_MD5;
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return DIGEST_SHA1;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return DIGEST_SHA224;
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return DIGEST_SHA256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return DIGEST_SHA384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return DIGEST_SHA512;
+ default:
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
+ }
+ }
+
+ @NonNull
+ public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) {
+ switch (digest) {
+ case KeymasterDefs.KM_DIGEST_NONE:
+ return "NONE";
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return "MD5";
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return "SHA1";
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return "SHA224";
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return "SHA256";
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return "SHA384";
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return "SHA512";
+ default:
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
+ }
+ }
+
+ @NonNull
+ public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) {
+ if (digests.isEmpty()) {
+ return EmptyArray.STRING;
+ }
+ String[] result = new String[digests.size()];
+ int offset = 0;
+ for (int digest : digests) {
+ result[offset] = fromKeymaster(digest);
+ offset++;
+ }
+ return result;
+ }
+
+ @NonNull
+ public static int[] allToKeymaster(@Nullable @DigestEnum String[] digests) {
+ if ((digests == null) || (digests.length == 0)) {
+ return EmptyArray.INT;
+ }
+ int[] result = new int[digests.length];
+ int offset = 0;
+ for (@DigestEnum String digest : digests) {
+ result[offset] = toKeymaster(digest);
+ offset++;
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ORIGIN_GENERATED,
+ ORIGIN_IMPORTED,
+ ORIGIN_UNKNOWN,
+ })
+ public @interface OriginEnum {}
+
+ /** Key was generated inside AndroidKeyStore. */
+ public static final int ORIGIN_GENERATED = 1 << 0;
+
+ /** Key was imported into AndroidKeyStore. */
+ public static final int ORIGIN_IMPORTED = 1 << 1;
+
+ /**
+ * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed
+ * implementation which does not record origin information.
+ */
+ public static final int ORIGIN_UNKNOWN = 1 << 2;
+
+ /**
+ * @hide
+ */
+ public static abstract class Origin {
+ private Origin() {}
+
+ public static @OriginEnum int fromKeymaster(int origin) {
+ switch (origin) {
+ case KeymasterDefs.KM_ORIGIN_GENERATED:
+ return ORIGIN_GENERATED;
+ case KeymasterDefs.KM_ORIGIN_IMPORTED:
+ return ORIGIN_IMPORTED;
+ case KeymasterDefs.KM_ORIGIN_UNKNOWN:
+ return ORIGIN_UNKNOWN;
+ default:
+ throw new IllegalArgumentException("Unknown origin: " + origin);
+ }
+ }
+ }
+
+ private static int[] getSetFlags(int flags) {
+ if (flags == 0) {
+ return EmptyArray.INT;
+ }
+ int result[] = new int[getSetBitCount(flags)];
+ int resultOffset = 0;
+ int flag = 1;
+ while (flags != 0) {
+ if ((flags & 1) != 0) {
+ result[resultOffset] = flag;
+ resultOffset++;
+ }
+ flags >>>= 1;
+ flag <<= 1;
+ }
+ return result;
+ }
+
+ private static int getSetBitCount(int value) {
+ if (value == 0) {
+ return 0;
+ }
+ int result = 0;
+ while (value != 0) {
+ if ((value & 1) != 0) {
+ result++;
+ }
+ value >>>= 1;
+ }
+ return result;
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
new file mode 100644
index 0000000..5b4b3e7
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -0,0 +1,602 @@
+/*
+ * 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.keystore;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.hardware.fingerprint.FingerprintManager;
+
+import java.security.Key;
+import java.security.Signature;
+import java.security.KeyStore.ProtectionParameter;
+import java.security.cert.Certificate;
+import java.util.Date;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * Specification of how a key or key pair is secured when imported into the
+ * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class
+ * specifies parameters such as whether user authentication is required for using the key, what uses
+ * the key is authorized for (e.g., only in {@code GCM} mode, or only for signing -- decryption not
+ * permitted), the key's and validity start and end dates.
+ *
+ * <p>To import a key or key pair into the Android KeyStore, create an instance of this class using
+ * the {@link Builder} and pass the instance into {@link java.security.KeyStore#setEntry(String, java.security.KeyStore.Entry, ProtectionParameter) KeyStore.setEntry}
+ * with the key or key pair being imported.
+ *
+ * <p>To obtain the secret/symmetric or private key from the Android KeyStore use
+ * {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or
+ * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}.
+ * To obtain the public key from the Android KeyStore use
+ * {@link java.security.KeyStore#getCertificate(String)} and then
+ * {@link Certificate#getPublicKey()}.
+ *
+ * <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * <p><h3>Example: Symmetric Key</h3>
+ * The following example illustrates how to import an AES key into the Android KeyStore under alias
+ * {@code key1} authorized to be used only for encryption/decryption in GCM mode with no padding.
+ * The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format.
+ * <pre> {@code
+ * SecretKey key = ...; // AES key
+ *
+ * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ * keyStore.load(null);
+ * keyStore.setEntry(
+ * "key1",
+ * new KeyStore.SecretKeyEntry(key),
+ * new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ * .setBlockMode(KeyProperties.BLOCK_MODE_GCM)
+ * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ * .build());
+ * // Key imported, obtain a reference to it.
+ * SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
+ * // The original key can now be thrown away.
+ * }</pre>
+ *
+ * <p><h3>Example: Asymmetric Key Pair</h3>
+ * The following example illustrates how to import an EC key pair into the Android KeyStore under
+ * alias {@code key2} authorized to be used only for signing with SHA-256 digest and only if
+ * the user has been authenticated within the last ten minutes. Both the private and the public key
+ * must export their key material via {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509}
+ * format respectively.
+ * <pre> {@code
+ * PrivateKey privateKey = ...; // EC private key
+ * Certificate[] certChain = ...; // Certificate chain with the first certificate
+ * // containing the corresponding EC public key.
+ *
+ * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ * keyStore.load(null);
+ * keyStore.setEntry(
+ * "key2",
+ * new KeyStore.PrivateKeyEntry(privateKey, certChain),
+ * new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+ * .setDigests(KeyProperties.DIGEST_SHA256)
+ * // Only permit this key to be used if the user
+ * // authenticated within the last ten minutes.
+ * .setUserAuthenticationRequired(true)
+ * .setUserAuthenticationValidityDurationSeconds(10 * 60)
+ * .build());
+ * // Key pair imported, obtain a reference to it.
+ * PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null);
+ * PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey();
+ * // The original private key can now be thrown away.
+ * }</pre>
+ */
+public final class KeyProtection implements ProtectionParameter {
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyProperties.PurposeEnum int mPurposes;
+ private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
+ private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
+ private final @KeyProperties.DigestEnum String[] mDigests;
+ private final @KeyProperties.BlockModeEnum String[] mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+ private final boolean mUserAuthenticationRequired;
+ private final int mUserAuthenticationValidityDurationSeconds;
+
+ private KeyProtection(
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyProperties.PurposeEnum int purposes,
+ @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings,
+ @KeyProperties.SignaturePaddingEnum String[] signaturePaddings,
+ @KeyProperties.DigestEnum String[] digests,
+ @KeyProperties.BlockModeEnum String[] blockModes,
+ boolean randomizedEncryptionRequired,
+ boolean userAuthenticationRequired,
+ int userAuthenticationValidityDurationSeconds) {
+ mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
+ mPurposes = purposes;
+ mEncryptionPaddings =
+ ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings));
+ mSignaturePaddings =
+ ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings));
+ mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
+ mUserAuthenticationRequired = userAuthenticationRequired;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Gets the time instant before which the key is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ @Nullable
+ public Date getKeyValidityStart() {
+ return Utils.cloneIfNotNull(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.
+ */
+ @Nullable
+ public Date getKeyValidityForConsumptionEnd() {
+ return Utils.cloneIfNotNull(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.
+ */
+ @Nullable
+ public Date getKeyValidityForOriginationEnd() {
+ return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd);
+ }
+
+ /**
+ * Gets the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used.
+ * Attempts to use the key for any other purpose will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code PURPOSE} flags.
+ */
+ public @KeyProperties.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code PKCS1Padding},
+ * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to use
+ * the key with any other padding scheme will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings);
+ }
+
+ /**
+ * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key
+ * can be used when signing/verifying. Attempts to use the key with any other padding scheme
+ * will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
+ */
+ @NonNull
+ public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() {
+ return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings);
+ }
+
+ /**
+ * Gets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the key
+ * can be used.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ *
+ * @throws IllegalStateException if this set has not been specified.
+ *
+ * @see #isDigestsSpecified()
+ */
+ @NonNull
+ public @KeyProperties.DigestEnum String[] getDigests() {
+ if (mDigests == null) {
+ throw new IllegalStateException("Digests not specified");
+ }
+ return ArrayUtils.cloneIfNotEmpty(mDigests);
+ }
+
+ /**
+ * Returns {@code true} if the set of digest algorithms with which the key can be used has been
+ * specified.
+ *
+ * @see #getDigests()
+ */
+ public boolean isDigestsSpecified() {
+ return mDigests != null;
+ }
+
+ /**
+ * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used
+ * when encrypting/decrypting. Attempts to use the key with any other block modes will be
+ * rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
+ */
+ @NonNull
+ public @KeyProperties.BlockModeEnum String[] getBlockModes() {
+ return ArrayUtils.cloneIfNotEmpty(mBlockModes);
+ }
+
+ /**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
+ * Returns {@code true} if the key is authorized to be used only if the user has been
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setUserAuthenticationRequired(boolean)
+ */
+ public boolean isUserAuthenticationRequired() {
+ return mUserAuthenticationRequired;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect only if user authentication is required
+ * (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return duration in seconds or {@code -1} if authentication is required for every use of the
+ * key.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationValidityDurationSeconds(int)
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Builder of {@link KeyProtection} instances.
+ */
+ public final static class Builder {
+ private @KeyProperties.PurposeEnum int mPurposes;
+
+ private Date mKeyValidityStart;
+ private Date mKeyValidityForOriginationEnd;
+ private Date mKeyValidityForConsumptionEnd;
+ private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings;
+ private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings;
+ private @KeyProperties.DigestEnum String[] mDigests;
+ private @KeyProperties.BlockModeEnum String[] mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+ private boolean mUserAuthenticationRequired;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+
+ /**
+ * Creates a new instance of the {@code Builder}.
+ *
+ * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
+ * used. Attempts to use the key for any other purpose will be rejected.
+ *
+ * <p>See {@link KeyProperties}.{@code PURPOSE} flags.
+ */
+ public Builder(@KeyProperties.PurposeEnum int purposes) {
+ mPurposes = purposes;
+ }
+
+ /**
+ * Sets the time instant before which the key is not yet valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityStart(Date startDate) {
+ mKeyValidityStart = Utils.cloneIfNotNull(startDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityStart(Date)
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ @NonNull
+ 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.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityForOriginationEnd(Date endDate) {
+ mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for decryption and
+ * verification.
+ *
+ * <p>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ @NonNull
+ public Builder setKeyValidityForConsumptionEnd(Date endDate) {
+ mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the set of padding schemes (e.g., {@code OAEPPadding}, {@code PKCS7Padding},
+ * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to
+ * use the key with any other padding scheme will be rejected.
+ *
+ * <p>This must be specified for keys which are used for encryption/decryption.
+ *
+ * <p>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it
+ * is usually necessary to authorize the use of no/any padding
+ * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is
+ * required by some cipher suites, and some stacks request decryption using no padding
+ * whereas others request PKCS#1 padding.
+ *
+ * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants.
+ */
+ @NonNull
+ public Builder setEncryptionPaddings(
+ @KeyProperties.EncryptionPaddingEnum String... paddings) {
+ mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings);
+ return this;
+ }
+
+ /**
+ * Sets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key
+ * can be used when signing/verifying. Attempts to use the key with any other padding scheme
+ * will be rejected.
+ *
+ * <p>This must be specified for RSA keys which are used for signing/verification.
+ *
+ * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants.
+ */
+ @NonNull
+ public Builder setSignaturePaddings(
+ @KeyProperties.SignaturePaddingEnum String... paddings) {
+ mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings);
+ return this;
+ }
+
+ /**
+ * Sets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the
+ * key can be used. Attempts to use the key with any other digest algorithm will be
+ * rejected.
+ *
+ * <p>This must be specified for signing/verification keys and RSA encryption/decryption
+ * keys used with RSA OAEP padding scheme because these operations involve a digest. For
+ * HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()} (e.g.,
+ * {@code SHA-256} for key algorithm {@code HmacSHA256}).
+ *
+ * <p>For private keys used for TLS/SSL client or server authentication it is usually
+ * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is
+ * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use
+ * a private key to sign it.
+ *
+ * <p>See {@link KeyProperties}.{@code DIGEST} constants.
+ */
+ @NonNull
+ public Builder setDigests(@KeyProperties.DigestEnum String... digests) {
+ mDigests = ArrayUtils.cloneIfNotEmpty(digests);
+ return this;
+ }
+
+ /**
+ * Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be
+ * used when encrypting/decrypting. Attempts to use the key with any other block modes will
+ * be rejected.
+ *
+ * <p>This must be specified for symmetric encryption/decryption keys.
+ *
+ * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants.
+ */
+ @NonNull
+ public Builder setBlockModes(@KeyProperties.BlockModeEnum String... blockModes) {
+ mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes);
+ return this;
+ }
+
+ /**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
+ * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code GCM},
+ * {@code CBC}, and {@code CTR} block modes, caller-provided IVs are rejected when
+ * encrypting, to ensure that only random IVs are used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ */
+ @NonNull
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets whether this key is authorized to be used only if the user has been authenticated.
+ *
+ * <p>By default, the key is authorized to be used regardless of whether the user has been
+ * authenticated.
+ *
+ * <p>When user authentication is required:
+ * <ul>
+ * <li>The key can only be import if secure lock screen is set up (see
+ * {@link KeyguardManager#isDeviceSecure()}). Additionally, if the key requires that user
+ * authentication takes place for every use of the key (see
+ * {@link #setUserAuthenticationValidityDurationSeconds(int)}), at least one fingerprint
+ * must be enrolled (see {@link FingerprintManager#hasEnrolledFingerprints()}).</li>
+ * <li>The use of the key must be authorized by the user by authenticating to this Android
+ * device using a subset of their secure lock screen credentials such as
+ * password/PIN/pattern or fingerprint.
+ * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More
+ * information</a>.
+ * <li>The key will become <em>irreversibly invalidated</em> once the secure lock screen is
+ * disabled (reconfigured to None, Swipe or other mode which does not authenticate the user)
+ * or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
+ * Additionally, if the key requires that user authentication takes place for every use of
+ * the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
+ * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
+ * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
+ * </ul>
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see #setUserAuthenticationValidityDurationSeconds(int)
+ * @see KeyguardManager#isDeviceSecure()
+ * @see FingerprintManager#hasEnrolledFingerprints()
+ */
+ @NonNull
+ public Builder setUserAuthenticationRequired(boolean required) {
+ mUserAuthenticationRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets the duration of time (seconds) for which this key is authorized to be used after the
+ * user is successfully authenticated. This has effect if the key requires user
+ * authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>Cryptographic operations involving keys which require user authentication to take
+ * place for every operation can only use fingerprint authentication. This is achieved by
+ * initializing a cryptographic operation ({@link Signature}, {@link Cipher}, {@link Mac})
+ * with the key, wrapping it into a {@link FingerprintManager.CryptoObject}, invoking
+ * {@code FingerprintManager.authenticate} with {@code CryptoObject}, and proceeding with
+ * the cryptographic operation only if the authentication flow succeeds.
+ *
+ * <p>Cryptographic operations involving keys which are authorized to be used for a duration
+ * of time after a successful user authentication event can only use secure lock screen
+ * authentication. These cryptographic operations will throw
+ * {@link UserNotAuthenticatedException} during initialization if the user needs to be
+ * authenticated to proceed. This situation can be resolved by the user unlocking the secure
+ * lock screen of the Android or by going through the confirm credential flow initiated by
+ * {@link KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}.
+ * Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param seconds duration in seconds or {@code -1} if user authentication must take place
+ * for every use of the key.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see FingerprintManager
+ * @see FingerprintManager.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationValidityDurationSeconds(
+ @IntRange(from = -1) int seconds) {
+ if (seconds < -1) {
+ throw new IllegalArgumentException("seconds must be -1 or larger");
+ }
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link KeyProtection}.
+ *
+ * @throws IllegalArgumentException if a required field is missing
+ */
+ @NonNull
+ public KeyProtection build() {
+ return new KeyProtection(
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mEncryptionPaddings,
+ mSignaturePaddings,
+ mDigests,
+ mBlockModes,
+ mRandomizedEncryptionRequired,
+ mUserAuthenticationRequired,
+ mUserAuthenticationValidityDurationSeconds);
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/keystore/KeyStoreConnectException.java
index 1aa3aec..e008976 100644
--- a/keystore/java/android/security/KeyStoreConnectException.java
+++ b/keystore/java/android/security/keystore/KeyStoreConnectException.java
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
+
+import java.security.ProviderException;
/**
* Indicates a communications error with keystore service.
*
* @hide
*/
-public class KeyStoreConnectException extends IllegalStateException {
+public class KeyStoreConnectException extends ProviderException {
public KeyStoreConnectException() {
super("Failed to communicate with keystore service");
}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
index 19abd05..2c709ae 100644
--- a/keystore/java/android/security/KeyStoreCryptoOperation.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
+
+import android.security.KeyStore;
/**
* Cryptographic operation backed by {@link KeyStore}.
@@ -25,7 +27,7 @@ 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.
+ * @return handle or {@code 0} if the KeyStore operation is not in progress.
*/
- Long getOperationHandle();
+ long getOperationHandle();
}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
index 0619199..ea0f4b9 100644
--- a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
import android.security.keymaster.OperationResult;
import libcore.util.EmptyArray;
@@ -33,8 +35,8 @@ import java.io.IOException;
* 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.
+ * {@link #doFinal(byte[], int, int, byte[], byte[]) 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
@@ -42,12 +44,12 @@ import java.io.IOException;
*
* @hide
*/
-public class KeyStoreCryptoOperationChunkedStreamer {
+class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer {
/**
* Bidirectional chunked data stream over a KeyStore crypto operation.
*/
- public interface Stream {
+ interface Stream {
/**
* Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
* be reached.
@@ -58,20 +60,21 @@ public class KeyStoreCryptoOperationChunkedStreamer {
* Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
* be reached.
*/
- OperationResult finish();
+ OperationResult finish(byte[] siganture, byte[] additionalEntropy);
}
// 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 byte[] mBuffered = EmptyArray.BYTE;
private int mBufferedOffset;
private int mBufferedLength;
+ private long mConsumedInputSizeBytes;
+ private long mProducedOutputSizeBytes;
public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_MAX_CHUNK_SIZE);
@@ -82,10 +85,11 @@ public class KeyStoreCryptoOperationChunkedStreamer {
mMaxChunkSize = maxChunkSize;
}
+ @Override
public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
if (inputLength == 0) {
// No input provided
- return EMPTY_BYTE_ARRAY;
+ return EmptyArray.BYTE;
}
ByteArrayOutputStream bufferedOutput = null;
@@ -117,6 +121,7 @@ public class KeyStoreCryptoOperationChunkedStreamer {
// Update input array references to reflect that some of its bytes are now in mBuffered.
inputOffset += inputBytesInChunk;
inputLength -= inputBytesInChunk;
+ mConsumedInputSizeBytes += inputBytesInChunk;
OperationResult opResult = mKeyStoreStream.update(chunk);
if (opResult == null) {
@@ -127,7 +132,7 @@ public class KeyStoreCryptoOperationChunkedStreamer {
if (opResult.inputConsumed == chunk.length) {
// The whole chunk was consumed
- mBuffered = EMPTY_BYTE_ARRAY;
+ mBuffered = EmptyArray.BYTE;
mBufferedOffset = 0;
mBufferedLength = 0;
} else if (opResult.inputConsumed == 0) {
@@ -165,9 +170,10 @@ public class KeyStoreCryptoOperationChunkedStreamer {
}
} else {
// No more output will be produced in this loop
+ byte[] result;
if (bufferedOutput == null) {
// No previously buffered output
- return opResult.output;
+ result = opResult.output;
} else {
// There was some previously buffered output
try {
@@ -175,25 +181,31 @@ public class KeyStoreCryptoOperationChunkedStreamer {
} catch (IOException e) {
throw new IllegalStateException("Failed to buffer output", e);
}
- return bufferedOutput.toByteArray();
+ result = bufferedOutput.toByteArray();
}
+ mProducedOutputSizeBytes += result.length;
+ return result;
}
}
}
+ byte[] result;
if (bufferedOutput == null) {
// No output produced
- return EMPTY_BYTE_ARRAY;
+ result = EmptyArray.BYTE;
} else {
- return bufferedOutput.toByteArray();
+ result = bufferedOutput.toByteArray();
}
+ mProducedOutputSizeBytes += result.length;
+ return result;
}
- public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
- throws KeyStoreException {
+ @Override
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
if (inputLength == 0) {
// No input provided -- simplify the rest of the code
- input = EMPTY_BYTE_ARRAY;
+ input = EmptyArray.BYTE;
inputOffset = 0;
}
@@ -201,20 +213,17 @@ public class KeyStoreCryptoOperationChunkedStreamer {
byte[] output = update(input, inputOffset, inputLength);
output = ArrayUtils.concat(output, flush());
- OperationResult opResult = mKeyStoreStream.finish();
+ OperationResult opResult = mKeyStoreStream.finish(signature, additionalEntropy);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
throw KeyStore.getKeyStoreException(opResult.resultCode);
}
+ mProducedOutputSizeBytes += opResult.output.length;
return ArrayUtils.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 KeyStoreException {
if (mBufferedLength <= 0) {
return EmptyArray.BYTE;
@@ -240,7 +249,19 @@ public class KeyStoreCryptoOperationChunkedStreamer {
+ " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed);
}
- return (opResult.output != null) ? opResult.output : EmptyArray.BYTE;
+ byte[] result = (opResult.output != null) ? opResult.output : EmptyArray.BYTE;
+ mProducedOutputSizeBytes += result.length;
+ return result;
+ }
+
+ @Override
+ public long getConsumedInputSizeBytes() {
+ return mConsumedInputSizeBytes;
+ }
+
+ @Override
+ public long getProducedOutputSizeBytes() {
+ return mProducedOutputSizeBytes;
}
/**
@@ -265,8 +286,8 @@ public class KeyStoreCryptoOperationChunkedStreamer {
}
@Override
- public OperationResult finish() {
- return mKeyStore.finish(mOperationToken, null, null);
+ public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
+ return mKeyStore.finish(mOperationToken, null, signature, additionalEntropy);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
new file mode 100644
index 0000000..062c2d4
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.keystore;
+
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+
+/**
+ * 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, byte[], byte[]) doFinal} operations which can be used to
+ * conveniently implement various JCA crypto primitives.
+ *
+ * @hide
+ */
+interface KeyStoreCryptoOperationStreamer {
+ byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
+ byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
+ byte[] additionalEntropy) throws KeyStoreException;
+ long getConsumedInputSizeBytes();
+ long getProducedOutputSizeBytes();
+}
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java
new file mode 100644
index 0000000..27c1b2a
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.keystore;
+
+import android.security.KeyStore;
+import android.security.keymaster.KeymasterDefs;
+
+import libcore.util.EmptyArray;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+
+/**
+ * Assorted utility methods for implementing crypto operations on top of KeyStore.
+ *
+ * @hide
+ */
+abstract class KeyStoreCryptoOperationUtils {
+
+ private static volatile SecureRandom sRng;
+
+ private KeyStoreCryptoOperationUtils() {}
+
+ /**
+ * Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of
+ * the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if
+ * the {@code init} method should succeed.
+ */
+ static InvalidKeyException getInvalidKeyExceptionForInit(
+ KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) {
+ if (beginOpResultCode == KeyStore.NO_ERROR) {
+ return null;
+ }
+
+ // An error occured. However, some errors should not lead to init throwing an exception.
+ // See below.
+ InvalidKeyException e =
+ keyStore.getInvalidKeyException(key.getAlias(), beginOpResultCode);
+ switch (beginOpResultCode) {
+ case KeyStore.OP_AUTH_NEEDED:
+ // Operation needs to be authorized by authenticating the user. Don't throw an
+ // exception is such authentication is possible for this key
+ // (UserNotAuthenticatedException). An example of when it's not possible is where
+ // the key is permanently invalidated (KeyPermanentlyInvalidatedException).
+ if (e instanceof UserNotAuthenticatedException) {
+ return null;
+ }
+ break;
+ }
+ return e;
+ }
+
+ /**
+ * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation
+ * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method
+ * should succeed.
+ */
+ public static GeneralSecurityException getExceptionForCipherInit(
+ KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) {
+ if (beginOpResultCode == KeyStore.NO_ERROR) {
+ return null;
+ }
+
+ // Cipher-specific cases
+ switch (beginOpResultCode) {
+ case KeymasterDefs.KM_ERROR_INVALID_NONCE:
+ return new InvalidAlgorithmParameterException("Invalid IV");
+ case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED:
+ return new InvalidAlgorithmParameterException("Caller-provided IV not permitted");
+ }
+
+ // General cases
+ return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode);
+ }
+
+ /**
+ * Returns the requested number of random bytes to mix into keystore/keymaster RNG.
+ *
+ * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default
+ * RNG.
+ */
+ static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) {
+ if (sizeBytes <= 0) {
+ return EmptyArray.BYTE;
+ }
+ if (rng == null) {
+ rng = getRng();
+ }
+ byte[] result = new byte[sizeBytes];
+ rng.nextBytes(result);
+ return result;
+ }
+
+ private static SecureRandom getRng() {
+ // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is
+ // required to be thread-safe.
+ if (sRng == null) {
+ sRng = new SecureRandom();
+ }
+ return sRng;
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
new file mode 100644
index 0000000..92d636c
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -0,0 +1,136 @@
+/*
+ * 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.keystore;
+
+import android.hardware.fingerprint.FingerprintManager;
+import android.security.GateKeeper;
+import android.security.KeyStore;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+/**
+ * @hide
+ */
+public abstract class KeymasterUtils {
+
+ private KeymasterUtils() {}
+
+ public static int getDigestOutputSizeBits(int keymasterDigest) {
+ switch (keymasterDigest) {
+ case KeymasterDefs.KM_DIGEST_NONE:
+ return -1;
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return 128;
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return 160;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return 224;
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return 256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return 384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return 512;
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + keymasterDigest);
+ }
+ }
+
+ public static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ int keymasterBlockMode) {
+ switch (keymasterBlockMode) {
+ case KeymasterDefs.KM_MODE_ECB:
+ return false;
+ case KeymasterDefs.KM_MODE_CBC:
+ case KeymasterDefs.KM_MODE_CTR:
+ case KeymasterDefs.KM_MODE_GCM:
+ return true;
+ default:
+ throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode);
+ }
+ }
+
+ public static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ int keymasterPadding) {
+ switch (keymasterPadding) {
+ case KeymasterDefs.KM_PAD_NONE:
+ return false;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return true;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported asymmetric encryption padding scheme: " + keymasterPadding);
+ }
+ }
+
+ /**
+ * Adds keymaster arguments to express the key's authorization policy supported by user
+ * authentication.
+ *
+ * @param userAuthenticationRequired whether user authentication is required to authorize the
+ * use of the key.
+ * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user
+ * authentication is valid as authorization for using the key or {@code -1} if every
+ * use of the key needs authorization.
+ *
+ * @throws IllegalStateException if user authentication is required but the system is in a wrong
+ * state (e.g., secure lock screen not set up) for generating or importing keys that
+ * require user authentication.
+ */
+ public static void addUserAuthArgs(KeymasterArguments args,
+ boolean userAuthenticationRequired,
+ int userAuthenticationValidityDurationSeconds) {
+ if (!userAuthenticationRequired) {
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+ return;
+ }
+
+ if (userAuthenticationValidityDurationSeconds == -1) {
+ // Every use of this key needs to be authorized by the user. This currently means
+ // fingerprint-only auth.
+ FingerprintManager fingerprintManager =
+ KeyStore.getApplicationContext().getSystemService(FingerprintManager.class);
+ // TODO: Restore USE_FINGERPRINT permission check in
+ // FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
+ long fingerprintOnlySid =
+ (fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0;
+ if (fingerprintOnlySid == 0) {
+ throw new IllegalStateException(
+ "At least one fingerprint must be enrolled to create keys requiring user"
+ + " authentication for every use");
+ }
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(fingerprintOnlySid));
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+ } else {
+ // The key is authorized for use for the specified amount of time after the user has
+ // authenticated. Whatever unlocks the secure lock screen should authorize this key.
+ long rootSid = GateKeeper.getSecureUserId();
+ if (rootSid == 0) {
+ throw new IllegalStateException("Secure lock screen must be enabled"
+ + " to create keys requiring user authentication");
+ }
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(rootSid));
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
+ KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
+ userAuthenticationValidityDurationSeconds);
+ }
+ }
+}
diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/keystore/UserNotAuthenticatedException.java
index d0410b8..21f861c 100644
--- a/keystore/java/android/security/UserNotAuthenticatedException.java
+++ b/keystore/java/android/security/keystore/UserNotAuthenticatedException.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import java.security.InvalidKeyException;
/**
* Indicates that a cryptographic operation could not be performed because the user has not been
- * authenticated recently enough.
- *
- * @hide
+ * authenticated recently enough. Authenticating the user will resolve this issue.
*/
public class UserNotAuthenticatedException extends InvalidKeyException {
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
new file mode 100644
index 0000000..9bec682
--- /dev/null
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -0,0 +1,32 @@
+/*
+ * 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.keystore;
+
+import java.util.Date;
+
+/**
+ * Assorted utility methods.
+ *
+ * @hide
+ */
+abstract class Utils {
+ private Utils() {}
+
+ static Date cloneIfNotNull(Date value) {
+ return (value != null) ? (Date) value.clone() : null;
+ }
+}
diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
index 681a9ff..bc8dd13 100644
--- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
+++ b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
@@ -24,11 +24,6 @@ import java.util.Date;
import javax.security.auth.x500.X500Principal;
public class KeyPairGeneratorSpecTest extends AndroidTestCase {
- private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
- private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
- private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1980
- private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
-
private static final String TEST_ALIAS_1 = "test1";
private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
@@ -110,37 +105,46 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase {
}
}
- public void testConstructor_NullSubjectDN_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec(
- getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- assertEquals(DEFAULT_CERT_SUBJECT, spec.getSubjectDN());
+ public void testConstructor_NullSubjectDN_Failure() throws Exception {
+ try {
+ new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
+ NOW_PLUS_10_YEARS, 0);
+ fail("Should throw IllegalArgumentException when subjectDN is null");
+ } catch (IllegalArgumentException success) {
+ }
}
- public void testConstructor_NullSerial_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec(
- getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
- NOW_PLUS_10_YEARS, 0);
- assertEquals(DEFAULT_CERT_SERIAL_NUMBER, spec.getSerialNumber());
+ public void testConstructor_NullSerial_Failure() throws Exception {
+ try {
+ new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
+ NOW_PLUS_10_YEARS, 0);
+ fail("Should throw IllegalArgumentException when startDate is null");
+ } catch (IllegalArgumentException success) {
+ }
}
- public void testConstructor_NullStartDate_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec(
- getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, null,
- NOW_PLUS_10_YEARS, 0);
- assertEquals(DEFAULT_CERT_NOT_BEFORE, spec.getStartDate());
+ public void testConstructor_NullStartDate_Failure() throws Exception {
+ try {
+ new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
+ null, NOW_PLUS_10_YEARS, 0);
+ fail("Should throw IllegalArgumentException when startDate is null");
+ } catch (IllegalArgumentException success) {
+ }
}
- public void testConstructor_NullEndDate_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec(
- getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW, null, 0);
- assertEquals(DEFAULT_CERT_NOT_AFTER, spec.getEndDate());
+ public void testConstructor_NullEndDate_Failure() throws Exception {
+ try {
+ new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
+ NOW, null, 0);
+ fail("Should throw IllegalArgumentException when keystoreAlias is null");
+ } catch (IllegalArgumentException success) {
+ }
}
public void testConstructor_EndBeforeStart_Failure() throws Exception {
try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1,
- SERIAL_1, NOW_PLUS_10_YEARS, NOW, 0);
+ new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
+ NOW_PLUS_10_YEARS, NOW, 0);
fail("Should throw IllegalArgumentException when end is before start");
} catch (IllegalArgumentException success) {
}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 916b1ba..319cf32 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -20,8 +20,6 @@ import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
-import android.os.ServiceManager;
-import android.security.KeyStore;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
@@ -35,13 +33,9 @@ import android.test.suitebuilder.annotation.MediumTest;
import com.android.org.conscrypt.NativeConstants;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import java.util.Date;
import java.util.HashSet;
import java.security.spec.RSAKeyGenParameterSpec;
-import android.util.Log;
-import android.util.Base64;
-
/**
* Junit / Instrumentation test case for KeyStore class
*
@@ -152,13 +146,13 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testPassword() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
}
public void testGet() throws Exception {
assertNull(mKeyStore.get(TEST_KEYNAME));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertNull(mKeyStore.get(TEST_KEYNAME));
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
KeyStore.FLAG_ENCRYPTED));
@@ -170,7 +164,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
KeyStore.FLAG_ENCRYPTED));
assertFalse(mKeyStore.contains(TEST_KEYNAME));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
KeyStore.FLAG_ENCRYPTED));
assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
@@ -181,7 +175,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
KeyStore.FLAG_ENCRYPTED));
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
KeyStore.FLAG_ENCRYPTED));
assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
@@ -192,7 +186,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
KeyStore.FLAG_ENCRYPTED));
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
KeyStore.FLAG_ENCRYPTED));
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
@@ -202,7 +196,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
KeyStore.FLAG_ENCRYPTED));
assertFalse(mKeyStore.contains(TEST_I18N_KEY));
- mKeyStore.password(TEST_I18N_KEY);
+ mKeyStore.onUserPasswordChanged(TEST_I18N_KEY);
assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
KeyStore.FLAG_ENCRYPTED));
assertTrue(mKeyStore.contains(TEST_I18N_KEY));
@@ -210,7 +204,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testDelete() throws Exception {
assertFalse(mKeyStore.delete(TEST_KEYNAME));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertFalse(mKeyStore.delete(TEST_KEYNAME));
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
@@ -222,7 +216,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testDelete_grantedUid_Wifi() throws Exception {
assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
@@ -234,7 +228,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testDelete_ungrantedUid_Bluetooth() throws Exception {
assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
@@ -247,7 +241,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testContains() throws Exception {
assertFalse(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.contains(TEST_KEYNAME));
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
@@ -258,7 +252,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testContains_grantedUid_Wifi() throws Exception {
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
@@ -269,7 +263,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testContains_grantedUid_Bluetooth() throws Exception {
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
@@ -277,58 +271,58 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
}
- public void testSaw() throws Exception {
- String[] emptyResult = mKeyStore.saw(TEST_KEYNAME);
+ public void testList() throws Exception {
+ String[] emptyResult = mKeyStore.list(TEST_KEYNAME);
assertNotNull(emptyResult);
assertEquals(0, emptyResult.length);
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- String[] results = mKeyStore.saw(TEST_KEYNAME);
+ String[] results = mKeyStore.list(TEST_KEYNAME);
assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
new HashSet(Arrays.asList(results)));
}
- public void testSaw_ungrantedUid_Bluetooth() throws Exception {
- String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID);
+ public void testList_ungrantedUid_Bluetooth() throws Exception {
+ String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
assertEquals(0, results1.length);
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID);
+ String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
assertEquals(0, results2.length);
}
- public void testSaw_grantedUid_Wifi() throws Exception {
- String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.WIFI_UID);
+ public void testList_grantedUid_Wifi() throws Exception {
+ String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
assertNotNull(results1);
assertEquals(0, results1.length);
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
- String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.WIFI_UID);
+ String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
new HashSet(Arrays.asList(results2)));
}
- public void testSaw_grantedUid_Vpn() throws Exception {
- String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.VPN_UID);
+ public void testList_grantedUid_Vpn() throws Exception {
+ String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
assertNotNull(results1);
assertEquals(0, results1.length);
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
- String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.VPN_UID);
+ String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
new HashSet(Arrays.asList(results2)));
@@ -337,7 +331,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testLock() throws Exception {
assertFalse(mKeyStore.lock());
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
assertTrue(mKeyStore.lock());
@@ -345,7 +339,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testUnlock() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
mKeyStore.lock();
@@ -355,7 +349,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testIsEmpty() throws Exception {
assertTrue(mKeyStore.isEmpty());
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertTrue(mKeyStore.isEmpty());
mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
assertFalse(mKeyStore.isEmpty());
@@ -370,7 +364,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGenerate_Locked_Fail() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
mKeyStore.lock();
assertFalse("Should fail when keystore is locked",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -378,7 +372,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGenerate_Success() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key when unlocked",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -388,7 +382,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGenerate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key when unlocked",
mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeConstants.EVP_PKEY_RSA,
@@ -398,7 +392,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID,
NativeConstants.EVP_PKEY_RSA, RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
@@ -408,7 +402,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testImport_Success() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -417,7 +411,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testImport_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
@@ -426,7 +420,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testImport_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID,
KeyStore.FLAG_ENCRYPTED));
@@ -436,7 +430,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testImport_Failure_BadEncoding() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertFalse("Invalid DER-encoded key should not be imported", mKeyStore.importKey(
TEST_KEYNAME, TEST_DATA, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -445,7 +439,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testSign_Success() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
@@ -456,7 +450,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testVerify_Success() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
@@ -475,7 +469,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testSign_NotGenerated_Failure() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
assertNull("Should not be able to sign without first generating keys",
mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
@@ -483,7 +477,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testGrant_Generated_Success() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key for testcase",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -494,7 +488,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGrant_Imported_Success() throws Exception {
- assertTrue("Password should work for keystore", mKeyStore.password(TEST_PASSWD));
+ assertTrue("Password should work for keystore", mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -504,7 +498,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testGrant_NoKey_Failure() throws Exception {
assertTrue("Should be able to unlock keystore for test",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse("Should not be able to grant without first initializing the keystore",
mKeyStore.grant(TEST_KEYNAME, 0));
@@ -517,7 +511,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testUngrant_Generated_Success() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key for testcase",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -532,7 +526,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testUngrant_Imported_Success() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -551,7 +545,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testUngrant_NoGrant_Failure() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key for testcase",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -563,7 +557,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testUngrant_DoubleUngrant_Failure() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key for testcase",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -581,7 +575,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to generate key for testcase",
mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
@@ -601,7 +595,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testDuplicate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.contains(TEST_KEYNAME));
@@ -640,7 +634,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testDuplicate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.password(TEST_PASSWD));
+ assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertFalse(mKeyStore.contains(TEST_KEYNAME));
@@ -666,7 +660,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testGetmtime_Success() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -697,7 +691,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testGetmtime_NonExist_Failure() throws Exception {
assertTrue("Password should work for keystore",
- mKeyStore.password(TEST_PASSWD));
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD));
assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
@@ -708,14 +702,13 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
private KeyCharacteristics generateRsaKey(String name) throws Exception {
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
- RSAKeyGenParameterSpec.F4.longValue());
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
@@ -732,14 +725,13 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
byte[] entropy = new byte[] {1,2,3,4,5};
String name = "test";
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
- RSAKeyGenParameterSpec.F4.longValue());
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int result = mKeyStore.generateKey(name, args, entropy, 0, outCharacteristics);
@@ -752,7 +744,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
}
public void testGetKeyCharacteristicsSuccess() throws Exception {
- mKeyStore.password(TEST_PASSWD);
+ mKeyStore.onUserPasswordChanged(TEST_PASSWD);
String name = "test";
KeyCharacteristics gen = generateRsaKey(name);
KeyCharacteristics call = new KeyCharacteristics();
@@ -765,16 +757,15 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
String name = "test";
byte[] id = new byte[] {0x01, 0x02, 0x03};
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addBlob(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
- args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
- RSAKeyGenParameterSpec.F4.longValue());
+ args.addBytes(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
@@ -801,30 +792,32 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testAesGcmEncryptSuccess() throws Exception {
String name = "test";
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
- args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
- KeymasterArguments out = new KeymasterArguments();
args = new KeymasterArguments();
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 128);
OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null, out);
+ true, args, null);
IBinder token = result.token;
assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
assertEquals("Finish should succeed", KeyStore.NO_ERROR,
mKeyStore.finish(token, null, null).resultCode);
+ // TODO: Assert that an AEAD tag was returned by finish
}
public void testBadToken() throws Exception {
@@ -836,20 +829,19 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
private int importAesKey(String name, byte[] key, int size, int mode) {
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0,
new KeyCharacteristics());
}
private byte[] doOperation(String name, int purpose, byte[] in, KeymasterArguments beginArgs) {
- KeymasterArguments out = new KeymasterArguments();
OperationResult result = mKeyStore.begin(name, purpose,
- true, beginArgs, null, out);
+ true, beginArgs, null);
assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
IBinder token = result.token;
result = mKeyStore.update(token, null, in);
@@ -881,14 +873,18 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
hexToBytes("591ccb10d410ed26dc5ba74a31362870"),
hexToBytes("b6ed21b99ca6f4f9f153e7b1beafed1d"),
hexToBytes("23304b7a39f9f3ff067d8d8f9e24ecc7")};
+ KeymasterArguments beginArgs = new KeymasterArguments();
+ beginArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ beginArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
+ beginArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
for (int i = 0; i < testVectors.length; i++) {
byte[] cipherText = doOperation(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, testVectors[i],
- new KeymasterArguments());
+ beginArgs);
MoreAsserts.assertEquals(cipherVectors[i], cipherText);
}
for (int i = 0; i < testVectors.length; i++) {
byte[] plainText = doOperation(name, KeymasterDefs.KM_PURPOSE_DECRYPT,
- cipherVectors[i], new KeymasterArguments());
+ cipherVectors[i], beginArgs);
MoreAsserts.assertEquals(testVectors[i], plainText);
}
}
@@ -898,28 +894,29 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testOperationPruning() throws Exception {
String name = "test";
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
- KeymasterArguments out = new KeymasterArguments();
args = new KeymasterArguments();
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null, out);
+ true, args, null);
assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
IBinder first = result.token;
// Implementation detail: softkeymaster supports 16 concurrent operations
for (int i = 0; i < 16; i++) {
- result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null,
- out);
+ result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null);
assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
}
// At this point the first operation should be pruned.
@@ -930,24 +927,48 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testAuthNeeded() throws Exception {
String name = "test";
KeymasterArguments args = new KeymasterArguments();
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
- args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
+ args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
+ args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- KeymasterArguments out = new KeymasterArguments();
assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null, out);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
+ true, args, null);
+ assertEquals("Begin should expect authorization", KeyStore.OP_AUTH_NEEDED,
+ result.resultCode);
IBinder token = result.token;
result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
assertEquals("Update should require authorization",
KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode);
}
+
+ public void testPasswordRemovalEncryptedEntry() throws Exception {
+ mKeyStore.onUserPasswordChanged("test");
+ assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+ KeyStore.FLAG_ENCRYPTED));
+ assertTrue(mKeyStore.contains(TEST_KEYNAME));
+ assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
+ mKeyStore.onUserPasswordChanged("");
+ // Removing the password should have deleted all entries using FLAG_ENCRYPTED
+ assertNull(mKeyStore.get(TEST_KEYNAME));
+ assertFalse(mKeyStore.contains(TEST_KEYNAME));
+ }
+
+ public void testPasswordRemovalUnencryptedEntry() throws Exception {
+ mKeyStore.onUserPasswordChanged("test");
+ assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+ KeyStore.FLAG_NONE));
+ assertTrue(mKeyStore.contains(TEST_KEYNAME));
+ assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
+ mKeyStore.onUserPasswordChanged("");
+ // Removing the password should not delete unencrypted entries.
+ assertTrue(mKeyStore.contains(TEST_KEYNAME));
+ assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
+ }
}
diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
index 95d14b7..e5c15c5 100644
--- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
+++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
@@ -14,24 +14,33 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
+import android.security.Credentials;
+import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeymasterDefs;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.KeyPair;
+import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.security.interfaces.ECKey;
import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
@@ -73,10 +82,10 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
}
private void setupPassword() {
- assertTrue(mAndroidKeyStore.password("1111"));
+ assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
assertTrue(mAndroidKeyStore.isUnlocked());
- String[] aliases = mAndroidKeyStore.saw("");
+ String[] aliases = mAndroidKeyStore.list("");
assertNotNull(aliases);
assertEquals(0, aliases.length);
}
@@ -153,6 +162,26 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
}
public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+ generator.initialize(new KeyGenParameterSpec.Builder(
+ TEST_ALIAS_1,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setCertificateSubject(TEST_DN_1)
+ .setCertificateSerialNumber(TEST_SERIAL_1)
+ .setCertificateNotBefore(NOW)
+ .setCertificateNotAfter(NOW_PLUS_10_YEARS)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .build());
+
+ final KeyPair pair = generator.generateKeyPair();
+ assertNotNull("The KeyPair returned should not be null", pair);
+
+ assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+ NOW_PLUS_10_YEARS);
+ }
+
+ public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
+ throws Exception {
mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
.setAlias(TEST_ALIAS_1)
.setKeyType("EC")
@@ -288,7 +317,7 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
} catch (IllegalStateException expected) {
}
- assertTrue(mAndroidKeyStore.password("1111"));
+ assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
assertTrue(mAndroidKeyStore.isUnlocked());
final KeyPair pair2 = mGenerator.generateKeyPair();
@@ -323,19 +352,40 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
assertEquals(keyType, privKey.getAlgorithm());
+ if ("EC".equalsIgnoreCase(keyType)) {
+ assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
+ privKey instanceof ECKey);
+ assertEquals("Private and public key must have the same EC parameters",
+ ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
+ } else if ("RSA".equalsIgnoreCase(keyType)) {
+ assertTrue("RSA private key must be instance of RSAKey: "
+ + privKey.getClass().getName(),
+ privKey instanceof RSAKey);
+ assertEquals("Private and public key must have the same RSA modulus",
+ ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
+ }
+
final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
- final Certificate userCert = cf
- .generateCertificate(new ByteArrayInputStream(userCertBytes));
+ final Certificate userCert =
+ cf.generateCertificate(new ByteArrayInputStream(userCertBytes));
assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
final X509Certificate x509userCert = (X509Certificate) userCert;
+ assertEquals(
+ "Public key used to sign certificate should have the same algorithm as in KeyPair",
+ pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
+
assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
- pubKey, x509userCert.getPublicKey());
+ pubKey,
+ AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
+ Credentials.USER_PRIVATE_KEY + alias,
+ x509userCert.getPublicKey().getAlgorithm(),
+ x509userCert.getPublicKey().getEncoded()));
assertEquals("The Subject DN should be the one passed into the params", dn,
x509userCert.getSubjectDN());
@@ -352,14 +402,22 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
assertDateEquals("The notAfter date should be the one passed into the params", end,
x509userCert.getNotAfter());
+ // Assert that the cert's signature verifies using the public key from generated KeyPair
x509userCert.verify(pubKey);
+ // Assert that the cert's signature verifies using the public key from the cert itself.
+ x509userCert.verify(x509userCert.getPublicKey());
final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
- final byte[] pubKeyBytes = mAndroidKeyStore.getPubkey(Credentials.USER_PRIVATE_KEY + alias);
+ ExportResult exportResult = mAndroidKeyStore.exportKey(
+ Credentials.USER_PRIVATE_KEY + alias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
+ assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
+ final byte[] pubKeyBytes = exportResult.exportData;
assertNotNull("The keystore should return the public key for the generated key",
pubKeyBytes);
+ assertTrue("Public key X.509 format should be as expected",
+ Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
}
private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
index a7046dd..c3b731b 100644
--- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
@@ -14,38 +14,36 @@
* limitations under the License.
*/
-package android.security;
+package android.security.keystore;
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
import com.android.org.conscrypt.NativeConstants;
-import com.android.org.conscrypt.OpenSSLEngine;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.KeyStoreParameter;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
-import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
+import java.security.KeyPair;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.spec.InvalidKeySpecException;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -736,10 +734,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
}
private void setupPassword() {
- assertTrue(mAndroidKeyStore.password("1111"));
+ assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
assertTrue(mAndroidKeyStore.isUnlocked());
- assertEquals(0, mAndroidKeyStore.saw("").length);
+ assertEquals(0, mAndroidKeyStore.list("").length);
}
private void assertAliases(final String[] expectedAliases) throws KeyStoreException {
@@ -1198,14 +1196,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
- if (expectedKey instanceof ECPrivateKey) {
+ if (expectedKey instanceof ECKey) {
assertEquals("Returned PrivateKey should be what we inserted",
- ((ECPrivateKey) expectedKey).getParams().getCurve(),
- ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
- } else if (expectedKey instanceof RSAPrivateKey) {
+ ((ECKey) expectedKey).getParams().getCurve(),
+ ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
+ } else if (expectedKey instanceof RSAKey) {
assertEquals("Returned PrivateKey should be what we inserted",
- ((RSAPrivateKey) expectedKey).getModulus(),
- ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
+ ((RSAKey) expectedKey).getModulus(),
+ ((RSAKey) keyEntry.getPrivateKey()).getModulus());
}
assertEquals("Returned Certificate should be what we inserted", expectedCert,
@@ -1258,15 +1256,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
assertNotNull("Key should exist", key);
- assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey);
-
- RSAPrivateKey actualKey = (RSAPrivateKey) key;
+ assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
+ assertTrue("Should be a RSAKey", key instanceof RSAKey);
KeyFactory keyFact = KeyFactory.getInstance("RSA");
PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
assertEquals("Inserted key should be same as retrieved key",
- ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
+ ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
}
public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
@@ -1282,15 +1279,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
assertNotNull("Key should exist", key);
- assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey);
-
- RSAPrivateKey actualKey = (RSAPrivateKey) key;
+ assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
+ assertTrue("Should be a RSAKey", key instanceof RSAKey);
KeyFactory keyFact = KeyFactory.getInstance("RSA");
PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
assertEquals("Inserted key should be same as retrieved key",
- ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
+ ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
}
public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
@@ -1319,9 +1315,9 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
}
public void testKeyStore_GetType_Encrypted_Success() throws Exception {
- assertEquals(AndroidKeyStore.NAME, mKeyStore.getType());
+ assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
setupPassword();
- assertEquals(AndroidKeyStore.NAME, mKeyStore.getType());
+ assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
}
public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception {
@@ -1921,28 +1917,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
Date notAfter) throws Exception {
final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
- final PrivateKey privKey;
- final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
- try {
- privKey = engine.getPrivateKeyById(privateKeyAlias);
- } catch (InvalidKeyException e) {
- throw new RuntimeException("Can't get key", e);
- }
-
- final byte[] pubKeyBytes = keyStore.getPubkey(privateKeyAlias);
-
- final PublicKey pubKey;
- try {
- final KeyFactory keyFact = KeyFactory.getInstance("RSA");
- pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Can't instantiate RSA key generator", e);
- } catch (InvalidKeySpecException e) {
- throw new IllegalStateException("keystore returned invalid key encoding", e);
- }
+ KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ keyStore, privateKeyAlias);
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
- certGen.setPublicKey(pubKey);
+ certGen.setPublicKey(keyPair.getPublic());
certGen.setSerialNumber(serialNumber);
certGen.setSubjectDN(subjectDN);
certGen.setIssuerDN(subjectDN);
@@ -1950,7 +1929,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
certGen.setNotAfter(notAfter);
certGen.setSignatureAlgorithm("sha1WithRSA");
- final X509Certificate cert = certGen.generate(privKey);
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate());
return cert;
}
@@ -2089,7 +2068,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
} catch (KeyStoreException success) {
}
- assertTrue(mAndroidKeyStore.password("1111"));
+ assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
assertTrue(mAndroidKeyStore.isUnlocked());
mKeyStore.setEntry(TEST_ALIAS_1, entry,