blob: f1681ec1f7d2a97b2116ef2d56f1dfec352ef4f9 [file] [log] [blame]
/*
* Copyright (C) 2015 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.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.util.Log;
import libcore.util.EmptyArray;
import java.security.InvalidAlgorithmParameterException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.SecretKey;
/**
* {@link KeyGeneratorSpi} backed by Android KeyStore.
*
* @hide
*/
public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
private static final String TAG = "AndroidKeyStoreKeyGeneratorSpi";
public static class AES extends AndroidKeyStoreKeyGeneratorSpi {
public AES() {
super(KeymasterDefs.KM_ALGORITHM_AES, 128);
}
@Override
protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
super.engineInit(params, random);
if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) {
throw new InvalidAlgorithmParameterException(
"Unsupported key size: " + mKeySizeBits
+ ". Supported: 128, 192, 256.");
}
}
}
public static class DESede extends AndroidKeyStoreKeyGeneratorSpi {
public DESede() {
super(KeymasterDefs.KM_ALGORITHM_3DES, 168);
}
}
protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
protected HmacBase(int keymasterDigest) {
super(KeymasterDefs.KM_ALGORITHM_HMAC,
keymasterDigest,
KeymasterUtils.getDigestOutputSizeBits(keymasterDigest));
}
}
public static class HmacSHA1 extends HmacBase {
public HmacSHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class HmacSHA224 extends HmacBase {
public HmacSHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class HmacSHA256 extends HmacBase {
public HmacSHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class HmacSHA384 extends HmacBase {
public HmacSHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class HmacSHA512 extends HmacBase {
public HmacSHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final KeyStore2 mKeyStore = KeyStore2.getInstance();
private final int mKeymasterAlgorithm;
private final int mKeymasterDigest;
private final int mDefaultKeySizeBits;
private KeyGenParameterSpec mSpec;
private SecureRandom mRng;
protected int mKeySizeBits;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
private int[] mKeymasterPaddings;
private int[] mKeymasterDigests;
protected AndroidKeyStoreKeyGeneratorSpi(
int keymasterAlgorithm,
int defaultKeySizeBits) {
this(keymasterAlgorithm, -1, defaultKeySizeBits);
}
protected AndroidKeyStoreKeyGeneratorSpi(
int keymasterAlgorithm,
int keymasterDigest,
int defaultKeySizeBits) {
mKeymasterAlgorithm = keymasterAlgorithm;
mKeymasterDigest = keymasterDigest;
mDefaultKeySizeBits = defaultKeySizeBits;
if (mDefaultKeySizeBits <= 0) {
throw new IllegalArgumentException("Default key size must be positive");
}
if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) {
throw new IllegalArgumentException(
"Digest algorithm must be specified for HMAC key");
}
}
@Override
protected void engineInit(SecureRandom random) {
throw new UnsupportedOperationException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
@Override
protected void engineInit(int keySize, SecureRandom random) {
throw new UnsupportedOperationException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
@Override
protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
if ((params == null) || (!(params instanceof KeyGenParameterSpec))) {
throw new InvalidAlgorithmParameterException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
KeyGenParameterSpec spec = (KeyGenParameterSpec) params;
if (spec.getKeystoreAlias() == null) {
throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
}
mRng = random;
mSpec = spec;
mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits;
if (mKeySizeBits <= 0) {
throw new InvalidAlgorithmParameterException(
"Key size must be positive: " + mKeySizeBits);
} else if ((mKeySizeBits % 8) != 0) {
throw new InvalidAlgorithmParameterException(
"Key size must be a multiple of 8: " + mKeySizeBits);
}
try {
mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
spec.getEncryptionPaddings());
if (spec.getSignaturePaddings().length > 0) {
throw new InvalidAlgorithmParameterException(
"Signature paddings not supported for symmetric key algorithms");
}
mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (spec.isRandomizedEncryptionRequired())) {
for (int keymasterBlockMode : mKeymasterBlockModes) {
if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
keymasterBlockMode)) {
throw new InvalidAlgorithmParameterException(
"Randomized encryption (IND-CPA) required but may be violated"
+ " by block mode: "
+ KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
+ ". See " + KeyGenParameterSpec.class.getName()
+ " documentation.");
}
}
}
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
if (mKeySizeBits != 168) {
throw new InvalidAlgorithmParameterException(
"3DES key size must be 168 bits.");
}
}
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
if (mKeySizeBits < 64 || mKeySizeBits > 512) {
throw new InvalidAlgorithmParameterException(
"HMAC key sizes must be within 64-512 bits, inclusive.");
}
// JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
// implies SHA-256 digest). Because keymaster HMAC key is authorized only for
// one digest, we don't let algorithm parameter spec override the digest implied
// by the key. If the spec specifies digests at all, it must specify only one
// digest, the only implied by key algorithm.
mKeymasterDigests = new int[] {mKeymasterDigest};
if (spec.isDigestsSpecified()) {
// Digest(s) explicitly specified in the spec. Check that the list
// consists of exactly one digest, the one implied by key algorithm.
int[] keymasterDigestsFromSpec =
KeyProperties.Digest.allToKeymaster(spec.getDigests());
if ((keymasterDigestsFromSpec.length != 1)
|| (keymasterDigestsFromSpec[0] != mKeymasterDigest)) {
throw new InvalidAlgorithmParameterException(
"Unsupported digests specification: "
+ Arrays.asList(spec.getDigests()) + ". Only "
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigest)
+ " supported for this HMAC key algorithm");
}
}
} else {
// Key algorithm does not imply a digest.
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<>(), spec);
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void resetAll() {
mSpec = null;
mRng = null;
mKeySizeBits = -1;
mKeymasterPurposes = null;
mKeymasterPaddings = null;
mKeymasterBlockModes = null;
}
@Override
protected SecretKey engineGenerateKey() {
KeyGenParameterSpec spec = mSpec;
if (spec == null) {
throw new IllegalStateException("Not initialized");
}
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits
));
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
));
ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PURPOSE, purpose
));
});
ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
if (blockMode == KeymasterDefs.KM_MODE_GCM
&& mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
.MIN_SUPPORTED_TAG_LENGTH_BITS
));
}
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
));
});
ArrayUtils.forEach(mKeymasterPaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, digest
));
});
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC
&& mKeymasterDigests.length != 0) {
int digestOutputSizeBits = KeymasterUtils.getDigestOutputSizeBits(mKeymasterDigests[0]);
if (digestOutputSizeBits == -1) {
throw new ProviderException(
"HMAC key authorized for unsupported digest: "
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigests[0]));
}
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits
));
}
KeyStore2ParameterUtils.addUserAuthArgs(params, spec);
if (spec.getKeyValidityStart() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()
));
}
if (spec.getKeyValidityForOriginationEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
spec.getKeyValidityForOriginationEnd()
));
}
if (spec.getKeyValidityForConsumptionEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
spec.getKeyValidityForConsumptionEnd()
));
}
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (!spec.isRandomizedEncryptionRequired())) {
// Permit caller-provided IV when encrypting with this key
params.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_CALLER_NONCE
));
}
if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
spec.getMaxUsageCount()
));
}
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
@SecurityLevel int securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT;
if (spec.isStrongBoxBacked()) {
securityLevel = SecurityLevel.STRONGBOX;
}
int flags = 0;
if (spec.isCriticalToDeviceEncryption()) {
flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
}
KeyDescriptor descriptor = new KeyDescriptor();
descriptor.alias = spec.getKeystoreAlias();
descriptor.nspace = spec.getNamespace();
descriptor.domain = descriptor.nspace == KeyProperties.NAMESPACE_APPLICATION
? Domain.APP
: Domain.SELINUX;
descriptor.blob = null;
KeyMetadata metadata = null;
KeyStoreSecurityLevel iSecurityLevel = null;
try {
iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
metadata = iSecurityLevel.generateKey(
descriptor,
null, /* Attestation key not applicable to symmetric keys. */
params,
flags,
additionalEntropy);
} catch (android.security.KeyStoreException e) {
switch (e.getErrorCode()) {
// TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec
// becomes available.
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generate key");
default:
throw new ProviderException("Keystore key generation failed", e);
}
}
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
try {
keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
mKeymasterAlgorithm, mKeymasterDigest);
} catch (IllegalArgumentException e) {
try {
mKeyStore.deleteKey(descriptor);
} catch (android.security.KeyStoreException kse) {
Log.e(TAG, "Failed to delete key after generating successfully but"
+ " failed to get the algorithm string.", kse);
}
throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
}
SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA,
iSecurityLevel);
return result;
}
}