| /* |
| * 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; |
| } |
| } |