Keystore 2.0: Remove Keystore 1.0 SPI with all remaining references
Bug: 171305684 Test: CtsKeystoreTestCases Change-Id: I337515dadc9e45b909bff058d4e13371b4fa843c
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
124
keystore/java/android/security/keystore2/KeymasterUtils.java
Normal file
124
keystore/java/android/security/keystore2/KeymasterUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user