Keystore 2.0: Remove Keystore 1.0 SPI with all remaining references

Bug: 171305684
Test: CtsKeystoreTestCases
Change-Id: I337515dadc9e45b909bff058d4e13371b4fa843c
This commit is contained in:
Janis Danisevskis
2021-03-05 10:23:09 -08:00
parent f11ccc456b
commit a6dcf091f5
57 changed files with 249 additions and 9825 deletions

View File

@@ -47,7 +47,6 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserAdded(@NonNull int userId) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
getService().onUserAdded(userId);
return 0;
@@ -68,7 +67,6 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserRemoved(int userId) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
getService().onUserRemoved(userId);
return 0;
@@ -91,7 +89,6 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserPasswordChanged(int userId, @Nullable byte[] password) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
getService().onUserPasswordChanged(userId, password);
return 0;
@@ -109,7 +106,6 @@ public class AndroidKeyStoreMaintenance {
* be cleared.
*/
public static int clearNamespace(@Domain int domain, long namespace) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
getService().clearNamespace(domain, namespace);
return 0;
@@ -144,7 +140,6 @@ public class AndroidKeyStoreMaintenance {
* Informs Keystore 2.0 that an off body event was detected.
*/
public static void onDeviceOffBody() {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return;
try {
getService().onDeviceOffBody();
} catch (Exception e) {

View File

@@ -48,7 +48,6 @@ public class Authorization {
* @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
*/
public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
getService().addAuthToken(authToken);
return 0;
@@ -80,7 +79,6 @@ public class Authorization {
*/
public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId,
@Nullable byte[] syntheticPassword) {
if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return 0;
try {
if (locked) {
getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null);

View File

@@ -208,78 +208,4 @@ public class Credentials {
pr.close();
}
}
/**
* Delete all types (private key, user certificate, CA certificate) for a
* particular {@code alias}. All three can exist for any given alias.
* Returns {@code true} if the alias no longer contains any types.
*/
public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) {
return deleteAllTypesForAlias(keystore, alias, KeyStore.UID_SELF);
}
/**
* Delete all types (private key, user certificate, CA certificate) for a
* particular {@code alias}. All three can exist for any given alias.
* Returns {@code true} if the alias no longer contains any types.
*/
public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias, int uid) {
/*
* Make sure every type is deleted. There can be all three types, so
* don't use a conditional here.
*/
return deleteUserKeyTypeForAlias(keystore, alias, uid)
& deleteCertificateTypesForAlias(keystore, alias, uid);
}
/**
* Delete certificate types (user certificate, CA certificate) for a
* particular {@code alias}. Both can exist for any given alias.
* Returns {@code true} if the alias no longer contains either type.
*/
public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) {
return deleteCertificateTypesForAlias(keystore, alias, KeyStore.UID_SELF);
}
/**
* Delete certificate types (user certificate, CA certificate) for a
* particular {@code alias}. Both can exist for any given alias.
* Returns {@code true} if the alias no longer contains either type.
*/
public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias, int uid) {
/*
* Make sure every certificate type is deleted. There can be two types,
* so don't use a conditional here.
*/
return keystore.delete(Credentials.USER_CERTIFICATE + alias, uid)
& keystore.delete(Credentials.CA_CERTIFICATE + alias, uid);
}
/**
* Delete user key for a particular {@code alias}.
* Returns {@code true} if the entry no longer exists.
*/
public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias) {
return deleteUserKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
}
/**
* Delete user key for a particular {@code alias}.
* Returns {@code true} if the entry no longer exists.
*/
public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
int ret = keystore.delete2(Credentials.USER_PRIVATE_KEY + alias, uid);
if (ret == KeyStore.KEY_NOT_FOUND) {
return keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
}
return ret == KeyStore.NO_ERROR;
}
/**
* Delete legacy prefixed entry for a particular {@code alias}
* Returns {@code true} if the entry no longer exists.
*/
public static boolean deleteLegacyKeyForAlias(KeyStore keystore, String alias, int uid) {
return keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
}
}

View File

@@ -37,7 +37,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Domain;
@@ -676,23 +675,13 @@ public final class KeyChain {
return null;
}
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
try {
return android.security.keystore2.AndroidKeyStoreProvider
.loadAndroidKeyStoreKeyPairFromKeystore(
KeyStore2.getInstance(),
getGrantDescriptor(keyId));
} catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) {
throw new KeyChainException(e);
}
} else {
try {
return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
} catch (RuntimeException | UnrecoverableKeyException
| KeyPermanentlyInvalidatedException e) {
throw new KeyChainException(e);
}
try {
return android.security.keystore2.AndroidKeyStoreProvider
.loadAndroidKeyStoreKeyPairFromKeystore(
KeyStore2.getInstance(),
getGrantDescriptor(keyId));
} catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) {
throw new KeyChainException(e);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@ package android.security;
import android.annotation.NonNull;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.vpnprofilestore.IVpnProfileStore;
import android.util.Log;
@@ -53,13 +52,8 @@ public class LegacyVpnProfileStore {
*/
public static boolean put(@NonNull String alias, @NonNull byte[] profile) {
try {
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
getService().put(alias, profile);
return true;
} else {
return KeyStore.getInstance().put(
alias, profile, KeyStore.UID_SELF, 0);
}
getService().put(alias, profile);
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to put vpn profile.", e);
return false;
@@ -77,11 +71,7 @@ public class LegacyVpnProfileStore {
*/
public static byte[] get(@NonNull String alias) {
try {
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
return getService().get(alias);
} else {
return KeyStore.getInstance().get(alias, true /* suppressKeyNotFoundWarning */);
}
return getService().get(alias);
} catch (ServiceSpecificException e) {
if (e.errorCode != PROFILE_NOT_FOUND) {
Log.e(TAG, "Failed to get vpn profile.", e);
@@ -100,12 +90,8 @@ public class LegacyVpnProfileStore {
*/
public static boolean remove(@NonNull String alias) {
try {
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
getService().remove(alias);
return true;
} else {
return KeyStore.getInstance().delete(alias);
}
getService().remove(alias);
return true;
} catch (ServiceSpecificException e) {
if (e.errorCode != PROFILE_NOT_FOUND) {
Log.e(TAG, "Failed to remove vpn profile.", e);
@@ -124,16 +110,11 @@ public class LegacyVpnProfileStore {
*/
public static @NonNull String[] list(@NonNull String prefix) {
try {
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
final String[] aliases = getService().list(prefix);
for (int i = 0; i < aliases.length; ++i) {
aliases[i] = aliases[i].substring(prefix.length());
}
return aliases;
} else {
final String[] result = KeyStore.getInstance().list(prefix);
return result != null ? result : new String[0];
final String[] aliases = getService().list(prefix);
for (int i = 0; i < aliases.length; ++i) {
aliases[i] = aliases[i].substring(prefix.length());
}
return aliases;
} catch (Exception e) {
Log.e(TAG, "Failed to list vpn profiles.", e);
}

View File

@@ -1,298 +0,0 @@
/*
* Copyright (C) 2018 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.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.CipherSpi;
import javax.crypto.spec.IvParameterSpec;
/**
* Base class for Android Keystore 3DES {@link CipherSpi} implementations.
*
* @hide
*/
public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
private static final int BLOCK_SIZE_BYTES = 8;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
/** Whether this transformation requires an IV. */
private final boolean mIvRequired;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
AndroidKeyStore3DESCipherSpi(
int keymasterBlockMode,
int keymasterPadding,
boolean ivRequired) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
mIvRequired = ivRequired;
}
abstract static class ECB extends AndroidKeyStore3DESCipherSpi {
protected ECB(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
}
public static class NoPadding extends ECB {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends ECB {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
abstract static class CBC extends AndroidKeyStore3DESCipherSpi {
protected CBC(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
}
public static class NoPadding extends CBC {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends CBC {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
@Override
protected void initKey(int i, Key key) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException(
"Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
KeyProperties.KEY_ALGORITHM_3DES + " supported");
}
setKey((AndroidKeyStoreSecretKey) key);
}
@Override
protected int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected int engineGetOutputSize(int inputLen) {
return inputLen + 3 * BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
@Override
protected AlgorithmParameters engineGetParameters() {
if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("DESede");
params.init(new IvParameterSpec(mIv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain 3DES AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize 3DES AlgorithmParameters with an IV",
e);
}
}
return null;
}
@Override
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
if (!mIvRequired) {
return;
}
// IV is used
if (!isEncrypting()) {
throw new InvalidKeyException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
}
@Override
protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException(
"IvParameterSpec must be provided when decrypting");
}
return;
}
if (!(params instanceof IvParameterSpec)) {
throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
}
mIv = ((IvParameterSpec) params).getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
}
}
@Override
protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
return;
}
if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: DESede");
}
IvParameterSpec ivSpec;
try {
ivSpec = params.getParameterSpec(IvParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ", but not found in parameters: " + params, e);
}
mIv = null;
return;
}
mIv = ivSpec.getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
}
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
// IV will need to be generated
return BLOCK_SIZE_BYTES;
}
return 0;
}
@Override
protected int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
// IV is being reused for encryption: this violates security best practices.
throw new IllegalStateException(
"IV has already been used. Reusing IV in encryption mode violates security best"
+ " practices.");
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_3DES);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
@Override
protected void loadAlgorithmSpecificParametersFromBeginResult(
KeymasterArguments keymasterArgs) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
if ((returnedIv != null) && (returnedIv.length == 0)) {
returnedIv = null;
}
if (mIvRequired) {
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
throw new ProviderException("IV in use differs from provided IV");
}
} else {
if (returnedIv != null) {
throw new ProviderException(
"IV in use despite IV not being used by this transformation");
}
}
}
@Override
protected final void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
}

View File

@@ -1,449 +0,0 @@
/*
* 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.keystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.CipherSpi;
import javax.crypto.spec.GCMParameterSpec;
/**
* Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
*
* @hide
*/
abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
private static final int DEFAULT_TAG_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 12;
private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
GCM(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
}
@Override
protected final void resetAll() {
mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
if (!isEncrypting()) {
throw new InvalidKeyException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException(
"GCMParameterSpec must be provided when decrypting");
}
return;
}
if (!(params instanceof GCMParameterSpec)) {
throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
}
GCMParameterSpec spec = (GCMParameterSpec) params;
byte[] iv = spec.getIV();
if (iv == null) {
throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
} else if (iv.length != IV_LENGTH_BYTES) {
throw new InvalidAlgorithmParameterException("Unsupported IV length: "
+ iv.length + " bytes. Only " + IV_LENGTH_BYTES
+ " bytes long IV supported");
}
int tagLengthBits = spec.getTLen();
if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
|| (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
|| ((tagLengthBits % 8) != 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported tag length: " + tagLengthBits + " bits"
+ ". Supported lengths: 96, 104, 112, 120, 128");
}
setIv(iv);
mTagLengthBits = tagLengthBits;
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
}
return;
}
if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: GCM");
}
GCMParameterSpec spec;
try {
spec = params.getParameterSpec(GCMParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV and tag length required when"
+ " decrypting, but not found in parameters: " + params, e);
}
setIv(null);
return;
}
initAlgorithmSpecificParameters(spec);
}
@Nullable
@Override
protected final AlgorithmParameters engineGetParameters() {
byte[] iv = getIv();
if ((iv != null) && (iv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
params.init(new GCMParameterSpec(mTagLengthBits, iv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain GCM AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize GCM AlgorithmParameters", e);
}
}
return null;
}
@NonNull
@Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
keyStore, operationToken), 0);
if (isEncrypting()) {
return streamer;
} else {
// When decrypting, to avoid leaking unauthenticated plaintext, do not return any
// plaintext before ciphertext is authenticated by KeyStore.finish.
return new BufferAllOutputUntilDoFinalStreamer(streamer);
}
}
@NonNull
@Override
protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new AdditionalAuthenticationDataStream(keyStore, operationToken), 0);
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
if ((getIv() == null) && (isEncrypting())) {
// IV will need to be generated
return IV_LENGTH_BYTES;
}
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
}
protected final int getTagLengthBits() {
return mTagLengthBits;
}
public static final class NoPadding extends GCM {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
@Override
protected final int engineGetOutputSize(int inputLen) {
int tagLengthBytes = (getTagLengthBits() + 7) / 8;
long result;
if (isEncrypting()) {
result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ tagLengthBytes;
} else {
result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
- tagLengthBytes;
}
if (result < 0) {
return 0;
} else if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) result;
}
}
}
private static final int BLOCK_SIZE_BYTES = 16;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
AndroidKeyStoreAuthenticatedAESCipherSpi(
int keymasterBlockMode,
int keymasterPadding) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
}
@Override
protected void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
@Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException(
"Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
KeyProperties.KEY_ALGORITHM_AES + " supported");
}
setKey((AndroidKeyStoreSecretKey) key);
}
@Override
protected void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
if ((isEncrypting()) && (mIvHasBeenUsed)) {
// IV is being reused for encryption: this violates security best practices.
throw new IllegalStateException(
"IV has already been used. Reusing IV in encryption mode violates security best"
+ " practices.");
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
if (mIv != null) {
keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
if ((returnedIv != null) && (returnedIv.length == 0)) {
returnedIv = null;
}
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
throw new ProviderException("IV in use differs from provided IV");
}
}
@Override
protected final int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
protected void setIv(byte[] iv) {
mIv = iv;
}
protected byte[] getIv() {
return mIv;
}
/**
* {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
* which it returns all output in one go, provided {@code doFinal} succeeds.
*/
private static class BufferAllOutputUntilDoFinalStreamer
implements KeyStoreCryptoOperationStreamer {
private final KeyStoreCryptoOperationStreamer mDelegate;
private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
private long mProducedOutputSizeBytes;
private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
mDelegate = delegate;
}
@Override
public byte[] update(byte[] input, int inputOffset, int inputLength)
throws KeyStoreException {
byte[] output = mDelegate.update(input, inputOffset, inputLength);
if (output != null) {
try {
mBufferedOutput.write(output);
} catch (IOException e) {
throw new ProviderException("Failed to buffer output", e);
}
}
return EmptyArray.BYTE;
}
@Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
additionalEntropy);
if (output != null) {
try {
mBufferedOutput.write(output);
} catch (IOException e) {
throw new ProviderException("Failed to buffer output", e);
}
}
byte[] result = mBufferedOutput.toByteArray();
mBufferedOutput.reset();
mProducedOutputSizeBytes += result.length;
return result;
}
@Override
public long getConsumedInputSizeBytes() {
return mDelegate.getConsumedInputSizeBytes();
}
@Override
public long getProducedOutputSizeBytes() {
return mProducedOutputSizeBytes;
}
}
/**
* Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
* sends AAD into the KeyStore.
*/
private static class AdditionalAuthenticationDataStream implements Stream {
private final KeyStore mKeyStore;
private final IBinder mOperationToken;
private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
mKeyStore = keyStore;
mOperationToken = operationToken;
}
@Override
public OperationResult update(byte[] input) {
KeymasterArguments keymasterArgs = new KeymasterArguments();
keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
// KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
// field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
// has been consumed if the method succeeds.
OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
if (result.resultCode == KeyStore.NO_ERROR) {
result = new OperationResult(
result.resultCode,
result.token,
result.operationHandle,
input.length, // inputConsumed
result.output,
result.outParams);
}
return result;
}
@Override
public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) {
if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
throw new ProviderException("AAD stream does not support additional entropy");
}
return new OperationResult(
KeyStore.NO_ERROR,
mOperationToken,
0, // operation handle -- nobody cares about this being returned from finish
0, // inputConsumed
EmptyArray.BYTE, // output
new KeymasterArguments() // additional params returned by finish
);
}
}
}

View File

@@ -1,279 +0,0 @@
/*
* 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.keystore;
import java.security.Provider;
/**
* {@link Provider} of JCA crypto operations operating on Android KeyStore keys.
*
* <p>This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue
* that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android
* KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto
* operations for Android KeyStore keys unless Android KeyStore's own implementations are installed
* as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to
* offer crypto operations operating on Android KeyStore keys and to be installed at higher priority
* than the Bouncy Castle provider.
*
* <p>Once Bouncy Castle provider is fixed, this provider can be merged into the
* {@code AndroidKeyStoreProvider}.
*
* @hide
*/
public class AndroidKeyStoreBCWorkaroundProvider extends Provider {
// IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
// classes when this provider is instantiated and installed early on during each app's
// initialization process.
private static final String PACKAGE_NAME = "android.security.keystore";
private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStoreSecretKey";
private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStorePrivateKey";
private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStorePublicKey";
private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede";
/** @hide */
public AndroidKeyStoreBCWorkaroundProvider() {
this("AndroidKeyStoreBCWorkaround");
}
/** @hide **/
public AndroidKeyStoreBCWorkaroundProvider(String providerName) {
super(providerName,
1.0,
"Android KeyStore security provider to work around Bouncy Castle");
// --------------------- javax.crypto.Mac
putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1");
put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1");
put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1");
put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1");
putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224");
put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA224");
put("Alg.Alias.Mac.HMAC-SHA224", "HmacSHA224");
put("Alg.Alias.Mac.HMAC/SHA224", "HmacSHA224");
putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256");
put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256");
put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256");
put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256");
putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384");
put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384");
put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384");
put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384");
putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512");
put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512");
put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512");
put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
// --------------------- javax.crypto.Cipher
putSymmetricCipherImpl("AES/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding");
putSymmetricCipherImpl("AES/CBC/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding");
putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding");
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
if ("true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY))) {
putSymmetricCipherImpl("DESede/CBC/NoPadding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding");
putSymmetricCipherImpl("DESede/CBC/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding");
putSymmetricCipherImpl("DESede/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("DESede/ECB/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding");
}
putSymmetricCipherImpl("AES/GCM/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");
putAsymmetricCipherImpl("RSA/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding");
put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPPadding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-384AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
// --------------------- java.security.Signature
putSignatureImpl("NONEwithRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$NONEWithPKCS1Padding");
putSignatureImpl("MD5withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$MD5WithPKCS1Padding");
put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5withRSA");
put("Alg.Alias.Signature.MD5/RSA", "MD5withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5withRSA");
put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5withRSA");
putSignatureImpl("SHA1withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPKCS1Padding");
put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1withRSA");
put("Alg.Alias.Signature.SHA1/RSA", "SHA1withRSA");
put("Alg.Alias.Signature.SHA-1/RSA", "SHA1withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1withRSA");
putSignatureImpl("SHA224withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPKCS1Padding");
put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1",
"SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11",
"SHA224withRSA");
putSignatureImpl("SHA256withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPKCS1Padding");
put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1",
"SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11",
"SHA256withRSA");
putSignatureImpl("SHA384withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPKCS1Padding");
put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1",
"SHA384withRSA");
putSignatureImpl("SHA512withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPKCS1Padding");
put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1",
"SHA512withRSA");
putSignatureImpl("SHA1withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding");
putSignatureImpl("SHA224withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPSSPadding");
putSignatureImpl("SHA256withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPSSPadding");
putSignatureImpl("SHA384withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPSSPadding");
putSignatureImpl("SHA512withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPSSPadding");
putSignatureImpl("NONEwithECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE");
putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1");
put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA");
put("Alg.Alias.Signature.ECDSAwithSHA1", "SHA1withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1)
put("Alg.Alias.Signature.1.2.840.10045.4.1", "SHA1withECDSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "SHA1withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
putSignatureImpl("SHA224withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA224");
// ecdsa-with-SHA224(1)
put("Alg.Alias.Signature.1.2.840.10045.4.3.1", "SHA224withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.10045.2.1", "SHA224withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
putSignatureImpl("SHA256withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA256");
// ecdsa-with-SHA256(2)
put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA");
putSignatureImpl("SHA384withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA384");
// ecdsa-with-SHA384(3)
put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA");
putSignatureImpl("SHA512withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA512");
// ecdsa-with-SHA512(4)
put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA");
}
private void putMacImpl(String algorithm, String implClass) {
put("Mac." + algorithm, implClass);
put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
}
private void putSymmetricCipherImpl(String transformation, String implClass) {
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
}
private void putAsymmetricCipherImpl(String transformation, String implClass) {
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses",
KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}
private void putSignatureImpl(String algorithm, String implClass) {
put("Signature." + algorithm, implClass);
put("Signature." + algorithm + " SupportedKeyClasses",
KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}
public static String[] getSupportedEcdsaSignatureDigests() {
return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
}
public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() {
return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
}
}

View File

@@ -1,920 +0,0 @@
/*
* 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.keystore;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import libcore.util.EmptyArray;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
/**
* Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private final KeyStore mKeyStore;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
private boolean mEncrypting;
private int mKeymasterPurposeOverride = -1;
private AndroidKeyStoreKey mKey;
private SecureRandom mRng;
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error
* conditions in between.
*/
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationStreamer mMainDataStreamer;
private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
private boolean mAdditionalAuthenticationDataStreamerClosed;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
* a method that does not throw checked exception. This exception will be thrown from
* {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
* {@code engineDoFinal} start ignoring input data.
*/
private Exception mCachedException;
AndroidKeyStoreCipherSpiBase() {
mKeyStore = KeyStore.getInstance();
}
@Override
protected final void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
ensureKeystoreOperationInitialized();
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
mEncrypting = true;
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
mEncrypting = false;
break;
default:
throw new InvalidParameterException("Unsupported opmode: " + opmode);
}
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
}
mRng = random;
}
/**
* Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
* cipher instance.
*
* <p>Subclasses storing additional state should override this method, reset the additional
* state, and then chain to superclass.
*/
@CallSuper
protected void resetAll() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mEncrypting = false;
mKeymasterPurposeOverride = -1;
mKey = null;
mRng = null;
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
/**
* Resets this cipher while preserving the initialized state. This must be equivalent to
* rolling back the cipher's state to just after the most recent {@code engineInit} completed
* successfully.
*
* <p>Subclasses storing additional post-init state should override this method, reset the
* additional state, and then chain to superclass.
*/
@CallSuper
protected void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForBegin());
int purpose;
if (mKeymasterPurposeOverride != -1) {
purpose = mKeymasterPurposeOverride;
} else {
purpose = mEncrypting
? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
}
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
purpose,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
additionalEntropy,
mKey.getUid());
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
if (e instanceof InvalidKeyException) {
throw (InvalidKeyException) e;
} else if (e instanceof InvalidAlgorithmParameterException) {
throw (InvalidAlgorithmParameterException) e;
} else {
throw new ProviderException("Unexpected exception type", e);
}
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams);
mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamer =
createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
mAdditionalAuthenticationDataStreamerClosed = false;
}
/**
* Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives
* the corresponding ciphertext/plaintext from the KeyStore.
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
keyStore, operationToken), 0);
}
/**
* Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.
*
* <p>This implementation returns {@code null}.
*
* @return stream or {@code null} if AAD is not supported by this cipher.
*/
@Nullable
protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
@SuppressWarnings("unused") KeyStore keyStore,
@SuppressWarnings("unused") IBinder operationToken) {
return null;
}
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return null;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
}
if (inputLen == 0) {
return null;
}
byte[] output;
try {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
private void flushAAD() throws KeyStoreException {
if ((mAdditionalAuthenticationDataStreamer != null)
&& (!mAdditionalAuthenticationDataStreamerClosed)) {
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null, // no signature
null // no additional entropy needed flushing AAD
);
} finally {
mAdditionalAuthenticationDataStreamerClosed = true;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException(
"AAD update unexpectedly returned data: " + output.length + " bytes");
}
}
}
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineUpdate(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineUpdate(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return;
}
if (mAdditionalAuthenticationDataStreamerClosed) {
throw new IllegalStateException(
"AAD can only be provided before Cipher.update is invoked");
}
if (mAdditionalAuthenticationDataStreamer == null) {
throw new IllegalStateException("This cipher does not support AAD");
}
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException("AAD update unexpectedly produced output: "
+ output.length + " bytes");
}
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
if (src == null) {
throw new IllegalArgumentException("src == null");
}
if (!src.hasRemaining()) {
return;
}
byte[] input;
int inputOffset;
int inputLen;
if (src.hasArray()) {
input = src.array();
inputOffset = src.arrayOffset() + src.position();
inputLen = src.remaining();
src.position(src.limit());
} else {
input = new byte[src.remaining()];
inputOffset = 0;
inputLen = input.length;
src.get(input);
}
engineUpdateAAD(input, inputOffset, inputLen);
}
@Override
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
byte[] output;
try {
flushAAD();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForFinish());
output = mMainDataStreamer.doFinal(
input, inputOffset, inputLen,
null, // no signature involved
additionalEntropy);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw (BadPaddingException) new BadPaddingException().initCause(e);
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw (AEADBadTagException) new AEADBadTagException().initCause(e);
default:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
resetWhilePreservingInitState();
return output;
}
@Override
protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineDoFinal(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineDoFinal(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (!isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (key == null) {
throw new NullPointerException("key == null");
}
byte[] encoded = null;
if (key instanceof SecretKey) {
if ("RAW".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
SecretKeySpec spec =
(SecretKeySpec) keyFactory.getKeySpec(
(SecretKey) key, SecretKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PrivateKey) {
if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
PKCS8EncodedKeySpec spec =
keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PublicKey) {
if ("X.509".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
X509EncodedKeySpec spec =
keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else {
throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
}
if (encoded == null) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material");
}
try {
return engineDoFinal(encoded, 0, encoded.length);
} catch (BadPaddingException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (wrappedKey == null) {
throw new NullPointerException("wrappedKey == null");
}
byte[] encoded;
try {
encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new InvalidKeyException("Failed to unwrap key", e);
}
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
{
return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
// break;
}
case Cipher.PRIVATE_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create private key from its PKCS#8 encoded form", e);
}
// break;
}
case Cipher.PUBLIC_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create public key from its X.509 encoded form", e);
}
// break;
}
default:
throw new InvalidParameterException(
"Unsupported wrappedKeyType: " + wrappedKeyType);
}
}
@Override
protected final void engineSetMode(String mode) throws NoSuchAlgorithmException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify block mode.
throw new UnsupportedOperationException();
}
@Override
protected final void engineSetPadding(String arg0) throws NoSuchPaddingException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify padding mode.
throw new UnsupportedOperationException();
}
@Override
protected final int engineGetKeySize(Key key) throws InvalidKeyException {
throw new UnsupportedOperationException();
}
@CallSuper
@Override
public void finalize() throws Throwable {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
} finally {
super.finalize();
}
}
@Override
public final long getOperationHandle() {
return mOperationHandle;
}
protected final void setKey(@NonNull AndroidKeyStoreKey key) {
mKey = key;
}
/**
* Overrides the default purpose/type of the crypto operation.
*/
protected final void setKeymasterPurposeOverride(int keymasterPurpose) {
mKeymasterPurposeOverride = keymasterPurpose;
}
protected final int getKeymasterPurposeOverride() {
return mKeymasterPurposeOverride;
}
/**
* Returns {@code true} if this cipher is initialized for encryption, {@code false} if this
* cipher is initialized for decryption.
*/
protected final boolean isEncrypting() {
return mEncrypting;
}
@NonNull
protected final KeyStore getKeyStore() {
return mKeyStore;
}
protected final long getConsumedInputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getConsumedInputSizeBytes();
}
protected final long getProducedOutputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getProducedOutputSizeBytes();
}
static String opmodeToString(int opmode) {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
return "ENCRYPT_MODE";
case Cipher.DECRYPT_MODE:
return "DECRYPT_MODE";
case Cipher.WRAP_MODE:
return "WRAP_MODE";
case Cipher.UNWRAP_MODE:
return "UNWRAP_MODE";
default:
return String.valueOf(opmode);
}
}
// The methods below need to be implemented by subclasses.
/**
* Initializes this cipher with the provided key.
*
* @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the
* specified {@code opmode}.
*
* @see #setKey(AndroidKeyStoreKey)
*/
protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
/**
* Returns algorithm-specific parameters used by this cipher or {@code null} if no
* algorithm-specific parameters are used.
*/
@Nullable
@Override
protected abstract AlgorithmParameters engineGetParameters();
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional
* initialization parameters were provided.
*
* @throws InvalidKeyException if this cipher cannot be configured based purely on the provided
* key and needs additional parameters to be provided to {@code Cipher.init}.
*/
protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException;
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code begin} operation. This amount of entropy is typically what's consumed to generate
* random parameters, such as IV.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon
* entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
* explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC
* encryption with an explicitly provided IV the return value should be {@code 0}, whereas for
* the case where IV is generated by the KeyStore's {@code begin} operation it should be
* {@code 16}.
*/
protected abstract int getAdditionalEntropyAmountForBegin();
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code finish} operation. This amount of entropy is typically what's consumed by encryption
* padding scheme.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon
* entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all
* explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with
* OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding
* the return value should be the size of the padding string or could be raised (for simplicity)
* to the size of the modulus.
*/
protected abstract int getAdditionalEntropyAmountForFinish();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs);
/**
* Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
* {@code begin} operation.
*
* <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such
* parameters, if not provided, must be generated by KeyStore and returned to the user of
* {@code Cipher} and potentially reused after {@code doFinal}.
*
* @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
* operation.
*/
protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs);
}

View File

@@ -1,202 +0,0 @@
/*
* 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.keystore;
import android.annotation.NonNull;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.SignatureSpi;
/**
* Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures.
*
* @hide
*/
abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
public final static class NONE extends AndroidKeyStoreECDSASignatureSpi {
public NONE() {
super(KeymasterDefs.KM_DIGEST_NONE);
}
@Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore,
IBinder operationToken) {
return new TruncateToFieldSizeMessageStreamer(
super.createMainDataStreamer(keyStore, operationToken),
getGroupSizeBits());
}
/**
* Streamer which buffers all input, then truncates it to field size, and then sends it into
* KeyStore via the provided delegate streamer.
*/
private static class TruncateToFieldSizeMessageStreamer
implements KeyStoreCryptoOperationStreamer {
private final KeyStoreCryptoOperationStreamer mDelegate;
private final int mGroupSizeBits;
private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
private long mConsumedInputSizeBytes;
private TruncateToFieldSizeMessageStreamer(
KeyStoreCryptoOperationStreamer delegate,
int groupSizeBits) {
mDelegate = delegate;
mGroupSizeBits = groupSizeBits;
}
@Override
public byte[] update(byte[] input, int inputOffset, int inputLength)
throws KeyStoreException {
if (inputLength > 0) {
mInputBuffer.write(input, inputOffset, inputLength);
mConsumedInputSizeBytes += inputLength;
}
return EmptyArray.BYTE;
}
@Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
byte[] additionalEntropy) throws KeyStoreException {
if (inputLength > 0) {
mConsumedInputSizeBytes += inputLength;
mInputBuffer.write(input, inputOffset, inputLength);
}
byte[] bufferedInput = mInputBuffer.toByteArray();
mInputBuffer.reset();
// Truncate input at field size (bytes)
return mDelegate.doFinal(bufferedInput,
0,
Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
signature, additionalEntropy);
}
@Override
public long getConsumedInputSizeBytes() {
return mConsumedInputSizeBytes;
}
@Override
public long getProducedOutputSizeBytes() {
return mDelegate.getProducedOutputSizeBytes();
}
}
}
public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
public SHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
public SHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
public SHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
public SHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
public SHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterDigest;
private int mGroupSizeBits = -1;
AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest;
}
@Override
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported");
}
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = getKeyStore().getKeyCharacteristics(
key.getAlias(), null, null, key.getUid(), keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw getKeyStore().getInvalidKeyException(key.getAlias(), key.getUid(), errorCode);
}
long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
if (keySizeBits == -1) {
throw new InvalidKeyException("Size of key not known");
} else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
}
mGroupSizeBits = (int) keySizeBits;
super.initKey(key);
}
@Override
protected final void resetAll() {
mGroupSizeBits = -1;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
return (mGroupSizeBits + 7) / 8;
}
protected final int getGroupSizeBits() {
if (mGroupSizeBits == -1) {
throw new IllegalStateException("Not initialized");
}
return mGroupSizeBits;
}
}

View File

@@ -1,40 +0,0 @@
/*
* 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.keystore;
import java.security.PrivateKey;
import java.security.interfaces.ECKey;
import java.security.spec.ECParameterSpec;
/**
* EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey {
private final ECParameterSpec mParams;
public AndroidKeyStoreECPrivateKey(String alias, int uid, ECParameterSpec params) {
super(alias, uid, KeyProperties.KEY_ALGORITHM_EC);
mParams = params;
}
@Override
public ECParameterSpec getParams() {
return mParams;
}
}

View File

@@ -1,57 +0,0 @@
/*
* 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.keystore;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
/**
* {@link ECPublicKey} backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey implements ECPublicKey {
private final ECParameterSpec mParams;
private final ECPoint mW;
public AndroidKeyStoreECPublicKey(String alias, int uid, byte[] x509EncodedForm, ECParameterSpec params,
ECPoint w) {
super(alias, uid, KeyProperties.KEY_ALGORITHM_EC, x509EncodedForm);
mParams = params;
mW = w;
}
public AndroidKeyStoreECPublicKey(String alias, int uid, ECPublicKey info) {
this(alias, uid, info.getEncoded(), info.getParams(), info.getW());
if (!"X.509".equalsIgnoreCase(info.getFormat())) {
throw new IllegalArgumentException(
"Unsupported key export format: " + info.getFormat());
}
}
@Override
public ECParameterSpec getParams() {
return mParams;
}
@Override
public ECPoint getW() {
return mW;
}
}

View File

@@ -1,265 +0,0 @@
/*
* 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.keystore;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.MacSpi;
/**
* {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
*
* @hide
*/
public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
public static class HmacSHA1 extends AndroidKeyStoreHmacSpi {
public HmacSHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class HmacSHA224 extends AndroidKeyStoreHmacSpi {
public HmacSHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class HmacSHA256 extends AndroidKeyStoreHmacSpi {
public HmacSHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class HmacSHA384 extends AndroidKeyStoreHmacSpi {
public HmacSHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class HmacSHA512 extends AndroidKeyStoreHmacSpi {
public HmacSHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final KeyStore mKeyStore = KeyStore.getInstance();
private final int mKeymasterDigest;
private final int mMacSizeBits;
// Fields below are populated by engineInit and should be preserved after engineDoFinal.
private AndroidKeyStoreSecretKey mKey;
// Fields below are reset when engineDoFinal succeeds.
private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
private IBinder mOperationToken;
private long mOperationHandle;
protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest;
mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
}
@Override
protected int engineGetMacLength() {
return (mMacSizeBits + 7) / 8;
}
@Override
protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(key, params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Only Android KeyStore secret keys supported. Key: " + key);
}
mKey = (AndroidKeyStoreSecretKey) key;
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unsupported algorithm parameters: " + params);
}
}
private void resetAll() {
mKey = null;
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mOperationToken = null;
mOperationHandle = 0;
mChunkedStreamer = null;
}
private void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
mOperationToken = null;
mOperationHandle = 0;
mChunkedStreamer = null;
}
@Override
protected void engineReset() {
resetWhilePreservingInitState();
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
if (mChunkedStreamer != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterArgs = new KeymasterArguments();
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits);
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
KeymasterDefs.KM_PURPOSE_SIGN,
true,
keymasterArgs,
null, // no additional entropy needed for HMAC because it's deterministic
mKey.getUid());
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
throw e;
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, mOperationToken));
}
@Override
protected void engineUpdate(byte input) {
engineUpdate(new byte[] {input}, 0, 1);
}
@Override
protected void engineUpdate(byte[] input, int offset, int len) {
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] output;
try {
output = mChunkedStreamer.update(input, offset, len);
} catch (KeyStoreException e) {
throw new ProviderException("Keystore operation failed", e);
}
if ((output != null) && (output.length != 0)) {
throw new ProviderException("Update operation unexpectedly produced output");
}
}
@Override
protected byte[] engineDoFinal() {
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] result;
try {
result = mChunkedStreamer.doFinal(
null, 0, 0,
null, // no signature provided -- this invocation will generate one
null // no additional entropy needed -- HMAC is deterministic
);
} catch (KeyStoreException e) {
throw new ProviderException("Keystore operation failed", e);
}
resetWhilePreservingInitState();
return result;
}
@Override
public void finalize() throws Throwable {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
} finally {
super.finalize();
}
}
@Override
public long getOperationHandle() {
return mOperationHandle;
}
}

View File

@@ -1,103 +0,0 @@
/*
* 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.keystore;
import java.security.Key;
/**
* {@link Key} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreKey implements Key {
private final String mAlias;
private final int mUid;
private final String mAlgorithm;
public AndroidKeyStoreKey(String alias, int uid, String algorithm) {
mAlias = alias;
mUid = uid;
mAlgorithm = algorithm;
}
String getAlias() {
return mAlias;
}
int getUid() {
return mUid;
}
@Override
public String getAlgorithm() {
return mAlgorithm;
}
@Override
public String getFormat() {
// This key does not export its key material
return null;
}
@Override
public byte[] getEncoded() {
// This key does not export its key material
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode());
result = prime * result + mUid;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj;
if (mAlgorithm == null) {
if (other.mAlgorithm != null) {
return false;
}
} else if (!mAlgorithm.equals(other.mAlgorithm)) {
return false;
}
if (mAlias == null) {
if (other.mAlias != null) {
return false;
}
} else if (!mAlias.equals(other.mAlias)) {
return false;
}
if (mUid != other.mUid) {
return false;
}
return true;
}
}

View File

@@ -1,151 +0,0 @@
/*
* 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.keystore;
import android.security.Credentials;
import android.security.KeyStore;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* {@link KeyFactorySpi} backed by Android KeyStore.
*
* @hide
*/
public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
private final KeyStore mKeyStore = KeyStore.getInstance();
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
throws InvalidKeySpecException {
if (key == null) {
throw new InvalidKeySpecException("key == null");
} else if ((!(key instanceof AndroidKeyStorePrivateKey))
&& (!(key instanceof AndroidKeyStorePublicKey))) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". This KeyFactory supports only Android Keystore asymmetric keys");
}
// key is an Android Keystore private or public key
if (keySpecClass == null) {
throw new InvalidKeySpecException("keySpecClass == null");
} else if (KeyInfo.class.equals(keySpecClass)) {
if (!(key instanceof AndroidKeyStorePrivateKey)) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". KeyInfo can be obtained only for Android Keystore private keys");
}
AndroidKeyStorePrivateKey keystorePrivateKey = (AndroidKeyStorePrivateKey) key;
String keyAliasInKeystore = keystorePrivateKey.getAlias();
String entryAlias;
if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
} else {
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
}
@SuppressWarnings("unchecked")
T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(
mKeyStore, entryAlias, keyAliasInKeystore, keystorePrivateKey.getUid());
return result;
} else if (X509EncodedKeySpec.class.equals(keySpecClass)) {
if (!(key instanceof AndroidKeyStorePublicKey)) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". X509EncodedKeySpec can be obtained only for Android Keystore public"
+ " keys");
}
@SuppressWarnings("unchecked")
T result = (T) new X509EncodedKeySpec(((AndroidKeyStorePublicKey) key).getEncoded());
return result;
} else if (PKCS8EncodedKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStorePrivateKey) {
throw new InvalidKeySpecException(
"Key material export of Android Keystore private keys is not supported");
} else {
throw new InvalidKeySpecException(
"Cannot export key material of public key in PKCS#8 format."
+ " Only X.509 format (X509EncodedKeySpec) supported for public keys.");
}
} else if (RSAPublicKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStoreRSAPublicKey) {
AndroidKeyStoreRSAPublicKey rsaKey = (AndroidKeyStoreRSAPublicKey) key;
@SuppressWarnings("unchecked")
T result =
(T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
return result;
} else {
throw new InvalidKeySpecException(
"Obtaining RSAPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ " key");
}
} else if (ECPublicKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStoreECPublicKey) {
AndroidKeyStoreECPublicKey ecKey = (AndroidKeyStoreECPublicKey) key;
@SuppressWarnings("unchecked")
T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams());
return result;
} else {
throw new InvalidKeySpecException(
"Obtaining ECPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ " key");
}
} else {
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
}
}
@Override
protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate a key pair in Android Keystore, use KeyPairGenerator initialized with"
+ " " + KeyGenParameterSpec.class.getName());
}
@Override
protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate a key pair in Android Keystore, use KeyPairGenerator initialized with"
+ " " + KeyGenParameterSpec.class.getName());
}
@Override
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if ((!(key instanceof AndroidKeyStorePrivateKey))
&& (!(key instanceof AndroidKeyStorePublicKey))) {
throw new InvalidKeyException(
"To import a key into Android Keystore, use KeyStore.setEntry");
}
return key;
}
}

View File

@@ -1,352 +0,0 @@
/*
* 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.keystore;
import android.security.Credentials;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import libcore.util.EmptyArray;
import java.security.InvalidAlgorithmParameterException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.SecretKey;
/**
* {@link KeyGeneratorSpi} backed by Android KeyStore.
*
* @hide
*/
public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
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 KeyStore mKeyStore = KeyStore.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).
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), 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");
}
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, mKeymasterPaddings);
args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
KeymasterUtils.addUserAuthArgs(args, spec);
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
mKeymasterAlgorithm,
mKeymasterBlockModes,
mKeymasterDigests);
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
spec.getKeyValidityForOriginationEnd());
args.addDateIfNotNull(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
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
int flags = 0;
if (spec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
if (spec.isCriticalToDeviceEncryption()) {
flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
}
String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
boolean success = false;
try {
Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias(), spec.getUid());
int errorCode = mKeyStore.generateKey(
keyAliasInKeystore,
args,
additionalEntropy,
spec.getUid(),
flags,
resultingKeyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) {
throw new StrongBoxUnavailableException("Failed to generate key");
} else {
throw new ProviderException(
"Keystore operation failed", KeyStore.getKeyStoreException(errorCode));
}
}
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
try {
keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
mKeymasterAlgorithm, mKeymasterDigest);
} catch (IllegalArgumentException e) {
throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
}
SecretKey result = new AndroidKeyStoreSecretKey(
keyAliasInKeystore, spec.getUid(), keyAlgorithmJCA);
success = true;
return result;
} finally {
if (!success) {
Credentials.deleteAllTypesForAlias(
mKeyStore, spec.getKeystoreAlias(), spec.getUid());
}
}
}
}

View File

@@ -1,986 +0,0 @@
/*
* 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.content.Context;
import android.os.Build;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
import com.android.internal.org.bouncycastle.asn1.DERBitString;
import com.android.internal.org.bouncycastle.asn1.DERNull;
import com.android.internal.org.bouncycastle.asn1.DERSequence;
import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import com.android.internal.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import com.android.internal.org.bouncycastle.asn1.x509.TBSCertificate;
import com.android.internal.org.bouncycastle.asn1.x509.Time;
import com.android.internal.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
import com.android.internal.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import com.android.internal.org.bouncycastle.jce.X509Principal;
import com.android.internal.org.bouncycastle.jce.provider.X509CertificateObject;
import com.android.internal.org.bouncycastle.x509.X509V3CertificateGenerator;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.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;
}
if (mSpec.isCriticalToDeviceEncryption()) {
flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
}
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 | IllegalArgumentException | DeviceIdAttestationException e) {
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(e);
} else {
throw new ProviderException(e);
}
} finally {
if (!success) {
Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
}
}
}
private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
throws ProviderException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
KeymasterArguments args = new KeymasterArguments();
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge);
if (mSpec.isDevicePropertiesAttestationIncluded()) {
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
Build.BRAND.getBytes(StandardCharsets.UTF_8));
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8));
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
Build.PRODUCT.getBytes(StandardCharsets.UTF_8));
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8));
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
Build.MODEL.getBytes(StandardCharsets.UTF_8));
}
int[] idTypes = mSpec.getAttestationIds();
if (idTypes != null) {
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) KeyStore.getApplicationContext().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:
args.addBytes(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");
}
args.addBytes(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");
}
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
meid.getBytes(StandardCharsets.UTF_8)
);
break;
}
case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
break;
}
default:
throw new IllegalArgumentException("Unknown device ID type " + idType);
}
}
}
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()
throws IllegalArgumentException, DeviceIdAttestationException {
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 {
final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
final int errorCode;
if (mSpec.isDevicePropertiesAttestationIncluded()
&& mSpec.getAttestationChallenge() == null) {
throw new ProviderException("An attestation challenge must be provided when requesting "
+ "device properties attestation.");
}
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 ASN1Integer(BigInteger.valueOf(0)));
v.add(new ASN1Integer(BigInteger.valueOf(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;
}
}

View File

@@ -1,38 +0,0 @@
/*
* 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.keystore;
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
private final int mUid;
AndroidKeyStoreLoadStoreParameter(int uid) {
mUid = uid;
}
@Override
public ProtectionParameter getProtectionParameter() {
return null;
}
int getUid() {
return mUid;
}
}

View File

@@ -1,31 +0,0 @@
/*
* 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.keystore;
import java.security.PrivateKey;
/**
* {@link PrivateKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey {
public AndroidKeyStorePrivateKey(String alias, int uid, String algorithm) {
super(alias, uid, algorithm);
}
}

View File

@@ -20,30 +20,13 @@ import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.security.KeyStore;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterDefs;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -57,117 +40,10 @@ import javax.crypto.Mac;
public class AndroidKeyStoreProvider extends Provider {
private static final String PROVIDER_NAME = "AndroidKeyStore";
// IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
// classes when this provider is instantiated and installed early on during each app's
// initialization process.
//
// Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider.
// Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc
// for details.
private static final String PACKAGE_NAME = "android.security.keystore";
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
/** @hide */
public AndroidKeyStoreProvider() {
this(PROVIDER_NAME);
}
/** @hide **/
public AndroidKeyStoreProvider(String providerName) {
super(providerName, 1.0, "Android KeyStore security provider");
boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY));
// java.security.KeyStore
put("KeyStore." + providerName, PACKAGE_NAME + ".AndroidKeyStoreSpi");
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
// java.security.KeyFactory
putKeyFactoryImpl("EC");
putKeyFactoryImpl("RSA");
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224");
put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256");
put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384");
put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512");
if (supports3DES) {
put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede");
}
// java.security.SecretKeyFactory
putSecretKeyFactoryImpl("AES");
if (supports3DES) {
putSecretKeyFactoryImpl("DESede");
}
putSecretKeyFactoryImpl("HmacSHA1");
putSecretKeyFactoryImpl("HmacSHA224");
putSecretKeyFactoryImpl("HmacSHA256");
putSecretKeyFactoryImpl("HmacSHA384");
putSecretKeyFactoryImpl("HmacSHA512");
}
/**
* This function indicates whether or not Keystore 2.0 is enabled. Some parts of the
* Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However,
* the platform property that indicates that Keystore 2.0 is enabled is not readable
* by applications. So we set this value when {@code install()} is called because it
* is called by zygote, which can access Keystore2Properties.
*
* This function can be removed once the transition to Keystore 2.0 is complete.
* b/171305684
*
* @return true if Keystore 2.0 is enabled.
* @hide
*/
public static boolean isKeystore2Enabled() {
return android.security.keystore2.AndroidKeyStoreProvider.isInstalled();
}
/**
* Installs a new instance of this provider (and the
* {@link AndroidKeyStoreBCWorkaroundProvider}).
* @hide
*/
public static void install() {
Provider[] providers = Security.getProviders();
int bcProviderIndex = -1;
for (int i = 0; i < providers.length; i++) {
Provider provider = providers[i];
if ("BC".equals(provider.getName())) {
bcProviderIndex = i;
break;
}
}
Security.addProvider(new AndroidKeyStoreProvider());
Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
if (bcProviderIndex != -1) {
// Bouncy Castle provider found -- install the workaround provider above it.
// insertProviderAt uses 1-based positions.
Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
} else {
// Bouncy Castle provider not found -- install the workaround provider at lowest
// priority.
Security.addProvider(workaroundProvider);
}
}
private void putSecretKeyFactoryImpl(String algorithm) {
put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
}
private void putKeyFactoryImpl(String algorithm) {
put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
public AndroidKeyStoreProvider(@NonNull String name) {
super(name, 1.0, "Android KeyStore security provider");
throw new IllegalStateException("Should not be instantiated.");
}
/**
@@ -189,229 +65,7 @@ public class AndroidKeyStoreProvider extends Provider {
if (cryptoPrimitive == null) {
throw new NullPointerException();
}
Object spi;
if (cryptoPrimitive instanceof Signature) {
spi = ((Signature) cryptoPrimitive).getCurrentSpi();
} else if (cryptoPrimitive instanceof Mac) {
spi = ((Mac) cryptoPrimitive).getCurrentSpi();
} else if (cryptoPrimitive instanceof Cipher) {
spi = ((Cipher) cryptoPrimitive).getCurrentSpi();
} else {
throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive
+ ". Supported: Signature, Mac, Cipher");
}
if (spi == null) {
throw new IllegalStateException("Crypto primitive not initialized");
} else if (!(spi instanceof KeyStoreCryptoOperation)) {
throw new IllegalArgumentException(
"Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive
+ ", spi: " + spi);
}
return ((KeyStoreCryptoOperation) spi).getOperationHandle();
}
/** @hide **/
@NonNull
public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey(
@NonNull String alias,
int uid,
@NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm,
@NonNull byte[] x509EncodedForm) {
PublicKey publicKey;
try {
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm));
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain " + keyAlgorithm + " KeyFactory", e);
} catch (InvalidKeySpecException e) {
throw new ProviderException("Invalid X.509 encoding of public key", e);
}
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
return new AndroidKeyStoreECPublicKey(alias, uid, (ECPublicKey) publicKey);
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
return new AndroidKeyStoreRSAPublicKey(alias, uid, (RSAPublicKey) publicKey);
} else {
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ keyAlgorithm);
}
}
@NonNull
private static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
@NonNull AndroidKeyStorePublicKey publicKey) {
String keyAlgorithm = publicKey.getAlgorithm();
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
return new AndroidKeyStoreECPrivateKey(
publicKey.getAlias(), publicKey.getUid(), ((ECKey) publicKey).getParams());
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
return new AndroidKeyStoreRSAPrivateKey(
publicKey.getAlias(), publicKey.getUid(), ((RSAKey) publicKey).getModulus());
} else {
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ keyAlgorithm);
}
}
@NonNull
private static KeyCharacteristics getKeyCharacteristics(@NonNull KeyStore keyStore,
@NonNull String alias, int uid)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = keyStore.getKeyCharacteristics(
alias, null, null, uid, keyCharacteristics);
if (errorCode == KeyStore.KEY_PERMANENTLY_INVALIDATED) {
throw (KeyPermanentlyInvalidatedException)
new KeyPermanentlyInvalidatedException(
"User changed or deleted their auth credentials",
KeyStore.getKeyStoreException(errorCode));
}
if (errorCode != KeyStore.NO_ERROR) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to obtain information about key")
.initCause(KeyStore.getKeyStoreException(errorCode));
}
return keyCharacteristics;
}
@NonNull
private static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
KeyCharacteristics keyCharacteristics)
throws UnrecoverableKeyException {
ExportResult exportResult = keyStore.exportKey(
privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid);
if (exportResult.resultCode != KeyStore.NO_ERROR) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to obtain X.509 form of public key")
.initCause(KeyStore.getKeyStoreException(exportResult.resultCode));
}
final byte[] x509EncodedPublicKey = exportResult.exportData;
Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
if (keymasterAlgorithm == null) {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
String jcaKeyAlgorithm;
try {
jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
keymasterAlgorithm);
} catch (IllegalArgumentException e) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to load private key")
.initCause(e);
}
return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
privateKeyAlias, uid, jcaKeyAlgorithm, x509EncodedPublicKey);
}
/** @hide **/
@NonNull
public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
return loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
getKeyCharacteristics(keyStore, privateKeyAlias, uid));
}
@NonNull
private static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
@NonNull KeyCharacteristics keyCharacteristics)
throws UnrecoverableKeyException {
AndroidKeyStorePublicKey publicKey =
loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
keyCharacteristics);
AndroidKeyStorePrivateKey privateKey =
AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
return new KeyPair(publicKey, privateKey);
}
/** @hide **/
@NonNull
public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
return loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
getKeyCharacteristics(keyStore, privateKeyAlias, uid));
}
@NonNull
private static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
@NonNull KeyCharacteristics keyCharacteristics)
throws UnrecoverableKeyException {
KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
keyCharacteristics);
return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
}
/** @hide **/
@NonNull
public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, privateKeyAlias, uid,
getKeyCharacteristics(keyStore, privateKeyAlias, uid));
}
@NonNull
private static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
@NonNull String secretKeyAlias, int uid, @NonNull KeyCharacteristics keyCharacteristics)
throws UnrecoverableKeyException {
Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
if (keymasterAlgorithm == null) {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
List<Integer> keymasterDigests = keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST);
int keymasterDigest;
if (keymasterDigests.isEmpty()) {
keymasterDigest = -1;
} else {
// More than one digest can be permitted for this key. Use the first one to form the
// JCA key algorithm name.
keymasterDigest = keymasterDigests.get(0);
}
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
try {
keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
keymasterAlgorithm, keymasterDigest);
} catch (IllegalArgumentException e) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
}
return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString);
}
/** @hide **/
@NonNull
public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String userKeyAlias, int uid)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid);
Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
if (keymasterAlgorithm == null) {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid,
keyCharacteristics);
} else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, userKeyAlias, uid,
keyCharacteristics);
} else {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
return 0;
}
/**
@@ -434,13 +88,9 @@ public class AndroidKeyStoreProvider extends Provider {
@NonNull
public static java.security.KeyStore getKeyStoreForUid(int uid)
throws KeyStoreException, NoSuchProviderException {
final java.security.KeyStore.LoadStoreParameter loadParameter;
if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
KeyProperties.legacyUidToNamespace(uid));
} else {
loadParameter = new AndroidKeyStoreLoadStoreParameter(uid);
}
final java.security.KeyStore.LoadStoreParameter loadParameter =
new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
KeyProperties.legacyUidToNamespace(uid));
java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
try {
result.load(loadParameter);

View File

@@ -1,71 +0,0 @@
/*
* 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.keystore;
import java.security.PublicKey;
import java.util.Arrays;
/**
* {@link PublicKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
private final byte[] mEncoded;
public AndroidKeyStorePublicKey(String alias, int uid, String algorithm, byte[] x509EncodedForm) {
super(alias, uid, algorithm);
mEncoded = ArrayUtils.cloneIfNotEmpty(x509EncodedForm);
}
@Override
public String getFormat() {
return "X.509";
}
@Override
public byte[] getEncoded() {
return ArrayUtils.cloneIfNotEmpty(mEncoded);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(mEncoded);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj;
if (!Arrays.equals(mEncoded, other.mEncoded)) {
return false;
}
return true;
}
}

View File

@@ -1,515 +0,0 @@
/*
* 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.keystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.MGF1ParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
/**
* Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption.
*
* @hide
*/
abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase {
/**
* Raw RSA cipher without any padding.
*/
public static final class NoPadding extends AndroidKeyStoreRSACipherSpi {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
@Override
protected boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with no padding using private key is a way to implement raw RSA
// signatures which JCA does not expose via Signature. We thus have to support this.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
return true;
}
@Override
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
}
/**
* RSA cipher with PKCS#1 v1.5 encryption padding.
*/
public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi {
public PKCS1Padding() {
super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
}
@Override
protected boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with PCKS#1 padding using private key is a way to implement RSA
// signatures with PKCS#1 padding. We have to support this for legacy reasons.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
return true;
}
@Override
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? getModulusSizeBytes() : 0;
}
}
/**
* RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF.
*/
abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
private static final String MGF_ALGORITGM_MGF1 = "MGF1";
private int mKeymasterDigest = -1;
private int mDigestOutputSizeBytes;
OAEPWithMGF1Padding(int keymasterDigest) {
super(KeymasterDefs.KM_PAD_RSA_OAEP);
mKeymasterDigest = keymasterDigest;
mDigestOutputSizeBytes =
(KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected final void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
if (params == null) {
return;
}
if (!(params instanceof OAEPParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"Unsupported parameter spec: " + params
+ ". Only OAEPParameterSpec supported");
}
OAEPParameterSpec spec = (OAEPParameterSpec) params;
if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported MGF: " + spec.getMGFAlgorithm()
+ ". Only " + MGF_ALGORITGM_MGF1 + " supported");
}
String jcaDigest = spec.getDigestAlgorithm();
int keymasterDigest;
try {
keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest);
} catch (IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(
"Unsupported digest: " + jcaDigest, e);
}
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_SHA1:
case KeymasterDefs.KM_DIGEST_SHA_2_224:
case KeymasterDefs.KM_DIGEST_SHA_2_256:
case KeymasterDefs.KM_DIGEST_SHA_2_384:
case KeymasterDefs.KM_DIGEST_SHA_2_512:
// Permitted.
break;
default:
throw new InvalidAlgorithmParameterException(
"Unsupported digest: " + jcaDigest);
}
AlgorithmParameterSpec mgfParams = spec.getMGFParameters();
if (mgfParams == null) {
throw new InvalidAlgorithmParameterException("MGF parameters must be provided");
}
// Check whether MGF parameters match the OAEPParameterSpec
if (!(mgfParams instanceof MGF1ParameterSpec)) {
throw new InvalidAlgorithmParameterException("Unsupported MGF parameters"
+ ": " + mgfParams + ". Only MGF1ParameterSpec supported");
}
MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams;
String mgf1JcaDigest = mgfSpec.getDigestAlgorithm();
if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) {
throw new InvalidAlgorithmParameterException(
"Unsupported MGF1 digest: " + mgf1JcaDigest
+ ". Only " + KeyProperties.DIGEST_SHA1 + " supported");
}
PSource pSource = spec.getPSource();
if (!(pSource instanceof PSource.PSpecified)) {
throw new InvalidAlgorithmParameterException(
"Unsupported source of encoding input P: " + pSource
+ ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
}
PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource;
byte[] pSourceValue = pSourceSpecified.getValue();
if ((pSourceValue != null) && (pSourceValue.length > 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported source of encoding input P: " + pSource
+ ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
}
mKeymasterDigest = keymasterDigest;
mDigestOutputSizeBytes =
(KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
}
@Override
protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params == null) {
return;
}
OAEPParameterSpec spec;
try {
spec = params.getParameterSpec(OAEPParameterSpec.class);
} catch (InvalidParameterSpecException e) {
throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ ", but not found in parameters: " + params, e);
}
if (spec == null) {
throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ ", but not provided in parameters: " + params);
}
initAlgorithmSpecificParameters(spec);
}
@Override
protected final AlgorithmParameters engineGetParameters() {
OAEPParameterSpec spec =
new OAEPParameterSpec(
KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
MGF_ALGORITGM_MGF1,
MGF1ParameterSpec.SHA1,
PSource.PSpecified.DEFAULT);
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
params.init(spec);
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain OAEP AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize OAEP AlgorithmParameters with an IV",
e);
}
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
KeymasterArguments keymasterArgs) {
super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
super.loadAlgorithmSpecificParametersFromBeginResult(keymasterArgs);
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
}
}
public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA1AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA224AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA256AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA384AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA512AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterPadding;
private int mKeymasterPaddingOverride;
private int mModulusSizeBytes = -1;
AndroidKeyStoreRSACipherSpi(int keymasterPadding) {
mKeymasterPadding = keymasterPadding;
}
@Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("Unsupported key: null");
}
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
AndroidKeyStoreKey keystoreKey;
if (key instanceof AndroidKeyStorePrivateKey) {
keystoreKey = (AndroidKeyStoreKey) key;
} else if (key instanceof AndroidKeyStorePublicKey) {
keystoreKey = (AndroidKeyStoreKey) key;
} else {
throw new InvalidKeyException("Unsupported key type: " + key);
}
if (keystoreKey instanceof PrivateKey) {
// Private key
switch (opmode) {
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
// Permitted
break;
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
if (!adjustConfigForEncryptingWithPrivateKey()) {
throw new InvalidKeyException(
"RSA private keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA public keys supported for this mode");
}
break;
default:
throw new InvalidKeyException(
"RSA private keys cannot be used with opmode: " + opmode);
}
} else {
// Public key
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
// Permitted
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA private keys supported for this opmode.");
// break;
default:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode));
}
}
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = getKeyStore().getKeyCharacteristics(
keystoreKey.getAlias(), null, null, keystoreKey.getUid(), keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw getKeyStore().getInvalidKeyException(
keystoreKey.getAlias(), keystoreKey.getUid(), errorCode);
}
long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
if (keySizeBits == -1) {
throw new InvalidKeyException("Size of key not known");
} else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
}
mModulusSizeBytes = (int) ((keySizeBits + 7) / 8);
setKey(keystoreKey);
}
/**
* Adjusts the configuration of this cipher for encrypting using the private key.
*
* <p>The default implementation does nothing and refuses to adjust the configuration.
*
* @return {@code true} if the configuration has been adjusted, {@code false} if encrypting
* using private key is not permitted for this cipher.
*/
protected boolean adjustConfigForEncryptingWithPrivateKey() {
return false;
}
@Override
protected final void resetAll() {
mModulusSizeBytes = -1;
mKeymasterPaddingOverride = -1;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
int keymasterPadding = getKeymasterPaddingOverride();
if (keymasterPadding == -1) {
keymasterPadding = mKeymasterPadding;
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
int purposeOverride = getKeymasterPurposeOverride();
if ((purposeOverride != -1)
&& ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN)
|| (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) {
// Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE.
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
}
}
@Override
protected void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
}
@Override
protected final int engineGetBlockSize() {
// Not a block cipher, according to the RI
return 0;
}
@Override
protected final byte[] engineGetIV() {
// IV never used
return null;
}
@Override
protected final int engineGetOutputSize(int inputLen) {
return getModulusSizeBytes();
}
protected final int getModulusSizeBytes() {
if (mModulusSizeBytes == -1) {
throw new IllegalStateException("Not initialized");
}
return mModulusSizeBytes;
}
/**
* Overrides the default padding of the crypto operation.
*/
protected final void setKeymasterPaddingOverride(int keymasterPadding) {
mKeymasterPaddingOverride = keymasterPadding;
}
protected final int getKeymasterPaddingOverride() {
return mKeymasterPaddingOverride;
}
}

View File

@@ -1,41 +0,0 @@
/*
* 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.keystore;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.interfaces.RSAKey;
/**
* RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey {
private final BigInteger mModulus;
public AndroidKeyStoreRSAPrivateKey(String alias, int uid, BigInteger modulus) {
super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA);
mModulus = modulus;
}
@Override
public BigInteger getModulus() {
return mModulus;
}
}

View File

@@ -1,55 +0,0 @@
/*
* 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.keystore;
import java.math.BigInteger;
import java.security.interfaces.RSAPublicKey;
/**
* {@link RSAPublicKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey {
private final BigInteger mModulus;
private final BigInteger mPublicExponent;
public AndroidKeyStoreRSAPublicKey(String alias, int uid, byte[] x509EncodedForm, BigInteger modulus,
BigInteger publicExponent) {
super(alias, uid, KeyProperties.KEY_ALGORITHM_RSA, x509EncodedForm);
mModulus = modulus;
mPublicExponent = publicExponent;
}
public AndroidKeyStoreRSAPublicKey(String alias, int uid, RSAPublicKey info) {
this(alias, uid, info.getEncoded(), info.getModulus(), info.getPublicExponent());
if (!"X.509".equalsIgnoreCase(info.getFormat())) {
throw new IllegalArgumentException(
"Unsupported key export format: " + info.getFormat());
}
}
@Override
public BigInteger getModulus() {
return mModulus;
}
@Override
public BigInteger getPublicExponent() {
return mPublicExponent;
}
}

View File

@@ -1,164 +0,0 @@
/*
* 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.keystore;
import android.annotation.NonNull;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.InvalidKeyException;
import java.security.SignatureSpi;
/**
* Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures.
*
* @hide
*/
abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi {
PKCS1Padding(int keymasterDigest) {
super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
// No entropy required for this deterministic signature scheme.
return 0;
}
}
public static final class NONEWithPKCS1Padding extends PKCS1Padding {
public NONEWithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_NONE);
}
}
public static final class MD5WithPKCS1Padding extends PKCS1Padding {
public MD5WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_MD5);
}
}
public static final class SHA1WithPKCS1Padding extends PKCS1Padding {
public SHA1WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static final class SHA224WithPKCS1Padding extends PKCS1Padding {
public SHA224WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static final class SHA256WithPKCS1Padding extends PKCS1Padding {
public SHA256WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static final class SHA384WithPKCS1Padding extends PKCS1Padding {
public SHA384WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static final class SHA512WithPKCS1Padding extends PKCS1Padding {
public SHA512WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi {
private static final int SALT_LENGTH_BYTES = 20;
PSSPadding(int keymasterDigest) {
super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PSS);
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
return SALT_LENGTH_BYTES;
}
}
public static final class SHA1WithPSSPadding extends PSSPadding {
public SHA1WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static final class SHA224WithPSSPadding extends PSSPadding {
public SHA224WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static final class SHA256WithPSSPadding extends PSSPadding {
public SHA256WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static final class SHA384WithPSSPadding extends PSSPadding {
public SHA384WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static final class SHA512WithPSSPadding extends PSSPadding {
public SHA512WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterDigest;
private final int mKeymasterPadding;
AndroidKeyStoreRSASignatureSpi(int keymasterDigest, int keymasterPadding) {
mKeymasterDigest = keymasterDigest;
mKeymasterPadding = keymasterPadding;
}
@Override
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
super.initKey(key);
}
@Override
protected final void resetAll() {
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
}
}

View File

@@ -1,31 +0,0 @@
/*
* 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.keystore;
import javax.crypto.SecretKey;
/**
* {@link SecretKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey {
public AndroidKeyStoreSecretKey(String alias, int uid, String algorithm) {
super(alias, uid, algorithm);
}
}

View File

@@ -1,248 +0,0 @@
/*
* 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.keystore;
import android.security.Credentials;
import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterDefs;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactorySpi;
import javax.crypto.spec.SecretKeySpec;
/**
* {@link SecretKeyFactorySpi} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
private final KeyStore mKeyStore = KeyStore.getInstance();
@Override
protected KeySpec engineGetKeySpec(SecretKey key,
@SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
if (keySpecClass == null) {
throw new InvalidKeySpecException("keySpecClass == null");
}
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " +
((key != null) ? key.getClass().getName() : "null"));
}
if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) {
throw new InvalidKeySpecException(
"Key material export of Android KeyStore keys is not supported");
}
if (!KeyInfo.class.equals(keySpecClass)) {
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
}
AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key;
String keyAliasInKeystore = keystoreKey.getAlias();
String entryAlias;
if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
} else if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)){
// key has legacy prefix
entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
} else {
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
}
return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore, keystoreKey.getUid());
}
static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore,
int keyUid) {
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = keyStore.getKeyCharacteristics(
keyAliasInKeystore, null, null, keyUid, keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw new ProviderException("Failed to obtain information about key."
+ " Keystore error: " + errorCode);
}
boolean insideSecureHardware;
@KeyProperties.OriginEnum int origin;
int keySize;
@KeyProperties.PurposeEnum int purposes;
String[] encryptionPaddings;
String[] signaturePaddings;
@KeyProperties.DigestEnum String[] digests;
@KeyProperties.BlockModeEnum String[] blockModes;
int keymasterSwEnforcedUserAuthenticators;
int keymasterHwEnforcedUserAuthenticators;
List<BigInteger> keymasterSecureUserIds;
try {
if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
insideSecureHardware = true;
origin = KeyProperties.Origin.fromKeymaster(
keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1));
} else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
insideSecureHardware = false;
origin = KeyProperties.Origin.fromKeymaster(
keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_ORIGIN, -1));
} else {
throw new ProviderException("Key origin not available");
}
long keySizeUnsigned =
keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
if (keySizeUnsigned == -1) {
throw new ProviderException("Key size not available");
} else if (keySizeUnsigned > Integer.MAX_VALUE) {
throw new ProviderException("Key too large: " + keySizeUnsigned + " bits");
}
keySize = (int) keySizeUnsigned;
purposes = KeyProperties.Purpose.allFromKeymaster(
keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PURPOSE));
List<String> encryptionPaddingsList = new ArrayList<String>();
List<String> signaturePaddingsList = new ArrayList<String>();
// Keymaster stores both types of paddings in the same array -- we split it into two.
for (int keymasterPadding : keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_PADDING)) {
try {
@KeyProperties.EncryptionPaddingEnum String jcaPadding =
KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
encryptionPaddingsList.add(jcaPadding);
} catch (IllegalArgumentException e) {
try {
@KeyProperties.SignaturePaddingEnum String padding =
KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding);
signaturePaddingsList.add(padding);
} catch (IllegalArgumentException e2) {
throw new ProviderException(
"Unsupported encryption padding: " + keymasterPadding);
}
}
}
encryptionPaddings =
encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]);
signaturePaddings =
signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]);
digests = KeyProperties.Digest.allFromKeymaster(
keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST));
blockModes = KeyProperties.BlockMode.allFromKeymaster(
keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_BLOCK_MODE));
keymasterSwEnforcedUserAuthenticators =
keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
keymasterHwEnforcedUserAuthenticators =
keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
keymasterSecureUserIds =
keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
} catch (IllegalArgumentException e) {
throw new ProviderException("Unsupported key characteristic", e);
}
Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
Date keyValidityForOriginationEnd =
keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME);
Date keyValidityForConsumptionEnd =
keyCharacteristics.getDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME);
boolean userAuthenticationRequired =
!keyCharacteristics.getBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
long userAuthenticationValidityDurationSeconds =
keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, 0);
if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) {
throw new ProviderException("User authentication timeout validity too long: "
+ userAuthenticationValidityDurationSeconds + " seconds");
}
boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
&& (keymasterHwEnforcedUserAuthenticators != 0)
&& (keymasterSwEnforcedUserAuthenticators == 0);
boolean userAuthenticationValidWhileOnBody =
keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
boolean trustedUserPresenceRequired =
keyCharacteristics.hwEnforced.getBoolean(
KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
boolean invalidatedByBiometricEnrollment = false;
if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC
|| keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) {
// Fingerprint-only key; will be invalidated if the root SID isn't in the SID list.
invalidatedByBiometricEnrollment = keymasterSecureUserIds != null
&& !keymasterSecureUserIds.isEmpty()
&& !keymasterSecureUserIds.contains(getGateKeeperSecureUserId());
}
boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED);
return new KeyInfo(entryAlias,
insideSecureHardware,
origin,
keySize,
keyValidityStart,
keyValidityForOriginationEnd,
keyValidityForConsumptionEnd,
purposes,
encryptionPaddings,
signaturePaddings,
digests,
blockModes,
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
keymasterHwEnforcedUserAuthenticators,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequired,
invalidatedByBiometricEnrollment,
userConfirmationRequired,
// Keystore 1.0 does not tell us the exact security level of the key
// so we report an unknown but secure security level.
insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE
: KeyProperties.SECURITY_LEVEL_SOFTWARE,
KeyProperties.UNRESTRICTED_USAGE_COUNT);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
try {
return BigInteger.valueOf(GateKeeper.getSecureUserId());
} catch (IllegalStateException e) {
throw new ProviderException("Failed to get GateKeeper secure user ID", e);
}
}
@Override
protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate secret key in Android Keystore, use KeyGenerator initialized with "
+ KeyGenParameterSpec.class.getName());
}
@Override
protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"To import a secret key into Android Keystore, use KeyStore.setEntry");
}
return key;
}
}

View File

@@ -1,431 +0,0 @@
/*
* 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.keystore;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import libcore.util.EmptyArray;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.SignatureSpi;
/**
* Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
implements KeyStoreCryptoOperation {
private final KeyStore mKeyStore;
// Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin
// and should be preserved after SignatureSpi.engineSign/engineVerify finishes.
private boolean mSigning;
private AndroidKeyStoreKey mKey;
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInitSign}/{@code engineInitVerify} and is invalidated when
* {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between.
*/
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationStreamer mMessageStreamer;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
* a method that does not throw checked exception. This exception will be thrown from
* {@code engineSign} or {@code engineVerify}. Once such an exception is encountered,
* {@code engineUpdate} starts ignoring input data.
*/
private Exception mCachedException;
AndroidKeyStoreSignatureSpiBase() {
mKeyStore = KeyStore.getInstance();
}
@Override
protected final void engineInitSign(PrivateKey key) throws InvalidKeyException {
engineInitSign(key, null);
}
@Override
protected final void engineInitSign(PrivateKey privateKey, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
if (privateKey == null) {
throw new InvalidKeyException("Unsupported key: null");
}
AndroidKeyStoreKey keystoreKey;
if (privateKey instanceof AndroidKeyStorePrivateKey) {
keystoreKey = (AndroidKeyStoreKey) privateKey;
} else {
throw new InvalidKeyException("Unsupported private key type: " + privateKey);
}
mSigning = true;
initKey(keystoreKey);
appRandom = random;
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
resetAll();
boolean success = false;
try {
if (publicKey == null) {
throw new InvalidKeyException("Unsupported key: null");
}
AndroidKeyStoreKey keystoreKey;
if (publicKey instanceof AndroidKeyStorePublicKey) {
keystoreKey = (AndroidKeyStorePublicKey) publicKey;
} else {
throw new InvalidKeyException("Unsupported public key type: " + publicKey);
}
mSigning = false;
initKey(keystoreKey);
appRandom = null;
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
/**
* Configures this signature instance to use the provided key.
*
* @throws InvalidKeyException if the {@code key} is not suitable.
*/
@CallSuper
protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
mKey = key;
}
/**
* Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
* cipher instance.
*
* <p>Subclasses storing additional state should override this method, reset the additional
* state, and then chain to superclass.
*/
@CallSuper
protected void resetAll() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mSigning = false;
mKey = null;
appRandom = null;
mOperationToken = null;
mOperationHandle = 0;
mMessageStreamer = null;
mCachedException = null;
}
/**
* Resets this cipher while preserving the initialized state. This must be equivalent to
* rolling back the cipher's state to just after the most recent {@code engineInit} completed
* successfully.
*
* <p>Subclasses storing additional post-init state should override this method, reset the
* additional state, and then chain to superclass.
*/
@CallSuper
protected void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mOperationHandle = 0;
mMessageStreamer = null;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
if (mMessageStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
null, // no additional entropy for begin -- only finish might need some
mKey.getUid());
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
throw e;
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token);
}
/**
* Creates a streamer which sends the message to be signed/verified into the provided KeyStore
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStore keyStore, IBinder operationToken) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
keyStore, operationToken));
}
@Override
public final long getOperationHandle() {
return mOperationHandle;
}
@Override
protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new SignatureException(e);
}
if (len == 0) {
return;
}
byte[] output;
try {
output = mMessageStreamer.update(b, off, len);
} catch (KeyStoreException e) {
throw new SignatureException(e);
}
if (output.length != 0) {
throw new ProviderException(
"Update operation unexpectedly produced output: " + output.length + " bytes");
}
}
@Override
protected final void engineUpdate(byte b) throws SignatureException {
engineUpdate(new byte[] {b}, 0, 1);
}
@Override
protected final void engineUpdate(ByteBuffer input) {
byte[] b;
int off;
int len = input.remaining();
if (input.hasArray()) {
b = input.array();
off = input.arrayOffset() + input.position();
input.position(input.limit());
} else {
b = new byte[len];
off = 0;
input.get(b);
}
try {
engineUpdate(b, off, len);
} catch (SignatureException e) {
mCachedException = e;
}
}
@Override
protected final int engineSign(byte[] out, int outOffset, int outLen)
throws SignatureException {
return super.engineSign(out, outOffset, outLen);
}
@Override
protected final byte[] engineSign() throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
byte[] signature;
try {
ensureKeystoreOperationInitialized();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
appRandom, getAdditionalEntropyAmountForSign());
signature = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null, // no signature provided -- it'll be generated by this invocation
additionalEntropy);
} catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e);
}
resetWhilePreservingInitState();
return signature;
}
@Override
protected final boolean engineVerify(byte[] signature) throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new SignatureException(e);
}
boolean verified;
try {
byte[] output = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
signature,
null // no additional entropy needed -- verification is deterministic
);
if (output.length != 0) {
throw new ProviderException(
"Signature verification unexpected produced output: " + output.length
+ " bytes");
}
verified = true;
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
verified = false;
break;
default:
throw new SignatureException(e);
}
}
resetWhilePreservingInitState();
return verified;
}
@Override
protected final boolean engineVerify(byte[] sigBytes, int offset, int len)
throws SignatureException {
return engineVerify(ArrayUtils.subarray(sigBytes, offset, len));
}
@Deprecated
@Override
protected final Object engineGetParameter(String param) throws InvalidParameterException {
throw new InvalidParameterException();
}
@Deprecated
@Override
protected final void engineSetParameter(String param, Object value)
throws InvalidParameterException {
throw new InvalidParameterException();
}
protected final KeyStore getKeyStore() {
return mKeyStore;
}
/**
* Returns {@code true} if this signature is initialized for signing, {@code false} if this
* signature is initialized for verification.
*/
protected final boolean isSigning() {
return mSigning;
}
// The methods below need to be implemented by subclasses.
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code finish} operation when generating a signature.
*
* <p>This value should match (or exceed) the amount of Shannon entropy of the produced
* signature assuming the key and the message are known. For example, for ECDSA signature this
* should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this
* should be {@code 0}.
*/
protected abstract int getAdditionalEntropyAmountForSign();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,319 +0,0 @@
/*
* 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.keystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.CipherSpi;
import javax.crypto.spec.IvParameterSpec;
/**
* Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations.
*
* @hide
*/
class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected ECB(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
}
public static class NoPadding extends ECB {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends ECB {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected CBC(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
}
public static class NoPadding extends CBC {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends CBC {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected CTR(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
}
public static class NoPadding extends CTR {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
}
private static final int BLOCK_SIZE_BYTES = 16;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
/** Whether this transformation requires an IV. */
private final boolean mIvRequired;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
AndroidKeyStoreUnauthenticatedAESCipherSpi(
int keymasterBlockMode,
int keymasterPadding,
boolean ivRequired) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
mIvRequired = ivRequired;
}
@Override
protected final void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException(
"Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
KeyProperties.KEY_ALGORITHM_AES + " supported");
}
setKey((AndroidKeyStoreSecretKey) key);
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
if (!mIvRequired) {
return;
}
// IV is used
if (!isEncrypting()) {
throw new InvalidKeyException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException(
"IvParameterSpec must be provided when decrypting");
}
return;
}
if (!(params instanceof IvParameterSpec)) {
throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
}
mIv = ((IvParameterSpec) params).getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
}
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
return;
}
if (!"AES".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: AES");
}
IvParameterSpec ivSpec;
try {
ivSpec = params.getParameterSpec(IvParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ", but not found in parameters: " + params, e);
}
mIv = null;
return;
}
mIv = ivSpec.getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
}
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
// IV will need to be generated
return BLOCK_SIZE_BYTES;
}
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
// IV is being reused for encryption: this violates security best practices.
throw new IllegalStateException(
"IV has already been used. Reusing IV in encryption mode violates security best"
+ " practices.");
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
if ((returnedIv != null) && (returnedIv.length == 0)) {
returnedIv = null;
}
if (mIvRequired) {
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
throw new ProviderException("IV in use differs from provided IV");
}
} else {
if (returnedIv != null) {
throw new ProviderException(
"IV in use despite IV not being used by this transformation");
}
}
}
@Override
protected final int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected final int engineGetOutputSize(int inputLen) {
return inputLen + 3 * BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
@Nullable
@Override
protected final AlgorithmParameters engineGetParameters() {
if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(mIv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain AES AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize AES AlgorithmParameters with an IV",
e);
}
}
return null;
}
}

View File

@@ -446,13 +446,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
@UnsupportedAppUsage
@Deprecated
public int getUid() {
if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
// If Keystore2 has not been enabled we have to behave as if mNamespace is actually
// a UID, because we are still being used with the old Keystore SPI.
// TODO This if statement and body can be removed when the Keystore 2 migration is
// complete. b/171563717
return mNamespace;
}
try {
return KeyProperties.namespaceToLegacyUid(mNamespace);
} catch (IllegalArgumentException e) {
@@ -1021,14 +1014,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
@NonNull
@Deprecated
public Builder setUid(int uid) {
if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
// If Keystore2 has not been enabled we have to behave as if mNamespace is actually
// a UID, because we are still being used with the old Keystore SPI.
// TODO This if statement and body can be removed when the Keystore 2 migration is
// complete. b/171563717
mNamespace = uid;
return this;
}
mNamespace = KeyProperties.legacyUidToNamespace(uid);
return this;
}
@@ -1666,9 +1651,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
* Set whether this key is critical to the device encryption flow
*
* This is a special flag only available to system servers to indicate the current key
* is part of the device encryption flow.
* is part of the device encryption flow. Setting this flag causes the key to not
* be cryptographically bound to the LSKF even if the key is otherwise authentication
* bound.
*
* @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
* @hide
*/
public Builder setCriticalToDeviceEncryption(boolean critical) {

View File

@@ -24,6 +24,7 @@ import android.app.KeyguardManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.security.GateKeeper;
import android.security.keystore2.KeymasterUtils;
import java.security.Key;
import java.security.KeyStore.ProtectionParameter;

View File

@@ -1,225 +0,0 @@
/*
* 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.keystore;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import libcore.util.EmptyArray;
/**
* Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
* {@code update} and {@code finish} operations.
*
* <p>The helper abstracts away issues that need to be solved in most code that uses KeyStore's
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. Thirdly, when the input is smaller than a threshold, skipping update
* and passing input data directly to final improves performance. This threshold is configurable;
* using a threshold <= 1 causes the helper act eagerly, which may be required for some types of
* operations (e.g. ciphers).
*
* <p>The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
* conveniently implement various JCA crypto primitives.
*
* <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
* a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
* parameters to {@code update} and {@code final} operations.
*
* @hide
*/
class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer {
/**
* Bidirectional chunked data stream over a KeyStore crypto operation.
*/
interface Stream {
/**
* Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
* be reached.
*/
OperationResult update(byte[] input);
/**
* Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
* be reached.
*/
OperationResult finish(byte[] input, byte[] siganture, byte[] additionalEntropy);
}
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
// Thus, it's safer to use a much smaller upper bound.
private static final int DEFAULT_CHUNK_SIZE_MAX = 64 * 1024;
// The chunk buffer will be sent to update until its size under this threshold.
// This threshold should be <= the max input allowed for finish.
// Setting this threshold <= 1 will effectivley disable buffering between updates.
private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024;
private final Stream mKeyStoreStream;
private final int mChunkSizeMax;
private final int mChunkSizeThreshold;
private final byte[] mChunk;
private int mChunkLength = 0;
private long mConsumedInputSizeBytes;
private long mProducedOutputSizeBytes;
KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX);
}
KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) {
this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX);
}
KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold,
int chunkSizeMax) {
mKeyStoreStream = operation;
mChunkSizeMax = chunkSizeMax;
if (chunkSizeThreshold <= 0) {
mChunkSizeThreshold = 1;
} else if (chunkSizeThreshold > chunkSizeMax) {
mChunkSizeThreshold = chunkSizeMax;
} else {
mChunkSizeThreshold = chunkSizeThreshold;
}
mChunk = new byte[mChunkSizeMax];
}
public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
if (inputLength == 0 || input == null) {
// No input provided
return EmptyArray.BYTE;
}
if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) {
throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
"Input offset and length out of bounds of input array");
}
byte[] output = EmptyArray.BYTE;
while (inputLength > 0 || mChunkLength >= mChunkSizeThreshold) {
int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength,
inputLength);
inputLength -= inputConsumed;
inputOffset += inputConsumed;
mChunkLength += inputConsumed;
mConsumedInputSizeBytes += inputConsumed;
if (mChunkLength > mChunkSizeMax) {
throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH,
"Chunk size exceeded max chunk size. Max: " + mChunkSizeMax
+ " Actual: " + mChunkLength);
}
if (mChunkLength >= mChunkSizeThreshold) {
OperationResult opResult = mKeyStoreStream.update(
ArrayUtils.subarray(mChunk, 0, mChunkLength));
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
throw KeyStore.getKeyStoreException(opResult.resultCode);
}
if (opResult.inputConsumed <= 0) {
throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH,
"Keystore consumed 0 of " + mChunkLength + " bytes provided.");
} else if (opResult.inputConsumed > mChunkLength) {
throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
"Keystore consumed more input than provided. Provided: "
+ mChunkLength + ", consumed: " + opResult.inputConsumed);
}
mChunkLength -= opResult.inputConsumed;
if (mChunkLength > 0) {
// Partialy consumed, shift chunk contents
ArrayUtils.copy(mChunk, opResult.inputConsumed, mChunk, 0, mChunkLength);
}
if ((opResult.output != null) && (opResult.output.length > 0)) {
// Output was produced
mProducedOutputSizeBytes += opResult.output.length;
output = ArrayUtils.concat(output, opResult.output);
}
}
}
return output;
}
public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
byte[] output = update(input, inputOffset, inputLength);
byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength);
OperationResult opResult = mKeyStoreStream.finish(finalChunk, signature, additionalEntropy);
if (opResult == null) {
throw new KeyStoreConnectException();
} else if (opResult.resultCode != KeyStore.NO_ERROR) {
throw KeyStore.getKeyStoreException(opResult.resultCode);
}
// If no error, assume all input consumed
mConsumedInputSizeBytes += finalChunk.length;
if ((opResult.output != null) && (opResult.output.length > 0)) {
mProducedOutputSizeBytes += opResult.output.length;
output = ArrayUtils.concat(output, opResult.output);
}
return output;
}
@Override
public long getConsumedInputSizeBytes() {
return mConsumedInputSizeBytes;
}
@Override
public long getProducedOutputSizeBytes() {
return mProducedOutputSizeBytes;
}
/**
* Main data stream via a KeyStore streaming operation.
*
* <p>For example, for an encryption operation, this is the stream through which plaintext is
* provided and ciphertext is obtained.
*/
public static class MainDataStream implements Stream {
private final KeyStore mKeyStore;
private final IBinder mOperationToken;
public MainDataStream(KeyStore keyStore, IBinder operationToken) {
mKeyStore = keyStore;
mOperationToken = operationToken;
}
@Override
public OperationResult update(byte[] input) {
return mKeyStore.update(mOperationToken, null, input);
}
@Override
public OperationResult finish(byte[] input, byte[] signature, byte[] additionalEntropy) {
return mKeyStore.finish(mOperationToken, null, input, signature, additionalEntropy);
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* 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.keystore;
import android.security.KeyStore;
import android.security.KeyStoreException;
/**
* Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
* {@code update} and {@code finish} operations.
*
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
* conveniently implement various JCA crypto primitives.
*
* @hide
*/
interface KeyStoreCryptoOperationStreamer {
byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
byte[] additionalEntropy) throws KeyStoreException;
long getConsumedInputSizeBytes();
long getProducedOutputSizeBytes();
}

View File

@@ -1,118 +0,0 @@
/*
* 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.keystore;
import android.security.KeyStore;
import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
/**
* Assorted utility methods for implementing crypto operations on top of KeyStore.
*
* @hide
*/
abstract class KeyStoreCryptoOperationUtils {
private static volatile SecureRandom sRng;
private KeyStoreCryptoOperationUtils() {}
/**
* Returns the {@link InvalidKeyException} to be thrown by the {@code init} method of
* the crypto operation in response to {@code KeyStore.begin} operation or {@code null} if
* the {@code init} method should succeed.
*/
static InvalidKeyException getInvalidKeyExceptionForInit(
KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) {
if (beginOpResultCode == KeyStore.NO_ERROR) {
return null;
}
// An error occurred. However, some errors should not lead to init throwing an exception.
// See below.
InvalidKeyException e =
keyStore.getInvalidKeyException(key.getAlias(), key.getUid(), beginOpResultCode);
switch (beginOpResultCode) {
case KeyStore.OP_AUTH_NEEDED:
// Operation needs to be authorized by authenticating the user. Don't throw an
// exception is such authentication is possible for this key
// (UserNotAuthenticatedException). An example of when it's not possible is where
// the key is permanently invalidated (KeyPermanentlyInvalidatedException).
if (e instanceof UserNotAuthenticatedException) {
return null;
}
break;
}
return e;
}
/**
* Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation
* in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method
* should succeed.
*/
public static GeneralSecurityException getExceptionForCipherInit(
KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) {
if (beginOpResultCode == KeyStore.NO_ERROR) {
return null;
}
// Cipher-specific cases
switch (beginOpResultCode) {
case KeymasterDefs.KM_ERROR_INVALID_NONCE:
return new InvalidAlgorithmParameterException("Invalid IV");
case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED:
return new InvalidAlgorithmParameterException("Caller-provided IV not permitted");
}
// General cases
return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode);
}
/**
* Returns the requested number of random bytes to mix into keystore/keymaster RNG.
*
* @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default
* RNG.
*/
static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) {
if (sizeBytes <= 0) {
return EmptyArray.BYTE;
}
if (rng == null) {
rng = getRng();
}
byte[] result = new byte[sizeBytes];
rng.nextBytes(result);
return result;
}
private static SecureRandom getRng() {
// IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is
// required to be thread-safe.
if (sRng == null) {
sRng = new SecureRandom();
}
return sRng;
}
}

View File

@@ -1,241 +0,0 @@
/*
* 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.keystore;
import android.hardware.biometrics.BiometricManager;
import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.ProviderException;
import java.util.ArrayList;
import java.util.List;
/**
* @hide
*/
public abstract class KeymasterUtils {
private KeymasterUtils() {}
public static int getDigestOutputSizeBits(int keymasterDigest) {
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_NONE:
return -1;
case KeymasterDefs.KM_DIGEST_MD5:
return 128;
case KeymasterDefs.KM_DIGEST_SHA1:
return 160;
case KeymasterDefs.KM_DIGEST_SHA_2_224:
return 224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return 256;
case KeymasterDefs.KM_DIGEST_SHA_2_384:
return 384;
case KeymasterDefs.KM_DIGEST_SHA_2_512:
return 512;
default:
throw new IllegalArgumentException("Unknown digest: " + keymasterDigest);
}
}
public static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
int keymasterBlockMode) {
switch (keymasterBlockMode) {
case KeymasterDefs.KM_MODE_ECB:
return false;
case KeymasterDefs.KM_MODE_CBC:
case KeymasterDefs.KM_MODE_CTR:
case KeymasterDefs.KM_MODE_GCM:
return true;
default:
throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode);
}
}
public static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
int keymasterPadding) {
switch (keymasterPadding) {
case KeymasterDefs.KM_PAD_NONE:
return false;
case KeymasterDefs.KM_PAD_RSA_OAEP:
case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
return true;
default:
throw new IllegalArgumentException(
"Unsupported asymmetric encryption padding scheme: " + keymasterPadding);
}
}
private static void addSids(KeymasterArguments args, UserAuthArgs spec) {
// If both biometric and credential are accepted, then just use the root sid from gatekeeper
if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG
| KeyProperties.AUTH_DEVICE_CREDENTIAL)) {
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(spec.getBoundToSpecificSecureUserId()));
} else {
// The key is authorized for use for the specified amount of time after the user has
// authenticated. Whatever unlocks the secure lock screen should authorize this key.
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(getRootSid()));
}
} else {
List<Long> sids = new ArrayList<>();
if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) {
final BiometricManager bm = KeyStore.getApplicationContext()
.getSystemService(BiometricManager.class);
// TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
// needed here.
final long[] biometricSids = bm.getAuthenticatorIds();
if (biometricSids.length == 0) {
throw new IllegalStateException(
"At least one biometric must be enrolled to create keys requiring user"
+ " authentication for every use");
}
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
sids.add(spec.getBoundToSpecificSecureUserId());
} else if (spec.isInvalidatedByBiometricEnrollment()) {
// The biometric-only SIDs will change on biometric enrollment or removal of all
// enrolled templates, invalidating the key.
for (long sid : biometricSids) {
sids.add(sid);
}
} else {
// The root SID will *not* change on fingerprint enrollment, or removal of all
// enrolled fingerprints, allowing the key to remain valid.
sids.add(getRootSid());
}
} else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL)
!= 0) {
sids.add(getRootSid());
} else {
throw new IllegalStateException("Invalid or no authentication type specified.");
}
for (int i = 0; i < sids.size(); i++) {
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(sids.get(i)));
}
}
}
/**
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
*
* @param args The arguments sent to keymaster that need to be populated from the spec
* @param spec The user authentication relevant portions of the spec passed in from the caller.
* This spec will be translated into the relevant keymaster tags to be loaded into args.
* @throws IllegalStateException if user authentication is required but the system is in a wrong
* state (e.g., secure lock screen not set up) for generating or importing keys that
* require user authentication.
*/
public static void addUserAuthArgs(KeymasterArguments args, UserAuthArgs spec) {
if (spec.isUserConfirmationRequired()) {
args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED);
}
if (spec.isUserPresenceRequired()) {
args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
}
if (spec.isUnlockedDeviceRequired()) {
args.addBoolean(KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED);
}
if (!spec.isUserAuthenticationRequired()) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return;
}
if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
// Every use of this key needs to be authorized by the user.
addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException("Key validity extension while device is on-body is not "
+ "supported for keys requiring fingerprint authentication");
}
} else {
addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
if (spec.isUserAuthenticationValidWhileOnBody()) {
args.addBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
}
}
}
/**
* Adds {@code KM_TAG_MIN_MAC_LENGTH} tag, if necessary, to the keymaster arguments for
* generating or importing a key. This tag may only be needed for symmetric keys (e.g., HMAC,
* AES-GCM).
*/
public static void addMinMacLengthAuthorizationIfNecessary(KeymasterArguments args,
int keymasterAlgorithm,
int[] keymasterBlockModes,
int[] keymasterDigests) {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_AES:
if (com.android.internal.util.ArrayUtils.contains(
keymasterBlockModes, KeymasterDefs.KM_MODE_GCM)) {
// AES GCM key needs the minimum length of AEAD tag specified.
args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
.MIN_SUPPORTED_TAG_LENGTH_BITS);
}
break;
case KeymasterDefs.KM_ALGORITHM_HMAC:
// HMAC key needs the minimum length of MAC set to the output size of the associated
// digest. This is because we do not offer a way to generate shorter MACs and
// don't offer a way to verify MACs (other than by generating them).
if (keymasterDigests.length != 1) {
throw new ProviderException(
"Unsupported number of authorized digests for HMAC key: "
+ keymasterDigests.length
+ ". Exactly one digest must be authorized");
}
int keymasterDigest = keymasterDigests[0];
int digestOutputSizeBits = getDigestOutputSizeBits(keymasterDigest);
if (digestOutputSizeBits == -1) {
throw new ProviderException(
"HMAC key authorized for unsupported digest: "
+ KeyProperties.Digest.fromKeymaster(keymasterDigest));
}
args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits);
break;
}
}
private static long getRootSid() {
long rootSid = GateKeeper.getSecureUserId();
if (rootSid == 0) {
throw new IllegalStateException("Secure lock screen must be enabled"
+ " to create keys requiring user authentication");
}
return rootSid;
}
}

View File

@@ -16,8 +16,8 @@
package android.security.keystore;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterDefs;
import java.security.ProviderException;
@@ -31,7 +31,7 @@ public class SecureKeyImportUnavailableException extends ProviderException {
}
public SecureKeyImportUnavailableException(String message) {
super(message, new KeyStoreException(KeyStore.HARDWARE_TYPE_UNAVAILABLE,
super(message, new KeyStoreException(KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE,
"Secure Key Import not available"));
}

View File

@@ -16,8 +16,8 @@
package android.security.keystore;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterDefs;
import java.security.ProviderException;
@@ -33,7 +33,8 @@ public class StrongBoxUnavailableException extends ProviderException {
public StrongBoxUnavailableException(String message) {
super(message,
new KeyStoreException(KeyStore.HARDWARE_TYPE_UNAVAILABLE, "No StrongBox available")
new KeyStoreException(KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE,
"No StrongBox available")
);
}

View File

@@ -21,7 +21,6 @@ import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyStoreCryptoOperation;
import android.security.keystore.KeymasterUtils;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;

View File

@@ -20,12 +20,10 @@ import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
@@ -259,7 +257,7 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
// 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(), spec);
KeyStore2ParameterUtils.addUserAuthArgs(new ArrayList<>(), spec);
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}

View File

@@ -18,6 +18,7 @@ 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.KeyParameter;
import android.hardware.security.keymint.KeyPurpose;
@@ -28,7 +29,6 @@ import android.os.RemoteException;
import android.security.GenerateRkpKey;
import android.security.GenerateRkpKeyException;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
@@ -39,7 +39,6 @@ import android.security.keystore.AttestationUtils;
import android.security.keystore.DeviceIdAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Authorization;
@@ -270,7 +269,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
// 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);
KeyStore2ParameterUtils.addUserAuthArgs(new ArrayList<>(), mSpec);
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -572,7 +571,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
GenerateRkpKey keyGen = new GenerateRkpKey(KeyStore.getApplicationContext());
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
if (mSpec.getAttestationChallenge() != null) {
keyGen.notifyKeyGenerated(securityLevel);
@@ -589,7 +589,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
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(KeyStore.getApplicationContext());
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
keyGen.notifyEmpty(securityLevel);
} catch (RemoteException f) {
@@ -665,8 +666,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
|| idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
telephonyService =
(TelephonyManager) KeyStore.getApplicationContext().getSystemService(
Context.TELEPHONY_SERVICE);
(TelephonyManager) android.app.AppGlobals.getInitialApplication()
.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyService == null) {
throw new DeviceIdAttestationException("Unable to access telephony service");
}

View File

@@ -110,23 +110,6 @@ public class AndroidKeyStoreProvider extends Provider {
putSecretKeyFactoryImpl("HmacSHA512");
}
private static boolean sInstalled = false;
/**
* This function indicates whether or not this provider was installed. This is manly used
* as indicator for
* {@link android.security.keystore.AndroidKeyStoreProvider#getKeyStoreForUid(int)}
* to whether or not to retrieve the Keystore provider by "AndroidKeyStoreLegacy".
* This function can be removed once the transition to Keystore 2.0 is complete.
* b/171305684
*
* @return true if this provider was installed.
* @hide
*/
public static boolean isInstalled() {
return sInstalled;
}
/**
* Installs a new instance of this provider (and the
* {@link AndroidKeyStoreBCWorkaroundProvider}).
@@ -142,7 +125,6 @@ public class AndroidKeyStoreProvider extends Provider {
break;
}
}
sInstalled = true;
Security.addProvider(new AndroidKeyStoreProvider());
Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();

View File

@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.hardware.security.keymint.KeyParameter;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.system.keystore2.Authorization;
import java.security.AlgorithmParameters;

View File

@@ -30,7 +30,6 @@ import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.KeymasterUtils;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.WrappedKeyEntry;
import android.system.keystore2.AuthenticatorSpec;

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2021 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.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import java.security.ProviderException;
/**
* @hide
*/
public abstract class KeymasterUtils {
private KeymasterUtils() {}
/** @hide */
static int getDigestOutputSizeBits(int keymasterDigest) {
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_NONE:
return -1;
case KeymasterDefs.KM_DIGEST_MD5:
return 128;
case KeymasterDefs.KM_DIGEST_SHA1:
return 160;
case KeymasterDefs.KM_DIGEST_SHA_2_224:
return 224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return 256;
case KeymasterDefs.KM_DIGEST_SHA_2_384:
return 384;
case KeymasterDefs.KM_DIGEST_SHA_2_512:
return 512;
default:
throw new IllegalArgumentException("Unknown digest: " + keymasterDigest);
}
}
/** @hide */
static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
int keymasterBlockMode) {
switch (keymasterBlockMode) {
case KeymasterDefs.KM_MODE_ECB:
return false;
case KeymasterDefs.KM_MODE_CBC:
case KeymasterDefs.KM_MODE_CTR:
case KeymasterDefs.KM_MODE_GCM:
return true;
default:
throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode);
}
}
/** @hide */
static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
int keymasterPadding) {
switch (keymasterPadding) {
case KeymasterDefs.KM_PAD_NONE:
return false;
case KeymasterDefs.KM_PAD_RSA_OAEP:
case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
return true;
default:
throw new IllegalArgumentException(
"Unsupported asymmetric encryption padding scheme: " + keymasterPadding);
}
}
/**
* Adds {@code KM_TAG_MIN_MAC_LENGTH} tag, if necessary, to the keymaster arguments for
* generating or importing a key. This tag may only be needed for symmetric keys (e.g., HMAC,
* AES-GCM).
*/
public static void addMinMacLengthAuthorizationIfNecessary(KeymasterArguments args,
int keymasterAlgorithm,
int[] keymasterBlockModes,
int[] keymasterDigests) {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_AES:
if (com.android.internal.util.ArrayUtils.contains(
keymasterBlockModes, KeymasterDefs.KM_MODE_GCM)) {
// AES GCM key needs the minimum length of AEAD tag specified.
args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
.MIN_SUPPORTED_TAG_LENGTH_BITS);
}
break;
case KeymasterDefs.KM_ALGORITHM_HMAC:
// HMAC key needs the minimum length of MAC set to the output size of the associated
// digest. This is because we do not offer a way to generate shorter MACs and
// don't offer a way to verify MACs (other than by generating them).
if (keymasterDigests.length != 1) {
throw new ProviderException(
"Unsupported number of authorized digests for HMAC key: "
+ keymasterDigests.length
+ ". Exactly one digest must be authorized");
}
int keymasterDigest = keymasterDigests[0];
int digestOutputSizeBits = getDigestOutputSizeBits(keymasterDigest);
if (digestOutputSizeBits == -1) {
throw new ProviderException(
"HMAC key authorized for unsupported digest: "
+ KeyProperties.Digest.fromKeymaster(keymasterDigest));
}
args.addUnsignedInt(KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits);
break;
}
}
}