blob: 91aac8367976e099625cb1c2737c91e82075169f [file] [log] [blame]
/*
* 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
* {@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, mSpec.isStrongBoxBacked());
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 | KeyPermanentlyInvalidatedException 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,
boolean isStrongBoxBacked)
throws InvalidAlgorithmParameterException {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
if (isStrongBoxBacked && keySize != 256) {
throw new InvalidAlgorithmParameterException(
"Unsupported StrongBox EC key size: "
+ keySize + " bits. Supported: 256");
}
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;
}
}