blob: 2e85b304ec47206cfef7f62cfa76b794872935c8 [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.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
import android.os.RemoteException;
import android.security.GenerateRkpKey;
import android.security.GenerateRkpKeyException;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.AttestationUtils;
import android.security.keystore.DeviceIdAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Authorization;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Log;
import libcore.util.EmptyArray;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyPairGeneratorSpi;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
/**
* 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 {
private static final String TAG = "AndroidKeyStoreKeyPairGeneratorSpi";
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 KeyStore2 mKeyStore;
private KeyGenParameterSpec mSpec;
private String mEntryAlias;
private int mEntryNamespace;
private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm;
private int mKeymasterAlgorithm = -1;
private int mKeySizeBits;
private SecureRandom mRng;
private KeyDescriptor mAttestKeyDescriptor;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
private int[] mKeymasterEncryptionPaddings;
private int[] mKeymasterSignaturePaddings;
private int[] mKeymasterDigests;
private Long mRSAPublicExponent;
protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) {
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
private @EcCurve int keySize2EcCurve(int keySizeBits)
throws InvalidAlgorithmParameterException {
switch (keySizeBits) {
case 224:
return EcCurve.P_224;
case 256:
return EcCurve.P_256;
case 384:
return EcCurve.P_384;
case 521:
return EcCurve.P_521;
default:
throw new InvalidAlgorithmParameterException(
"Unsupported EC curve keysize: " + keySizeBits);
}
}
@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 {
keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm,
legacySpec);
spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm);
} 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();
mEntryNamespace = spec.getNamespace();
mSpec = spec;
mKeymasterAlgorithm = keymasterAlgorithm;
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).
KeyStore2ParameterUtils.addUserAuthArgs(new ArrayList<>(), mSpec);
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
mJcaKeyAlgorithm = jcaKeyAlgorithm;
mRng = random;
mKeyStore = KeyStore2.getInstance();
mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
checkAttestKeyPurpose(spec);
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void checkAttestKeyPurpose(KeyGenParameterSpec spec)
throws InvalidAlgorithmParameterException {
if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0
&& spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) {
throw new InvalidAlgorithmParameterException(
"PURPOSE_ATTEST_KEY may not be specified with any other purposes");
}
}
private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
throws InvalidAlgorithmParameterException {
if (spec.getAttestKeyAlias() != null) {
KeyDescriptor attestKeyDescriptor = new KeyDescriptor();
attestKeyDescriptor.domain = Domain.APP;
attestKeyDescriptor.alias = spec.getAttestKeyAlias();
try {
KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor);
checkAttestKeyChallenge(spec);
checkAttestKeyPurpose(attestKey.metadata.authorizations);
checkAttestKeySecurityLevel(spec, attestKey);
} catch (KeyStoreException e) {
throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e);
}
return attestKeyDescriptor;
}
return null;
}
private void checkAttestKeyChallenge(KeyGenParameterSpec spec)
throws InvalidAlgorithmParameterException {
if (spec.getAttestationChallenge() == null) {
throw new InvalidAlgorithmParameterException(
"AttestKey specified but no attestation challenge provided");
}
}
private void checkAttestKeyPurpose(Authorization[] keyAuths)
throws InvalidAlgorithmParameterException {
Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE
&& x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY;
if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) {
throw new InvalidAlgorithmParameterException(
("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
}
}
private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key)
throws InvalidAlgorithmParameterException {
boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX;
if (spec.isStrongBoxBacked() != attestKeyInStrongBox) {
if (attestKeyInStrongBox) {
throw new InvalidAlgorithmParameterException(
"Invalid security level: Cannot sign non-StrongBox key with "
+ "StrongBox attestKey");
} else {
throw new InvalidAlgorithmParameterException(
"Invalid security level: Cannot sign StrongBox key with "
+ "non-StrongBox attestKey");
}
}
}
private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm,
KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException {
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);
}
}
return keymasterAlgorithm;
}
private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec,
int keymasterAlgorithm) {
KeyGenParameterSpec.Builder specBuilder;
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());
specBuilder.setUserAuthenticationRequired(false);
return specBuilder.build();
}
private void resetAll() {
mEntryAlias = null;
mEntryNamespace = KeyProperties.NAMESPACE_APPLICATION;
mJcaKeyAlgorithm = null;
mKeymasterAlgorithm = -1;
mKeymasterPurposes = null;
mKeymasterBlockModes = null;
mKeymasterEncryptionPaddings = null;
mKeymasterSignaturePaddings = null;
mKeymasterDigests = null;
mKeySizeBits = 0;
mSpec = null;
mRSAPublicExponent = null;
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.signum() == -1)
|| (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported RSA public exponent: " + publicExponent
+ ". Maximum supported value: "
+ KeymasterArguments.UINT64_MAX_VALUE);
}
mRSAPublicExponent = publicExponent.longValue();
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() {
try {
return generateKeyPairHelper();
} catch (GenerateRkpKeyException e) {
try {
return generateKeyPairHelper();
} catch (GenerateRkpKeyException f) {
throw new ProviderException("Failed to provision new attestation keys.");
}
}
}
private KeyPair generateKeyPairHelper() throws GenerateRkpKeyException {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
final @SecurityLevel int securityLevel =
mSpec.isStrongBoxBacked()
? SecurityLevel.STRONGBOX
: SecurityLevel.TRUSTED_ENVIRONMENT;
final int flags =
mSpec.isCriticalToDeviceEncryption()
? IKeystoreSecurityLevel
.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING
: 0;
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
KeyDescriptor descriptor = new KeyDescriptor();
descriptor.alias = mEntryAlias;
descriptor.domain = mEntryNamespace == KeyProperties.NAMESPACE_APPLICATION
? Domain.APP
: Domain.SELINUX;
descriptor.nspace = mEntryNamespace;
descriptor.blob = null;
boolean success = false;
try {
KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor,
constructKeyGenerationArguments(), flags, additionalEntropy);
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
if (mSpec.getAttestationChallenge() != null) {
keyGen.notifyKeyGenerated(securityLevel);
}
} catch (RemoteException e) {
// This is not really an error state, and necessarily does not apply to non RKP
// systems or hybrid systems where RKP is not currently turned on.
Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
}
success = true;
return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (android.security.KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
case ResponseCode.OUT_OF_KEYS:
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
keyGen.notifyEmpty(securityLevel);
} catch (RemoteException f) {
throw new ProviderException("Failed to talk to RemoteProvisioner", f);
}
throw new GenerateRkpKeyException();
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(p);
}
throw p;
}
} catch (UnrecoverableKeyException | IllegalArgumentException
| DeviceIdAttestationException | InvalidAlgorithmParameterException e) {
throw new ProviderException(
"Failed to construct key object from newly generated key pair.", e);
} finally {
if (!success) {
try {
mKeyStore.deleteKey(descriptor);
} catch (KeyStoreException e) {
if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
Log.e(TAG, "Failed to delete newly generated key after "
+ "generation failed unexpectedly.", e);
}
}
}
}
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge
));
if (mSpec.isDevicePropertiesAttestationIncluded()) {
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
Build.BRAND.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
Build.MODEL.getBytes(StandardCharsets.UTF_8)
));
}
int[] idTypes = mSpec.getAttestationIds();
if (idTypes.length == 0) {
return;
}
final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
for (int idType : idTypes) {
idTypesSet.add(idType);
}
TelephonyManager telephonyService = null;
if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
|| idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
telephonyService =
(TelephonyManager) android.app.AppGlobals.getInitialApplication()
.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyService == null) {
throw new DeviceIdAttestationException("Unable to access telephony service");
}
}
for (final Integer idType : idTypesSet) {
switch (idType) {
case AttestationUtils.ID_TYPE_SERIAL:
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
Build.getSerial().getBytes(StandardCharsets.UTF_8)
));
break;
case AttestationUtils.ID_TYPE_IMEI: {
final String imei = telephonyService.getImei(0);
if (imei == null) {
throw new DeviceIdAttestationException("Unable to retrieve IMEI");
}
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
imei.getBytes(StandardCharsets.UTF_8)
));
break;
}
case AttestationUtils.ID_TYPE_MEID: {
final String meid = telephonyService.getMeid(0);
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
meid.getBytes(StandardCharsets.UTF_8)
));
break;
}
case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
params.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
break;
}
default:
throw new IllegalArgumentException("Unknown device ID type " + idType);
}
}
}
}
private Collection<KeyParameter> constructKeyGenerationArguments()
throws DeviceIdAttestationException, IllegalArgumentException,
InvalidAlgorithmParameterException {
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
));
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
params.add(KeyStore2ParameterUtils.makeEnum(
Tag.EC_CURVE, keySize2EcCurve(mKeySizeBits)
));
}
ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PURPOSE, purpose
));
});
ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
));
});
ArrayUtils.forEach(mKeymasterEncryptionPaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, digest
));
});
KeyStore2ParameterUtils.addUserAuthArgs(params, mSpec);
if (mSpec.getKeyValidityStart() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()
));
}
if (mSpec.getKeyValidityForOriginationEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
mSpec.getKeyValidityForOriginationEnd()
));
}
if (mSpec.getKeyValidityForConsumptionEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
mSpec.getKeyValidityForConsumptionEnd()
));
}
if (mSpec.getCertificateNotAfter() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_CERTIFICATE_NOT_AFTER,
mSpec.getCertificateNotAfter()
));
}
if (mSpec.getCertificateNotBefore() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_CERTIFICATE_NOT_BEFORE,
mSpec.getCertificateNotBefore()
));
}
if (mSpec.getCertificateSerialNumber() != null) {
params.add(KeyStore2ParameterUtils.makeBignum(
KeymasterDefs.KM_TAG_CERTIFICATE_SERIAL,
mSpec.getCertificateSerialNumber()
));
}
if (mSpec.getCertificateSubject() != null) {
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_CERTIFICATE_SUBJECT,
mSpec.getCertificateSubject().getEncoded()
));
}
if (mSpec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
mSpec.getMaxUsageCount()
));
}
addAlgorithmSpecificParameters(params);
if (mSpec.isUniqueIdIncluded()) {
params.add(KeyStore2ParameterUtils.makeBool(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID));
}
addAttestationParameters(params);
return params;
}
private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
switch (mKeymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_RSA:
params.add(KeyStore2ParameterUtils.makeLong(
KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent
));
break;
case KeymasterDefs.KM_ALGORITHM_EC:
break;
default:
throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
}
}
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;
}
}