/*
 * 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.GateKeeper;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.KeyStoreException;
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 KeyStore.getInstance(String) using the "AndroidKeyStore" type.

This class can not be directly instantiated and must instead be used via the KeyPairGenerator.getInstance("AndroidKeyStore") API.

@hide
/** * 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); } 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"); } 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"); } if (mSpec.isStrongBoxBacked()) { flags |= KeyStore.FLAG_STRONGBOX; } 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; } catch (ProviderException e) { if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { throw new SecureKeyImportUnavailableException(e); } else { throw e; } } 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) { if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) { throw new StrongBoxUnavailableException("Failed to generate key pair"); } else { 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); 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 Signature algorithm to be used for signing a certificate using the specified key or null if the key cannot be used for signing a certificate.
/** * 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; } }