/* * 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.KeymasterCertificateChain; 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.io.ByteArrayOutputStream; import java.io.IOException; 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.CertificateParsingException; 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.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; 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 int mEntryUid; 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; } @SuppressWarnings("deprecation") @Override public void initialize(int keysize, SecureRandom random) { throw new IllegalArgumentException( KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName() + " required to initialize this KeyPairGenerator"); } @SuppressWarnings("deprecation") @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). // MD5 was never offered for Android Keystore for ECDSA. specBuilder.setDigests( KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512); 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, KeyProperties.DIGEST_MD5, KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512); // Authorized to be used with any encryption and signature padding // schemes (including no padding). specBuilder.setEncryptionPaddings( KeyProperties.ENCRYPTION_PADDING_NONE, KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); specBuilder.setSignaturePaddings( KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS); // 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(); mEntryUid = spec.getUid(); 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(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment()); } 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; mEntryUid = KeyStore.UID_SELF; 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"); } byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, (mKeySizeBits + 7) / 8); Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; boolean success = false; try { generateKeystoreKeyPair( privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags); KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias); storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair)); success = true; return keyPair; } finally { if (!success) { Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); } } } private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair) throws ProviderException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { KeymasterArguments args = new KeymasterArguments(); args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge); return getAttestationChain(privateKeyAlias, keyPair, args); } // Very short certificate chain in the non-attestation case. return Collections.singleton(generateSelfSignedCertificateBytes(keyPair)); } private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args, byte[] additionalEntropy, final int flags) throws ProviderException { KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy, mEntryUid, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { throw new ProviderException( "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode)); } } private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException { try { KeyPair result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( mKeyStore, privateKeyAlias, mEntryUid); if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) { throw new ProviderException( "Generated key pair algorithm does not match requested algorithm: " + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm); } return result; } catch (UnrecoverableKeyException e) { throw new ProviderException("Failed to load generated key pair from keystore", e); } } private KeymasterArguments constructKeyGenerationArguments() { 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(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment()); 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); if (mSpec.isUniqueIdIncluded()) args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); return args; } private void storeCertificateChain(final int flags, Iterable<byte[]> iterable) throws ProviderException { Iterator<byte[]> iter = iterable.iterator(); storeCertificate( Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate"); if (!iter.hasNext()) { return; } ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream(); while (iter.hasNext()) { byte[] data = iter.next(); certificateConcatenationStream.write(data, 0, data.length); } storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(), flags, "Failed to store attestation CA certificate"); } private void storeCertificate(String prefix, byte[] certificateBytes, final int flags, String failureMessage) throws ProviderException { int insertErrorCode = mKeyStore.insert( prefix + mEntryAlias, certificateBytes, mEntryUid, flags); if (insertErrorCode != KeyStore.NO_ERROR) { throw new ProviderException(failureMessage, KeyStore.getKeyStoreException(insertErrorCode)); } } private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException { try { return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic()) .getEncoded(); } catch (IOException | CertificateParsingException e) { throw new ProviderException("Failed to generate self-signed certificate", e); } catch (CertificateEncodingException e) { throw new ProviderException( "Failed to obtain encoded form of self-signed certificate", e); } } private Iterable<byte[]> getAttestationChain(String privateKeyAlias, KeyPair keyPair, KeymasterArguments args) throws ProviderException { KeymasterCertificateChain outChain = new KeymasterCertificateChain(); int errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain); if (errorCode != KeyStore.NO_ERROR) { throw new ProviderException("Failed to generate attestation certificate chain", KeyStore.getKeyStoreException(errorCode)); } Collection<byte[]> chain = outChain.getCertificates(); if (chain.size() < 2) { throw new ProviderException("Attestation certificate chain contained " + chain.size() + " entries. At least two are required."); } return chain; } 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 CertificateParsingException, IOException { 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 IOException, CertificateParsingException { 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 = com.android.internal.util.ArrayUtils.contains( KeyProperties.SignaturePadding.allToKeymaster( spec.getSignaturePaddings()), KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); 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); } Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests); result.retainAll(authorizedKeymasterKeyDigests); return result; } }