diff options
Diffstat (limited to 'keystore')
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()) { + * // The key is bound to the secure hardware of this Android + * }}</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("CN=myKey")).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 { + * keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</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 { + * keyInfo = factory.getKeySpec(key, KeyInfo.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</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, |