summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2020-11-16 22:51:03 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-11-16 22:51:03 +0000
commitaeb15e8592b9a74937afbb3fa232d2ced5971f3c (patch)
tree1d60d433579554151324eb04664f6205ba426d53
parentfebaec9f3e70de992cc6cc72789aa95c0b80a66f (diff)
parent4392c6977ce935a084ab30baeed511f170a606d5 (diff)
Merge changes I9731d978,I9e325782,I441a4d4d,I86a85e48,I9268fd66, ...
* changes: Keystore 2.0 SPI: Install legacy Keystore provider as AndroidKeyStoreLegacy Keystore 2.0 SPI: Zygote install Keystore2 provider conditionally Keystore 2.0 SPI: Evolve the generator SPI. Keystore 2.0 SPI: Evolve Factory SPI Keystore 2.0 SPI: AndroidKeyStoreProvider loads keys from Keystore 2.0 Keystore 2.0 SPI: Evolve the Crypto SPI. Keystore 2.0 SPI: KeyParameter utilities. Keystore 2.0 SPI: Update the chunked streamer. Keystore 2.0 SPI: KeyStoreCryptoOperationUtils Keystore 2.0 SPI: KeyStoreKeys adopt Keystore 2.0 Keystore 2.0: Shim around the basic functionality of Keystore 2.0 Keystore 2.0 SPI: Duplicate Keystore SPI to android.security.keystore2 package
-rw-r--r--api/current.txt6
-rw-r--r--core/api/current.txt6
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java13
-rw-r--r--keystore/java/android/security/CheckedRemoteRequest.java33
-rw-r--r--keystore/java/android/security/KeyStore2.java277
-rw-r--r--keystore/java/android/security/KeyStoreOperation.java141
-rw-r--r--keystore/java/android/security/KeyStoreSecurityLevel.java217
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java12
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreProvider.java16
-rw-r--r--keystore/java/android/security/keystore/BackendBusyException.java52
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java317
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java439
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java273
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java905
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java207
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java50
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java74
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java259
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKey.java141
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java144
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java428
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java804
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java41
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java39
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java415
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java81
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java525
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java51
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java71
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java172
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java38
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java272
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java423
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java1167
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java335
-rw-r--r--keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java313
-rw-r--r--keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java229
-rw-r--r--keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java42
-rw-r--r--keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java211
39 files changed, 9232 insertions, 7 deletions
diff --git a/api/current.txt b/api/current.txt
index 69f76451a5c4..6632db3d7acf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -42742,6 +42742,12 @@ package android.security.identity {
package android.security.keystore {
+ public class BackendBusyException extends java.security.ProviderException {
+ ctor public BackendBusyException();
+ ctor public BackendBusyException(@NonNull String);
+ ctor public BackendBusyException(@NonNull String, @NonNull Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(String);
diff --git a/core/api/current.txt b/core/api/current.txt
index 055f909077f7..02652b2c103c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40910,6 +40910,12 @@ package android.security.identity {
package android.security.keystore {
+ public class BackendBusyException extends java.security.ProviderException {
+ ctor public BackendBusyException();
+ ctor public BackendBusyException(@NonNull String);
+ ctor public BackendBusyException(@NonNull String, @NonNull Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(String);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index aa37334b2c54..6a67670d8160 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -73,6 +73,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Provider;
import java.security.Security;
+import java.util.Optional;
/**
* Startup class for the zygote process.
@@ -225,7 +226,17 @@ public class ZygoteInit {
// AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert
// preferred providers. Note this is not done via security.properties as the JCA providers
// are not on the classpath in the case of, for example, raw dalvikvm runtimes.
- AndroidKeyStoreProvider.install();
+ // TODO b/171305684 This code is used to conditionally enable the installation of the
+ // Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own
+ // pace. This code will be removed when all calling code was adjusted to
+ // Keystore 2.0.
+ Optional<Boolean> keystore2_enabled =
+ android.sysprop.Keystore2Properties.keystore2_enabled();
+ if (keystore2_enabled.isPresent() && keystore2_enabled.get()) {
+ android.security.keystore2.AndroidKeyStoreProvider.install();
+ } else {
+ AndroidKeyStoreProvider.install();
+ }
Log.i(TAG, "Installed AndroidKeyStoreProvider in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
diff --git a/keystore/java/android/security/CheckedRemoteRequest.java b/keystore/java/android/security/CheckedRemoteRequest.java
new file mode 100644
index 000000000000..b6e7c1fa61b9
--- /dev/null
+++ b/keystore/java/android/security/CheckedRemoteRequest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.os.RemoteException;
+
+/**
+ * This is a Producer of {@code R} that is expected to throw a {@link RemoteException}.
+ *
+ * It is used by Keystore2 service wrappers to handle and convert {@link RemoteException}
+ * and {@link android.os.ServiceSpecificException} into {@link KeyStoreException}.
+ *
+ * @hide
+ * @param <R>
+ */
+@FunctionalInterface
+interface CheckedRemoteRequest<R> {
+ R execute() throws RemoteException;
+}
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
new file mode 100644
index 000000000000..92d87aa0fed6
--- /dev/null
+++ b/keystore/java/android/security/KeyStore2.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.system.keystore2.IKeystoreService;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
+import android.system.keystore2.ResponseCode;
+import android.util.Log;
+
+import java.util.Calendar;
+
+/**
+ * @hide This should not be made public in its present form because it
+ * assumes that private and secret key bytes are available and would
+ * preclude the use of hardware crypto.
+ */
+public class KeyStore2 {
+ private static final String TAG = "KeyStore";
+
+ private static final int RECOVERY_GRACE_PERIOD_MS = 50;
+
+ /**
+ * Keystore operation creation may fail
+ *
+ * Keystore used to work under the assumption that the creation of cryptographic operations
+ * always succeeds. However, the KeyMint backend has only a limited number of operation slots.
+ * In order to keep up the appearance of "infinite" operation slots, the Keystore daemon
+ * would prune least recently used operations if there is no available operation slot.
+ * As a result, good operations could be terminated prematurely.
+ *
+ * This opens AndroidKeystore up to denial-of-service and unintended livelock situations.
+ * E.g.: if multiple apps wake up at the same time, e.g., due to power management optimizations,
+ * and attempt to perform crypto operations, they start terminating each others operations
+ * without making any progress.
+ *
+ * To break out of livelocks and to discourage DoS attempts we have changed the pruning
+ * strategy such that it prefers clients that use few operation slots and only briefly.
+ * As a result we can, almost, guarantee that single operations that don't linger inactive
+ * for more than 5 seconds will conclude unhampered by the pruning strategy. "Almost",
+ * because there are operations related to file system encryption that can prune even
+ * these operations, but those are extremely rare.
+ *
+ * As a side effect of this new pruning strategy operation creation can now fail if the
+ * client has a lower pruning power than all of the existing operations.
+ *
+ * Pruning strategy
+ *
+ * To find a suitable candidate we compute the malus for the caller and each existing
+ * operation. The malus is the inverse of the pruning power (caller) or pruning
+ * resistance (existing operation). For the caller to be able to prune an operation it must
+ * find an operation with a malus higher than its own.
+ *
+ * For more detail on the pruning strategy consult the implementation at
+ * https://android.googlesource.com/platform/system/security/+/refs/heads/master/keystore2/src/operation.rs
+ *
+ * For older SDK version, KeyStore2 will poll the Keystore daemon for a free operation
+ * slot. So to applications, targeting earlier SDK versions, it will still look like cipher and
+ * signature object initialization always succeeds, however, it may take longer to get an
+ * operation.
+ *
+ * All SDK version benefit from fairer operation slot scheduling and a better chance to
+ * successfully conclude an operation.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L;
+
+ // Never use mBinder directly, use KeyStore2.getService() instead or better yet
+ // handleRemoteExceptionWithRetry which retries connecting to Keystore once in case
+ // of a remote exception.
+ private IKeystoreService mBinder;
+
+
+ @FunctionalInterface
+ interface CheckedRemoteRequest<R> {
+ R execute(IKeystoreService service) throws RemoteException;
+ }
+
+ private <R> R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest<R> request)
+ throws KeyStoreException {
+ IKeystoreService service = getService(false /* retryLookup */);
+ boolean firstTry = true;
+ while (true) {
+ try {
+ return request.execute(service);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "KeyStore exception", e);
+ throw new KeyStoreException(e.errorCode, "");
+ } catch (RemoteException e) {
+ if (firstTry) {
+ Log.w(TAG, "Looks like we may have lost connection to the Keystore "
+ + "daemon.");
+ Log.w(TAG, "Retrying after giving Keystore "
+ + RECOVERY_GRACE_PERIOD_MS + "ms to recover.");
+ interruptedPreservingSleep(RECOVERY_GRACE_PERIOD_MS);
+ service = getService(true /* retry Lookup */);
+ firstTry = false;
+ } else {
+ Log.e(TAG, "Cannot connect to Keystore daemon.", e);
+ throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, "");
+ }
+ }
+ }
+ }
+
+
+ private KeyStore2() {
+ mBinder = null;
+ }
+
+ public static KeyStore2 getInstance() {
+ return new KeyStore2();
+ }
+
+ private synchronized IKeystoreService getService(boolean retryLookup) {
+ if (mBinder == null || retryLookup) {
+ mBinder = IKeystoreService.Stub.asInterface(ServiceManager
+ .getService("android.system.keystore2"));
+ }
+ return mBinder;
+ }
+
+ void delete(KeyDescriptor descriptor) throws KeyStoreException {
+ handleRemoteExceptionWithRetry((service) -> {
+ service.deleteKey(descriptor);
+ return 0;
+ });
+ }
+
+ /**
+ * List all entries in the keystore for in the given namespace.
+ */
+ public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException {
+ return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
+ }
+
+ /**
+ * Create a grant that allows the grantee identified by {@code granteeUid} to use
+ * the key specified by {@code descriptor} withint the restrictions given by
+ * {@code accessVectore}.
+ * @see IKeystoreService#grant(KeyDescriptor, int, int) for more details.
+ * @param descriptor
+ * @param granteeUid
+ * @param accessVector
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector)
+ throws KeyStoreException {
+ return handleRemoteExceptionWithRetry(
+ (service) -> service.grant(descriptor, granteeUid, accessVector)
+ );
+ }
+
+ /**
+ * Destroys a grant.
+ * @see IKeystoreService#ungrant(KeyDescriptor, int) for more details.
+ * @param descriptor
+ * @param granteeUid
+ * @throws KeyStoreException
+ * @hide
+ */
+ public void ungrant(KeyDescriptor descriptor, int granteeUid)
+ throws KeyStoreException {
+ handleRemoteExceptionWithRetry((service) -> {
+ service.ungrant(descriptor, granteeUid);
+ return 0;
+ });
+ }
+
+ /**
+ * Retrieves a key entry from the keystore backend.
+ * @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details.
+ * @param descriptor
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)
+ throws KeyStoreException {
+ return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
+ }
+
+ /**
+ * Get the security level specific keystore interface from the keystore daemon.
+ * @see IKeystoreService#getSecurityLevel(int) for more details.
+ * @param securityLevel
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyStoreSecurityLevel getSecurityLevel(int securityLevel)
+ throws KeyStoreException {
+ return handleRemoteExceptionWithRetry((service) ->
+ new KeyStoreSecurityLevel(
+ service.getSecurityLevel(securityLevel)
+ )
+ );
+ }
+
+ /**
+ * Update the subcomponents of a key entry designated by the key descriptor.
+ * @see IKeystoreService#updateSubcomponent(KeyDescriptor, byte[], byte[]) for more details.
+ * @param key
+ * @param publicCert
+ * @param publicCertChain
+ * @throws KeyStoreException
+ * @hide
+ */
+ public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert,
+ byte[] publicCertChain) throws KeyStoreException {
+ handleRemoteExceptionWithRetry((service) -> {
+ service.updateSubcomponent(key, publicCert, publicCertChain);
+ return 0;
+ });
+ }
+
+ /**
+ * Delete the key designed by the key descriptor.
+ * @see IKeystoreService#deleteKey(KeyDescriptor) for more details.
+ * @param descriptor
+ * @throws KeyStoreException
+ * @hide
+ */
+ public void deleteKey(@NonNull KeyDescriptor descriptor)
+ throws KeyStoreException {
+ handleRemoteExceptionWithRetry((service) -> {
+ service.deleteKey(descriptor);
+ return 0;
+ });
+ }
+
+ protected static void interruptedPreservingSleep(long millis) {
+ boolean wasInterrupted = false;
+ Calendar calendar = Calendar.getInstance();
+ long target = calendar.getTimeInMillis() + millis;
+ while (true) {
+ try {
+ Thread.sleep(target - calendar.getTimeInMillis());
+ break;
+ } catch (InterruptedException e) {
+ wasInterrupted = true;
+ } catch (IllegalArgumentException e) {
+ // This means that the argument to sleep was negative.
+ // So we are done sleeping.
+ break;
+ }
+ }
+ if (wasInterrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java
new file mode 100644
index 000000000000..9af15a5f4a16
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreOperation.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.security.keymaster.KeymasterDefs;
+import android.system.keystore2.IKeystoreOperation;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.ResponseCode;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class KeyStoreOperation {
+ static final String TAG = "KeyStoreOperation";
+ private final IKeystoreOperation mOperation;
+ private final Long mChallenge;
+ private final KeyParameter[] mParameters;
+
+ public KeyStoreOperation(
+ @NonNull IKeystoreOperation operation,
+ Long challenge,
+ KeyParameter[] parameters
+ ) {
+ this.mOperation = operation;
+ this.mChallenge = challenge;
+ this.mParameters = parameters;
+ }
+
+ /**
+ * Gets the challenge associated with this operation.
+ * @return null if the operation does not required authorization. A 64bit operation
+ * challenge otherwise.
+ */
+ public Long getChallenge() {
+ return mChallenge;
+ }
+
+ /**
+ * Gets the parameters associated with this operation.
+ * @return
+ */
+ public KeyParameter[] getParameters() {
+ return mParameters;
+ }
+
+ private <R> R handleExceptions(@NonNull CheckedRemoteRequest<R> request)
+ throws KeyStoreException {
+ try {
+ return request.execute();
+ } catch (ServiceSpecificException e) {
+ switch(e.errorCode) {
+ case ResponseCode.OPERATION_BUSY: {
+ throw new IllegalThreadStateException(
+ "Cannot update the same operation concurrently."
+ );
+ }
+ default:
+ // TODO Human readable string. Use something like KeyStore.getKeyStoreException
+ throw new KeyStoreException(e.errorCode, "");
+ }
+ } catch (RemoteException e) {
+ // Log exception and report invalid operation handle.
+ // This should prompt the caller drop the reference to this operation and retry.
+ Log.e(
+ TAG,
+ "Remote exception while advancing a KeyStoreOperation.",
+ e
+ );
+ throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, "");
+ }
+ }
+
+ /**
+ * Updates the Keystore operation represented by this object with more associated data.
+ * @see IKeystoreOperation#updateAad(byte[]) for more details.
+ * @param input
+ * @throws KeyStoreException
+ */
+ public void updateAad(@NonNull byte[] input) throws KeyStoreException {
+ handleExceptions(() -> {
+ mOperation.updateAad(input);
+ return 0;
+ });
+ }
+
+ /**
+ * Updates the Keystore operation represented by this object.
+ * @see IKeystoreOperation#update(byte[]) for more details.
+ * @param input
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public byte[] update(@NonNull byte[] input) throws KeyStoreException {
+ return handleExceptions(() -> mOperation.update(input));
+ }
+
+ /**
+ * Finalizes the Keystore operation represented by this object.
+ * @see IKeystoreOperation#finish(byte[], byte[]) for more details.
+ * @param input
+ * @param signature
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException {
+ return handleExceptions(() -> mOperation.finish(input, signature));
+ }
+
+ /**
+ * Aborts the Keystore operation represented by this object.
+ * @see IKeystoreOperation#abort() for more details.
+ * @throws KeyStoreException
+ * @hide
+ */
+ public void abort() throws KeyStoreException {
+ handleExceptions(() -> {
+ mOperation.abort();
+ return 0;
+ });
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
new file mode 100644
index 000000000000..9d3b62278ba0
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.security.keystore.BackendBusyException;
+import android.security.keystore.KeyStoreConnectException;
+import android.system.keystore2.AuthenticatorSpec;
+import android.system.keystore2.CreateOperationResponse;
+import android.system.keystore2.IKeystoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.ResponseCode;
+import android.util.Log;
+
+import java.util.Calendar;
+import java.util.Collection;
+
+/**
+ * This is a shim around the security level specific interface of Keystore 2.0. Services with
+ * this interface are instantiated per KeyMint backend, each having there own security level.
+ * Thus this object representation of a security level.
+ * @hide
+ */
+public class KeyStoreSecurityLevel {
+ private static final String TAG = "KeyStoreSecurityLevel";
+ private final IKeystoreSecurityLevel mSecurityLevel;
+
+ public KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel) {
+ this.mSecurityLevel = securityLevel;
+ }
+
+ private <R> R handleExceptions(CheckedRemoteRequest<R> request) throws KeyStoreException {
+ try {
+ return request.execute();
+ } catch (ServiceSpecificException e) {
+ throw new KeyStoreException(e.errorCode, "");
+ } catch (RemoteException e) {
+ // Log exception and report invalid operation handle.
+ // This should prompt the caller drop the reference to this operation and retry.
+ Log.e(TAG, "Could not connect to Keystore.", e);
+ throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, "");
+ }
+ }
+
+ /**
+ * Creates a new keystore operation.
+ * @see IKeystoreSecurityLevel#createOperation(KeyDescriptor, KeyParameter[], boolean) for more
+ * details.
+ * @param keyDescriptor
+ * @param args
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor,
+ Collection<KeyParameter> args) throws KeyStoreException {
+ while (true) {
+ try {
+ CreateOperationResponse createOperationResponse =
+ mSecurityLevel.createOperation(
+ keyDescriptor,
+ args.toArray(new KeyParameter[args.size()]),
+ false /* forced */
+ );
+ Long challenge = null;
+ if (createOperationResponse.operationChallenge != null) {
+ challenge = createOperationResponse.operationChallenge.challenge;
+ }
+ KeyParameter[] parameters = null;
+ if (createOperationResponse.parameters != null) {
+ parameters = createOperationResponse.parameters.keyParameter;
+ }
+ return new KeyStoreOperation(
+ createOperationResponse.iOperation,
+ challenge,
+ parameters);
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ResponseCode.BACKEND_BUSY: {
+ if (CompatChanges.isChangeEnabled(
+ KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) {
+ // Starting with Android S we inform the caller about the
+ // backend being busy.
+ throw new BackendBusyException();
+ } else {
+ // Before Android S operation creation must always succeed. So we
+ // just have to retry. We do so with a randomized back-off between
+ // 50 and 250ms.
+ // It is a little awkward that we cannot break out of this loop
+ // by interrupting this thread. But that is the expected behavior.
+ // There is some comfort in the fact that interrupting a thread
+ // also does not unblock a thread waiting for a binder transaction.
+ interruptedPreservingSleep((long) (Math.random() * 200 + 50));
+ }
+ break;
+ }
+ default:
+ throw new KeyStoreException(e.errorCode, "");
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ throw new KeyStoreConnectException();
+ }
+ }
+ }
+
+ /**
+ * Generates a new key in Keystore.
+ * @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
+ * byte[]) for more details.
+ * @param descriptor
+ * @param attestationKey
+ * @param args
+ * @param flags
+ * @param entropy
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
+ Collection<KeyParameter> args, int flags, byte[] entropy)
+ throws KeyStoreException {
+ return handleExceptions(() -> mSecurityLevel.generateKey(
+ descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
+ flags, entropy));
+ }
+
+ /**
+ * Imports a key into Keystore.
+ * @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
+ * byte[]) for more details.
+ * @param descriptor
+ * @param attestationKey
+ * @param args
+ * @param flags
+ * @param keyData
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
+ Collection<KeyParameter> args, int flags, byte[] keyData)
+ throws KeyStoreException {
+ return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
+ args.toArray(new KeyParameter[args.size()]), flags, keyData));
+ }
+
+ /**
+ * Imports a wrapped key into Keystore.
+ * @see IKeystoreSecurityLevel#importWrappedKey(KeyDescriptor, KeyDescriptor, byte[],
+ * KeyParameter[], AuthenticatorSpec[]) for more details.
+ * @param wrappedKeyDescriptor
+ * @param wrappingKeyDescriptor
+ * @param wrappedKey
+ * @param maskingKey
+ * @param args
+ * @param authenticatorSpecs
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public KeyMetadata importWrappedKey(@NonNull KeyDescriptor wrappedKeyDescriptor,
+ @NonNull KeyDescriptor wrappingKeyDescriptor,
+ @NonNull byte[] wrappedKey, byte[] maskingKey,
+ Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)
+ throws KeyStoreException {
+ KeyDescriptor keyDescriptor = new KeyDescriptor();
+ keyDescriptor.alias = wrappedKeyDescriptor.alias;
+ keyDescriptor.nspace = wrappedKeyDescriptor.nspace;
+ keyDescriptor.blob = wrappedKey;
+ keyDescriptor.domain = wrappedKeyDescriptor.domain;
+
+ return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor,
+ wrappingKeyDescriptor, maskingKey,
+ args.toArray(new KeyParameter[args.size()]), authenticatorSpecs));
+ }
+
+ protected static void interruptedPreservingSleep(long millis) {
+ boolean wasInterrupted = false;
+ Calendar calendar = Calendar.getInstance();
+ long target = calendar.getTimeInMillis() + millis;
+ while (true) {
+ try {
+ Thread.sleep(target - calendar.getTimeInMillis());
+ break;
+ } catch (InterruptedException e) {
+ wasInterrupted = true;
+ } catch (IllegalArgumentException e) {
+ // This means that the argument to sleep was negative.
+ // So we are done sleeping.
+ break;
+ }
+ }
+ if (wasInterrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index 624321cbf5ea..5730234184ab 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -34,7 +34,7 @@ import java.security.Provider;
*
* @hide
*/
-class AndroidKeyStoreBCWorkaroundProvider extends Provider {
+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
@@ -50,8 +50,14 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede";
- AndroidKeyStoreBCWorkaroundProvider() {
- super("AndroidKeyStoreBCWorkaround",
+ /** @hide */
+ public AndroidKeyStoreBCWorkaroundProvider() {
+ this("AndroidKeyStoreBCWorkaround");
+ }
+
+ /** @hide **/
+ public AndroidKeyStoreBCWorkaroundProvider(String providerName) {
+ super(providerName,
1.0,
"Android KeyStore security provider to work around Bouncy Castle");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index d1b4464c1aed..3ac9d68d5a9f 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -71,14 +71,20 @@ public class AndroidKeyStoreProvider extends Provider {
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
- /** @hide **/
+ /** @hide */
public AndroidKeyStoreProvider() {
- super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
+ 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.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
+ put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
@@ -438,8 +444,12 @@ public class AndroidKeyStoreProvider extends Provider {
@NonNull
public static java.security.KeyStore getKeyStoreForUid(int uid)
throws KeyStoreException, NoSuchProviderException {
+ String providerName = PROVIDER_NAME;
+ if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
+ providerName = "AndroidKeyStoreLegacy";
+ }
java.security.KeyStore result =
- java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME);
+ java.security.KeyStore.getInstance(providerName);
try {
result.load(new AndroidKeyStoreLoadStoreParameter(uid));
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {
diff --git a/keystore/java/android/security/keystore/BackendBusyException.java b/keystore/java/android/security/keystore/BackendBusyException.java
new file mode 100644
index 000000000000..1a88469d7e54
--- /dev/null
+++ b/keystore/java/android/security/keystore/BackendBusyException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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 java.security.ProviderException;
+
+/**
+ * Indicates a transient error that prevented a key operation from being created.
+ * Callers should try again with a back-off period of 10-30 milliseconds.
+ */
+public class BackendBusyException extends ProviderException {
+
+ /**
+ * Constructs a new {@code BackendBusyException} without detail message and cause.
+ */
+ public BackendBusyException() {
+ super("The keystore backend has no operation slots available. Retry later.");
+ }
+
+ /**
+ * Constructs a new {@code BackendBusyException} with the provided detail message and
+ * no cause.
+ */
+ public BackendBusyException(@NonNull String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code BackendBusyException} with the provided detail message and
+ * cause.
+ */
+ public BackendBusyException(@NonNull String message, @NonNull Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java
new file mode 100644
index 000000000000..70713a47ad6d
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java
@@ -0,0 +1,317 @@
+/*
+ * 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.keystore2;
+
+import android.annotation.NonNull;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.KeyParameter;
+
+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 java.util.List;
+
+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(@NonNull List<KeyParameter> parameters) {
+ 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.");
+ }
+
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM,
+ KeymasterDefs.KM_ALGORITHM_3DES
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE,
+ mKeymasterBlockMode
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ mKeymasterPadding
+ ));
+
+ if (mIvRequired && (mIv != null)) {
+ parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv));
+ }
+ }
+
+ @Override
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = null;
+ if (parameters != null) {
+ for (KeyParameter p : parameters) {
+ if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
+ returnedIv = p.blob;
+ break;
+ }
+ }
+ }
+
+ 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();
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java
new file mode 100644
index 000000000000..dd094b7a5fd0
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+import android.security.keystore.KeyProperties;
+import android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.Stream;
+import android.system.keystore2.KeyParameter;
+
+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 java.util.List;
+
+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(
+ KeyStoreOperation operation) {
+ KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ operation), 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(
+ KeyStoreOperation operation) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new AdditionalAuthenticationDataStream(operation), 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 List<KeyParameter> parameters) {
+ super.addAlgorithmSpecificParametersToBegin(parameters);
+ parameters.add(KeyStore2ParameterUtils.makeInt(
+ 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 List<KeyParameter> parameters) {
+ 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.");
+ }
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM,
+ KeymasterDefs.KM_ALGORITHM_AES
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE,
+ mKeymasterBlockMode
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ mKeymasterPadding
+ ));
+
+ if (mIv != null) {
+ parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv));
+ }
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = null;
+ if (parameters != null) {
+ for (KeyParameter p : parameters) {
+ if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
+ returnedIv = p.blob;
+ break;
+ }
+ }
+ }
+
+ 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) throws KeyStoreException {
+ byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature);
+ 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 KeyStoreOperation mOperation;
+
+ private AdditionalAuthenticationDataStream(KeyStoreOperation operation) {
+ mOperation = operation;
+ }
+
+ @Override
+ public byte[] update(byte[] input) throws KeyStoreException {
+ mOperation.updateAad(input);
+ return null;
+ }
+
+ @Override
+ public byte[] finish(byte[] input, byte[] signature) {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java
new file mode 100644
index 000000000000..dd943d422e62
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import 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
+ */
+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.keystore2";
+ 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";
+
+ AndroidKeyStoreBCWorkaroundProvider() {
+ super("AndroidKeyStoreBCWorkaround",
+ 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"};
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
new file mode 100644
index 000000000000..b785ee5c6966
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -0,0 +1,905 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyStoreCryptoOperation;
+import android.system.keystore2.KeyParameter;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+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 static final String TAG = "AndroidKeyStoreCipherSpiBase";
+
+ // 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;
+
+ /**
+ * Object representing 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 KeyStoreOperation mOperation;
+ /**
+ * The operation challenge is required when an operation needs user authorization.
+ * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric
+ * authenticator, and included in the authentication token minted by this authenticator.
+ * It may be null, if the operation does not require authorization.
+ */
+ private long mOperationChallenge;
+ 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() {
+ mOperation = null;
+ mEncrypting = false;
+ mKeymasterPurposeOverride = -1;
+ mKey = null;
+ mRng = null;
+ mOperationChallenge = 0;
+ mMainDataStreamer = null;
+ mAdditionalAuthenticationDataStreamer = null;
+ mAdditionalAuthenticationDataStreamerClosed = false;
+ mCachedException = null;
+ }
+
+ @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;
+ }
+
+ private void abortOperation() {
+ KeyStoreCryptoOperationUtils.abortOperation(mOperation);
+ mOperation = null;
+ }
+
+ /**
+ * 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() {
+ abortOperation();
+ mEncrypting = false;
+ mKeymasterPurposeOverride = -1;
+ mKey = null;
+ mRng = null;
+ mOperationChallenge = 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() {
+ abortOperation();
+ mOperationChallenge = 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");
+ }
+
+ List<KeyParameter> parameters = new ArrayList<>();
+ addAlgorithmSpecificParametersToBegin(parameters);
+
+ int purpose;
+ if (mKeymasterPurposeOverride != -1) {
+ purpose = mKeymasterPurposeOverride;
+ } else {
+ purpose = mEncrypting
+ ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
+ }
+
+ parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
+
+ try {
+ mOperation = mKey.getSecurityLevel().createOperation(
+ mKey.getKeyIdDescriptor(),
+ parameters
+ );
+ } catch (KeyStoreException keyStoreException) {
+ GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
+ mKey, keyStoreException);
+ 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);
+ }
+ }
+ }
+
+ // Now we check if we got an operation challenge. This indicates that user authorization
+ // is required. And if we got a challenge we check if the authorization can possibly
+ // succeed.
+ mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
+ mOperation, mKey);
+
+ loadAlgorithmSpecificParametersFromBeginResult(mOperation.getParameters());
+ mMainDataStreamer = createMainDataStreamer(mOperation);
+ mAdditionalAuthenticationDataStreamer =
+ createAdditionalAuthenticationDataStreamer(mOperation);
+ 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(
+ KeyStoreOperation operation) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ operation), 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") KeyStoreOperation operation) {
+ 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
+ } 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();
+ output = mMainDataStreamer.doFinal(
+ input, inputOffset, inputLen,
+ null); // no signature involved
+ } catch (KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ 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 {
+ abortOperation();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public final long getOperationHandle() {
+ return mOperationChallenge;
+ }
+
+ 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;
+ }
+
+ 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 parameters keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected abstract void addAlgorithmSpecificParametersToBegin(
+ @NonNull List<KeyParameter> parameters);
+
+ /**
+ * 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 parameters keystore/keymaster arguments returned by KeyStore {@code createOperation}.
+ */
+ protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters);
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
new file mode 100644
index 000000000000..9f7f2383a416
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyParameter;
+
+import libcore.util.EmptyArray;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidKeyException;
+import java.security.SignatureSpi;
+import java.util.List;
+
+/**
+ * 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(
+ KeyStoreOperation operation) {
+ return new TruncateToFieldSizeMessageStreamer(
+ super.createMainDataStreamer(operation),
+ 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)
+ 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);
+ }
+
+ @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");
+ }
+
+ long keySizeBits = -1;
+ for (Authorization a : key.getAuthorizations()) {
+ if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
+ keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
+ }
+ }
+
+ 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 List<KeyParameter> parameters) {
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ 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;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java
new file mode 100644
index 000000000000..35effde6234b
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyDescriptor;
+
+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(@NonNull KeyDescriptor descriptor,
+ long keyId,
+ Authorization[] authorizations,
+ @NonNull KeyStoreSecurityLevel securityLevel,
+ @NonNull ECParameterSpec params) {
+ super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
+ mParams = params;
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return mParams;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
new file mode 100644
index 000000000000..6ddaa704afa8
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+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(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull KeyStoreSecurityLevel securityLevel,
+ @NonNull ECParameterSpec params, @NonNull ECPoint w) {
+ super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
+ mParams = params;
+ mW = w;
+ }
+
+ public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) {
+ this(descriptor, metadata, securityLevel, info.getParams(), info.getW());
+ if (!"X.509".equalsIgnoreCase(info.getFormat())) {
+ throw new IllegalArgumentException(
+ "Unsupported key export format: " + info.getFormat());
+ }
+ }
+
+ @Override
+ public AndroidKeyStorePrivateKey getPrivateKey() {
+ return new AndroidKeyStoreECPrivateKey(
+ getUserKeyDescriptor(), getKeyIdDescriptor().nspace, getAuthorizations(),
+ getSecurityLevel(), mParams);
+ }
+
+ @Override
+ public ECParameterSpec getParams() {
+ return mParams;
+ }
+
+ @Override
+ public ECPoint getW() {
+ return mW;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
new file mode 100644
index 000000000000..3dde2e592259
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyStoreCryptoOperation;
+import android.security.keystore.KeymasterUtils;
+import android.system.keystore2.KeyParameter;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.MacSpi;
+
+/**
+ * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
+
+ private static final String TAG = "AndroidKeyStoreHmacSpi";
+
+ 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 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 KeyStoreOperation mOperation;
+ private long mOperationChallenge;
+
+ protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
+ mKeymasterDigest = keymasterDigest;
+ mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ mOperation = null;
+ mOperationChallenge = 0;
+ mKey = null;
+ mChunkedStreamer = null;
+ }
+
+ @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 abortOperation() {
+ KeyStoreCryptoOperationUtils.abortOperation(mOperation);
+ mOperation = null;
+ }
+
+ private void resetAll() {
+ abortOperation();
+ mOperationChallenge = 0;
+ mKey = null;
+ mChunkedStreamer = null;
+ }
+
+ private void resetWhilePreservingInitState() {
+ abortOperation();
+ mOperationChallenge = 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");
+ }
+
+ List<KeyParameter> parameters = new ArrayList<>();
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits
+ ));
+
+ try {
+ mOperation = mKey.getSecurityLevel().createOperation(
+ mKey.getKeyIdDescriptor(),
+ parameters
+ );
+ } catch (KeyStoreException keyStoreException) {
+ // If necessary, throw an exception due to KeyStore operation having failed.
+ InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyException(
+ mKey, keyStoreException);
+ if (e != null) {
+ throw e;
+ }
+ }
+
+ // Now we check if we got an operation challenge. This indicates that user authorization
+ // is required. And if we got a challenge we check if the authorization can possibly
+ // succeed.
+ mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
+ mOperation, mKey);
+
+ mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mOperation));
+ }
+
+ @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
+ } catch (KeyStoreException e) {
+ throw new ProviderException("Keystore operation failed", e);
+ }
+
+ resetWhilePreservingInitState();
+ return result;
+ }
+
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ abortOperation();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public long getOperationHandle() {
+ return mOperationChallenge;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
new file mode 100644
index 000000000000..32650aeda1b1
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.util.Log;
+
+import java.security.Key;
+
+/**
+ * {@link Key} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreKey implements Key {
+ // This is the original KeyDescriptor by which the key was loaded from
+ // with alias and domain.
+ private final KeyDescriptor mDescriptor;
+ // The key id can be used make certain manipulations to the keystore database
+ // assuring that the manipulation is made to the exact key that was loaded
+ // from the database. Alias based manipulations can not assure this, because
+ // aliases can be rebound to other keys at any time.
+ private final long mKeyId;
+ private final Authorization[] mAuthorizations;
+ // TODO extract algorithm string from metadata.
+ private final String mAlgorithm;
+
+ // This is the security level interface, that this key is associated with.
+ // We do not include this member in comparisons.
+ private final KeyStoreSecurityLevel mSecurityLevel;
+
+ AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor,
+ long keyId,
+ @NonNull Authorization[] authorizations,
+ @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel securityLevel) {
+ mDescriptor = descriptor;
+ mKeyId = keyId;
+ mAuthorizations = authorizations;
+ mAlgorithm = algorithm;
+ mSecurityLevel = securityLevel;
+ }
+
+ KeyDescriptor getUserKeyDescriptor() {
+ return mDescriptor;
+ }
+
+ KeyDescriptor getKeyIdDescriptor() {
+ KeyDescriptor descriptor = new KeyDescriptor();
+ descriptor.nspace = mKeyId;
+ descriptor.domain = Domain.KEY_ID;
+ descriptor.alias = null;
+ descriptor.blob = null;
+ return descriptor;
+ }
+
+ Authorization[] getAuthorizations() {
+ return mAuthorizations;
+ }
+
+ KeyStoreSecurityLevel getSecurityLevel() {
+ return mSecurityLevel;
+ }
+
+
+ @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 + ((mDescriptor == null) ? 0 : mDescriptor.hashCode());
+ result = prime * result + (int) (mKeyId >>> 32);
+ result = prime * result + (int) (mKeyId & 0xffffffff);
+ result = prime * result + ((mAuthorizations == null) ? 0 : mAuthorizations.hashCode());
+ result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
+ 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 (mKeyId != other.mKeyId) {
+ return false;
+ }
+
+ // If the key ids are equal and the class matches all the other fields cannot differ
+ // unless we have a bug.
+ if (!mAlgorithm.equals(other.mAlgorithm)
+ || !mAuthorizations.equals(other.mAuthorizations)
+ || !mDescriptor.equals(other.mDescriptor)) {
+ Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata"
+ + "differs.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
new file mode 100644
index 000000000000..a8dd7f3f8b14
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.security.KeyStore;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+
+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;
+ @SuppressWarnings("unchecked")
+ T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(keystorePrivateKey);
+ 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;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
new file mode 100644
index 000000000000..ccd0a4bf92ff
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.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;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.SecurityLevel;
+import android.util.Log;
+
+import libcore.util.EmptyArray;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.ProviderException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.crypto.KeyGeneratorSpi;
+import javax.crypto.SecretKey;
+
+/**
+ * {@link KeyGeneratorSpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
+ private static final String TAG = "AndroidKeyStoreKeyGeneratorSpi";
+
+ public static class AES extends AndroidKeyStoreKeyGeneratorSpi {
+ public AES() {
+ super(KeymasterDefs.KM_ALGORITHM_AES, 128);
+ }
+
+ @Override
+ protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ super.engineInit(params, random);
+ if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported key size: " + mKeySizeBits
+ + ". Supported: 128, 192, 256.");
+ }
+ }
+ }
+
+ public static class DESede extends AndroidKeyStoreKeyGeneratorSpi {
+ public DESede() {
+ super(KeymasterDefs.KM_ALGORITHM_3DES, 168);
+ }
+ }
+
+ protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
+ protected HmacBase(int keymasterDigest) {
+ super(KeymasterDefs.KM_ALGORITHM_HMAC,
+ keymasterDigest,
+ KeymasterUtils.getDigestOutputSizeBits(keymasterDigest));
+ }
+ }
+
+ public static class HmacSHA1 extends HmacBase {
+ public HmacSHA1() {
+ super(KeymasterDefs.KM_DIGEST_SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends HmacBase {
+ public HmacSHA224() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_224);
+ }
+ }
+
+ public static class HmacSHA256 extends HmacBase {
+ public HmacSHA256() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_256);
+ }
+ }
+
+ public static class HmacSHA384 extends HmacBase {
+ public HmacSHA384() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_384);
+ }
+ }
+
+ public static class HmacSHA512 extends HmacBase {
+ public HmacSHA512() {
+ super(KeymasterDefs.KM_DIGEST_SHA_2_512);
+ }
+ }
+
+ private final KeyStore2 mKeyStore = KeyStore2.getInstance();
+ private final int mKeymasterAlgorithm;
+ private final int mKeymasterDigest;
+ private final int mDefaultKeySizeBits;
+
+ private KeyGenParameterSpec mSpec;
+ private SecureRandom mRng;
+
+ protected int mKeySizeBits;
+ private int[] mKeymasterPurposes;
+ private int[] mKeymasterBlockModes;
+ private int[] mKeymasterPaddings;
+ private int[] mKeymasterDigests;
+
+ protected AndroidKeyStoreKeyGeneratorSpi(
+ int keymasterAlgorithm,
+ int defaultKeySizeBits) {
+ this(keymasterAlgorithm, -1, defaultKeySizeBits);
+ }
+
+ protected AndroidKeyStoreKeyGeneratorSpi(
+ int keymasterAlgorithm,
+ int keymasterDigest,
+ int defaultKeySizeBits) {
+ mKeymasterAlgorithm = keymasterAlgorithm;
+ mKeymasterDigest = keymasterDigest;
+ mDefaultKeySizeBits = defaultKeySizeBits;
+ if (mDefaultKeySizeBits <= 0) {
+ throw new IllegalArgumentException("Default key size must be positive");
+ }
+
+ if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) {
+ throw new IllegalArgumentException(
+ "Digest algorithm must be specified for HMAC key");
+ }
+ }
+
+ @Override
+ protected void engineInit(SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+
+ @Override
+ protected void engineInit(int keySize, SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+
+ @Override
+ protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if ((params == null) || (!(params instanceof KeyGenParameterSpec))) {
+ throw new InvalidAlgorithmParameterException("Cannot initialize without a "
+ + KeyGenParameterSpec.class.getName() + " parameter");
+ }
+ KeyGenParameterSpec spec = (KeyGenParameterSpec) params;
+ if (spec.getKeystoreAlias() == null) {
+ throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
+ }
+
+ mRng = random;
+ mSpec = spec;
+
+ mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits;
+ if (mKeySizeBits <= 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Key size must be positive: " + mKeySizeBits);
+ } else if ((mKeySizeBits % 8) != 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Key size must be a multiple of 8: " + mKeySizeBits);
+ }
+
+ try {
+ mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
+ mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (spec.getSignaturePaddings().length > 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Signature paddings not supported for symmetric key algorithms");
+ }
+ mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterBlockMode : mKeymasterBlockModes) {
+ if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ keymasterBlockMode)) {
+ throw new InvalidAlgorithmParameterException(
+ "Randomized encryption (IND-CPA) required but may be violated"
+ + " by block mode: "
+ + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
+ + ". See " + KeyGenParameterSpec.class.getName()
+ + " documentation.");
+ }
+ }
+ }
+ if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
+ if (mKeySizeBits != 168) {
+ throw new InvalidAlgorithmParameterException(
+ "3DES key size must be 168 bits.");
+ }
+ }
+ if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+ if (mKeySizeBits < 64 || mKeySizeBits > 512) {
+ throw new InvalidAlgorithmParameterException(
+ "HMAC key sizes must be within 64-512 bits, inclusive.");
+ }
+
+ // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
+ // implies SHA-256 digest). Because keymaster HMAC key is authorized only for
+ // one digest, we don't let algorithm parameter spec override the digest implied
+ // by the key. If the spec specifies digests at all, it must specify only one
+ // digest, the only implied by key algorithm.
+ mKeymasterDigests = new int[] {mKeymasterDigest};
+ if (spec.isDigestsSpecified()) {
+ // Digest(s) explicitly specified in the spec. Check that the list
+ // consists of exactly one digest, the one implied by key algorithm.
+ int[] keymasterDigestsFromSpec =
+ KeyProperties.Digest.allToKeymaster(spec.getDigests());
+ if ((keymasterDigestsFromSpec.length != 1)
+ || (keymasterDigestsFromSpec[0] != mKeymasterDigest)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported digests specification: "
+ + Arrays.asList(spec.getDigests()) + ". Only "
+ + KeyProperties.Digest.fromKeymaster(mKeymasterDigest)
+ + " supported for this HMAC key algorithm");
+ }
+ }
+ } else {
+ // Key algorithm does not imply a digest.
+ if (spec.isDigestsSpecified()) {
+ mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
+ } else {
+ mKeymasterDigests = EmptyArray.INT;
+ }
+ }
+
+ // Check that user authentication related parameters are acceptable. This method
+ // will throw an IllegalStateException if there are issues (e.g., secure lock screen
+ // not set up).
+ 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");
+ }
+
+ List<KeyParameter> params = new ArrayList<>();
+
+ params.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits
+ ));
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
+ ));
+ ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PURPOSE, purpose
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
+ if (blockMode == KeymasterDefs.KM_MODE_GCM
+ && mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
+ params.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
+ AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
+ .MIN_SUPPORTED_TAG_LENGTH_BITS
+ ));
+ }
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterPaddings, (padding) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING, padding
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, digest
+ ));
+ });
+
+ if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC
+ && mKeymasterDigests.length != 0) {
+ int digestOutputSizeBits = KeymasterUtils.getDigestOutputSizeBits(mKeymasterDigests[0]);
+ if (digestOutputSizeBits == -1) {
+ throw new ProviderException(
+ "HMAC key authorized for unsupported digest: "
+ + KeyProperties.Digest.fromKeymaster(mKeymasterDigests[0]));
+ }
+ params.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits
+ ));
+ }
+
+ KeyStore2ParameterUtils.addUserAuthArgs(params, spec);
+
+ if (spec.getKeyValidityStart() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()
+ ));
+ }
+ if (spec.getKeyValidityForOriginationEnd() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ spec.getKeyValidityForOriginationEnd()
+ ));
+ }
+ if (spec.getKeyValidityForConsumptionEnd() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ spec.getKeyValidityForConsumptionEnd()
+ ));
+ }
+
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
+ params.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_CALLER_NONCE
+ ));
+ }
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ @SecurityLevel int securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT;
+ if (spec.isStrongBoxBacked()) {
+ securityLevel = SecurityLevel.STRONGBOX;
+ }
+
+ int flags = 0;
+ if (spec.isCriticalToDeviceEncryption()) {
+ flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
+ }
+
+ KeyDescriptor descriptor = new KeyDescriptor();
+ descriptor.alias = spec.getKeystoreAlias();
+ descriptor.nspace = spec.getNamespace();
+ descriptor.domain = descriptor.nspace == KeyProperties.NAMESPACE_APPLICATION
+ ? Domain.APP
+ : Domain.SELINUX;
+ descriptor.blob = null;
+
+ KeyMetadata metadata = null;
+ KeyStoreSecurityLevel iSecurityLevel = null;
+ try {
+ iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
+ metadata = iSecurityLevel.generateKey(
+ descriptor,
+ null, /* Attestation key not applicable to symmetric keys. */
+ params,
+ flags,
+ additionalEntropy);
+ } catch (android.security.KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ // TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec
+ // becomes available.
+ case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
+ throw new StrongBoxUnavailableException("Failed to generate key");
+ default:
+ throw new ProviderException("Keystore key generation failed", e);
+ }
+ }
+ @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
+ try {
+ keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
+ mKeymasterAlgorithm, mKeymasterDigest);
+ } catch (IllegalArgumentException e) {
+ try {
+ mKeyStore.deleteKey(descriptor);
+ } catch (android.security.KeyStoreException kse) {
+ Log.e(TAG, "Failed to delete key after generating successfully but"
+ + " failed to get the algorithm string.", kse);
+ }
+ throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
+ }
+ SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA,
+ iSecurityLevel);
+ return result;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
new file mode 100644
index 000000000000..a747a0e727d8
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore2;
+import android.security.KeyStoreException;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+import android.security.keystore.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.Domain;
+import android.system.keystore2.IKeystoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.ResponseCode;
+import android.system.keystore2.SecurityLevel;
+import android.util.Log;
+
+import libcore.util.EmptyArray;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyPairGeneratorSpi;
+import java.security.ProviderException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides a way to create instances of a KeyPair which will be placed in the
+ * Android keystore service usable only by the application that called it. This
+ * can be used in conjunction with
+ * {@link java.security.KeyStore#getInstance(String)} using the
+ * {@code "AndroidKeyStore"} type.
+ * <p>
+ * This class can not be directly instantiated and must instead be used via the
+ * {@link KeyPairGenerator#getInstance(String)
+ * KeyPairGenerator.getInstance("AndroidKeyStore")} API.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi {
+ private static final String TAG = "AndroidKeyStoreKeyPairGeneratorSpi";
+
+ public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi {
+ public RSA() {
+ super(KeymasterDefs.KM_ALGORITHM_RSA);
+ }
+ }
+
+ public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi {
+ public EC() {
+ super(KeymasterDefs.KM_ALGORITHM_EC);
+ }
+ }
+
+ /*
+ * These must be kept in sync with system/security/keystore/defaults.h
+ */
+
+ /* EC */
+ private static final int EC_DEFAULT_KEY_SIZE = 256;
+
+ /* RSA */
+ private static final int RSA_DEFAULT_KEY_SIZE = 2048;
+ private static final int RSA_MIN_KEY_SIZE = 512;
+ private static final int RSA_MAX_KEY_SIZE = 8192;
+
+ private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+ new HashMap<String, Integer>();
+ private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
+ private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
+ static {
+ // Aliases for NIST P-224
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+
+
+ // Aliases for NIST P-256
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+
+ // Aliases for NIST P-384
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+
+ // Aliases for NIST P-521
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+
+ SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+
+ SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
+ new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
+ }
+
+ private final int mOriginalKeymasterAlgorithm;
+
+ private KeyStore2 mKeyStore;
+
+ private KeyGenParameterSpec mSpec;
+
+ private String mEntryAlias;
+ private int mEntryUid;
+ 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 Long 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());
+ 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;
+ 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 = KeyStore2.getInstance();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void resetAll() {
+ mEntryAlias = null;
+ mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
+ mJcaKeyAlgorithm = null;
+ mKeymasterAlgorithm = -1;
+ mKeymasterPurposes = null;
+ mKeymasterBlockModes = null;
+ mKeymasterEncryptionPaddings = null;
+ mKeymasterSignaturePaddings = null;
+ mKeymasterDigests = null;
+ mKeySizeBits = 0;
+ mSpec = null;
+ mRSAPublicExponent = null;
+ mRng = null;
+ mKeyStore = null;
+ }
+
+ private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
+ AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec();
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ BigInteger publicExponent = null;
+ if (algSpecificSpec instanceof RSAKeyGenParameterSpec) {
+ RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec;
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = rsaSpec.getKeysize();
+ } else if (mKeySizeBits != rsaSpec.getKeysize()) {
+ throw new InvalidAlgorithmParameterException("RSA key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize());
+ }
+ publicExponent = rsaSpec.getPublicExponent();
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA may only use RSAKeyGenParameterSpec");
+ }
+ if (publicExponent == null) {
+ publicExponent = RSAKeyGenParameterSpec.F4;
+ }
+ if (publicExponent.compareTo(BigInteger.ZERO) < 1) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA public exponent must be positive: " + publicExponent);
+ }
+ if ((publicExponent.signum() == -1)
+ || (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported RSA public exponent: " + publicExponent
+ + ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE);
+ }
+ mRSAPublicExponent = publicExponent.longValue();
+ break;
+ }
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (algSpecificSpec instanceof ECGenParameterSpec) {
+ ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
+ String curveName = ecSpec.getName();
+ Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
+ curveName.toLowerCase(Locale.US));
+ if (ecSpecKeySizeBits == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported EC curve name: " + curveName
+ + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+ }
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = ecSpecKeySizeBits;
+ } else if (mKeySizeBits != ecSpecKeySizeBits) {
+ throw new InvalidAlgorithmParameterException("EC key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits);
+ }
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "EC may only use ECGenParameterSpec");
+ }
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+ }
+
+ @Override
+ public KeyPair generateKeyPair() {
+ if (mKeyStore == null || mSpec == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ final @SecurityLevel int securityLevel =
+ mSpec.isStrongBoxBacked()
+ ? SecurityLevel.STRONGBOX
+ : SecurityLevel.TRUSTED_ENVIRONMENT;
+
+ final int flags =
+ mSpec.isCriticalToDeviceEncryption()
+ ? IKeystoreSecurityLevel
+ .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING
+ : 0;
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ KeyDescriptor descriptor = new KeyDescriptor();
+ descriptor.alias = mEntryAlias;
+ descriptor.domain = mEntryUid == KeyProperties.NAMESPACE_APPLICATION
+ ? Domain.APP
+ : Domain.SELINUX;
+ descriptor.nspace = mEntryUid;
+ descriptor.blob = null;
+
+ boolean success = false;
+ try {
+ KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
+
+ KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
+ constructKeyGenerationArguments(), flags, additionalEntropy);
+
+ AndroidKeyStorePublicKey publicKey =
+ AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
+ descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
+
+ success = true;
+ return new KeyPair(publicKey, publicKey.getPrivateKey());
+ } catch (android.security.KeyStoreException e) {
+ switch(e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
+ throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
+ default:
+ ProviderException p = new ProviderException("Failed to generate key pair.", e);
+ if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
+ throw new SecureKeyImportUnavailableException(p);
+ }
+ throw p;
+ }
+ } catch (UnrecoverableKeyException e) {
+ throw new ProviderException(
+ "Failed to construct key object from newly generated key pair.", e);
+ } finally {
+ if (!success) {
+ try {
+ mKeyStore.deleteKey(descriptor);
+ } catch (KeyStoreException e) {
+ if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
+ Log.e(TAG, "Failed to delete newly generated key after "
+ + "generation failed unexpectedly.", e);
+ }
+ }
+ }
+ }
+ }
+
+ private void addAttestationParameters(@NonNull List<KeyParameter> params)
+ throws ProviderException {
+ byte[] challenge = mSpec.getAttestationChallenge();
+
+ if (challenge != null) {
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge
+ ));
+
+ if (mSpec.isDevicePropertiesAttestationIncluded()) {
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
+ Build.BRAND.getBytes(StandardCharsets.UTF_8)
+ ));
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
+ Build.DEVICE.getBytes(StandardCharsets.UTF_8)
+ ));
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
+ Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
+ ));
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
+ Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
+ ));
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
+ Build.MODEL.getBytes(StandardCharsets.UTF_8)
+ ));
+ }
+ } else {
+ if (mSpec.isDevicePropertiesAttestationIncluded()) {
+ throw new ProviderException("An attestation challenge must be provided when "
+ + "requesting device properties attestation.");
+ }
+ }
+ }
+
+ private Collection<KeyParameter> constructKeyGenerationArguments() {
+ List<KeyParameter> params = new ArrayList<>();
+ params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
+ ));
+ ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PURPOSE, purpose
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterEncryptionPaddings, (padding) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING, padding
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING, padding
+ ));
+ });
+ ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+ params.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, digest
+ ));
+ });
+
+ KeyStore2ParameterUtils.addUserAuthArgs(params, mSpec);
+
+ if (mSpec.getKeyValidityStart() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()
+ ));
+ }
+ if (mSpec.getKeyValidityForOriginationEnd() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ mSpec.getKeyValidityForOriginationEnd()
+ ));
+ }
+ if (mSpec.getKeyValidityForConsumptionEnd() != null) {
+ params.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ mSpec.getKeyValidityForConsumptionEnd()
+ ));
+ }
+
+ addAlgorithmSpecificParameters(params);
+
+ if (mSpec.isUniqueIdIncluded()) {
+ params.add(KeyStore2ParameterUtils.makeBool(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID));
+ }
+
+ addAttestationParameters(params);
+
+ return params;
+ }
+
+ private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ params.add(KeyStore2ParameterUtils.makeLong(
+ KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent
+ ));
+ break;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+ }
+
+ private static int getDefaultKeySize(int keymasterAlgorithm) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC_DEFAULT_KEY_SIZE;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA_DEFAULT_KEY_SIZE;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ private static void checkValidKeySize(
+ int keymasterAlgorithm,
+ int keySize,
+ boolean isStrongBoxBacked)
+ throws InvalidAlgorithmParameterException {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (isStrongBoxBacked && keySize != 256) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported StrongBox EC key size: "
+ + keySize + " bits. Supported: 256");
+ }
+ if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+ throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
+ + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
+ }
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
+ throw new InvalidAlgorithmParameterException("RSA key size must be >= "
+ + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
+ }
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ /**
+ * Returns the {@code Signature} algorithm to be used for signing a certificate using the
+ * specified key or {@code null} if the key cannot be used for signing a certificate.
+ */
+ @Nullable
+ private static String getCertificateSignatureAlgorithm(
+ int keymasterAlgorithm,
+ int keySizeBits,
+ KeyGenParameterSpec spec) {
+ // Constraints:
+ // 1. Key must be authorized for signing without user authentication.
+ // 2. Signature digest must be one of key's authorized digests.
+ // 3. For RSA keys, the digest output size must not exceed modulus size minus space overhead
+ // of RSA PKCS#1 signature padding scheme (about 30 bytes).
+ // 4. For EC keys, the there is no point in using a digest whose output size is longer than
+ // key/field size because the digest will be truncated to that size.
+
+ if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) {
+ // Key not authorized for signing
+ return null;
+ }
+ if (spec.isUserAuthenticationRequired()) {
+ // Key not authorized for use without user authentication
+ return null;
+ }
+ if (!spec.isDigestsSpecified()) {
+ // Key not authorized for any digests -- can't sign
+ return null;
+ }
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ {
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits == keySizeBits) {
+ // Perfect match -- use this digest
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ break;
+ }
+ // Not a perfect match -- check against the best digest so far
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // Prefer output size to be as close to key size as possible, with output
+ // sizes larger than key size preferred to those smaller than key size.
+ if (bestDigestOutputSizeBits < keySizeBits) {
+ // Output size of the best digest so far is smaller than key size.
+ // Anything larger is a win.
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ } else {
+ // Output size of the best digest so far is larger than key size.
+ // Anything smaller is a win, as long as it's not smaller than key size.
+ if ((outputSizeBits < bestDigestOutputSizeBits)
+ && (outputSizeBits >= keySizeBits)) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
+ }
+ }
+ if (bestKeymasterDigest == -1) {
+ return null;
+ }
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithECDSA";
+ }
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ // Check whether this key is authorized for PKCS#1 signature padding.
+ // We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle
+ // only supports RSA certificates signed using PKCS#1 padding scheme. The key needs
+ // to be authorized for PKCS#1 padding or padding NONE which means any padding.
+ boolean pkcs1SignaturePaddingSupported =
+ com.android.internal.util.ArrayUtils.contains(
+ KeyProperties.SignaturePadding.allToKeymaster(
+ spec.getSignaturePaddings()),
+ KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
+ if (!pkcs1SignaturePaddingSupported) {
+ // Key not authorized for PKCS#1 signature padding -- can't sign
+ return null;
+ }
+
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ // The amount of space available for the digest is less than modulus size by about
+ // 30 bytes because padding must be at least 11 bytes long (00 || 01 || PS || 00,
+ // where PS must be at least 8 bytes long), and then there's also the 15--19 bytes
+ // overhead (depending the on chosen digest) for encoding digest OID and digest
+ // value in DER.
+ int maxDigestOutputSizeBits = keySizeBits - 30 * 8;
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits > maxDigestOutputSizeBits) {
+ // Digest too long (signature generation will fail) -- skip
+ continue;
+ }
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // The longer the better
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
+ }
+ if (bestKeymasterDigest == -1) {
+ return null;
+ }
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithRSA";
+ }
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
+ }
+ }
+
+ private static Set<Integer> getAvailableKeymasterSignatureDigests(
+ @KeyProperties.DigestEnum String[] authorizedKeyDigests,
+ @KeyProperties.DigestEnum String[] supportedSignatureDigests) {
+ Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>();
+ for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) {
+ authorizedKeymasterKeyDigests.add(keymasterDigest);
+ }
+ Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>();
+ for (int keymasterDigest
+ : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) {
+ supportedKeymasterSignatureDigests.add(keymasterDigest);
+ }
+ Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests);
+ result.retainAll(authorizedKeymasterKeyDigests);
+ return result;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
new file mode 100644
index 000000000000..afb10547411b
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import java.security.KeyStore;
+import java.security.KeyStore.ProtectionParameter;
+
+/**
+ * @hide
+ */
+class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
+
+ private final int mNamespace;
+
+ AndroidKeyStoreLoadStoreParameter(int namespace) {
+ mNamespace = namespace;
+ }
+
+ @Override
+ public ProtectionParameter getProtectionParameter() {
+ return null;
+ }
+
+ int getNamespace() {
+ return mNamespace;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java
new file mode 100644
index 000000000000..8b331ee3b880
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyDescriptor;
+
+import java.security.PrivateKey;
+
+/**
+ * {@link PrivateKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey {
+
+ public AndroidKeyStorePrivateKey(@NonNull KeyDescriptor descriptor,
+ long keyId, @NonNull Authorization[] authorizations, @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel securityLevel) {
+ super(descriptor, keyId, authorizations, algorithm, securityLevel);
+ }
+
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
new file mode 100644
index 000000000000..b2e32a3175e3
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStore;
+import android.security.KeyStore2;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyStoreCryptoOperation;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
+import android.system.keystore2.KeyMetadata;
+import android.system.keystore2.ResponseCode;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+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.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * A provider focused on providing JCA interfaces for the Android KeyStore.
+ *
+ * @hide
+ */
+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.keystore2";
+
+ private static final String DESEDE_SYSTEM_PROPERTY =
+ "ro.hardware.keystore_desede";
+
+ /** @hide **/
+ public AndroidKeyStoreProvider() {
+ super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
+
+ boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY));
+
+ // java.security.KeyStore
+ put("KeyStore.AndroidKeyStore", 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");
+ }
+
+ 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}).
+ * @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;
+ }
+ }
+ sInstalled = true;
+
+ Security.addProvider(new AndroidKeyStoreProvider());
+ Security.addProvider(
+ new android.security.keystore.AndroidKeyStoreProvider(
+ "AndroidKeyStoreLegacy"));
+ Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
+ Provider legacyWorkaroundProvider =
+ new android.security.keystore.AndroidKeyStoreBCWorkaroundProvider(
+ "AndroidKeyStoreBCWorkaroundLegacy");
+ if (bcProviderIndex != -1) {
+ // Bouncy Castle provider found -- install the workaround provider above it.
+ // insertProviderAt uses 1-based positions.
+ Security.insertProviderAt(legacyWorkaroundProvider, bcProviderIndex + 1);
+ Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
+ } else {
+ // Bouncy Castle provider not found -- install the workaround provider at lowest
+ // priority.
+ Security.addProvider(workaroundProvider);
+ Security.addProvider(legacyWorkaroundProvider);
+ }
+ }
+
+ private void putSecretKeyFactoryImpl(String algorithm) {
+ put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
+ }
+
+ private void putKeyFactoryImpl(String algorithm) {
+ put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
+ }
+
+ /**
+ * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+ * primitive.
+ *
+ * <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
+ *
+ * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
+ * is not in progress.
+ *
+ * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
+ * by AndroidKeyStore provider.
+ * @throws IllegalStateException if the provided primitive is not initialized.
+ * @hide
+ */
+ public static long getKeyStoreOperationHandle(Object cryptoPrimitive) {
+ 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();
+ }
+
+ /**
+ * This helper function gets called if the key loaded from the keystore daemon
+ * is for an asymmetric algorithm. It constructs an instance of {@link AndroidKeyStorePublicKey}
+ * which implements {@link PublicKey}.
+ *
+ * @param descriptor The original key descriptor that was used to load the key.
+ *
+ * @param metadata The key metadata which includes the public key material, a reference to the
+ * stored private key material, the key characteristics.
+ * @param iSecurityLevel A binder interface that allows using the private key.
+ * @param algorithm Must indicate EC or RSA.
+ * @return AndroidKeyStorePublicKey
+ * @throws UnrecoverableKeyException
+ * @hide
+ */
+ @NonNull
+ static AndroidKeyStorePublicKey makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
+ @NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull KeyStoreSecurityLevel iSecurityLevel, int algorithm)
+ throws UnrecoverableKeyException {
+ if (metadata.certificate == null) {
+ throw new UnrecoverableKeyException("Failed to obtain X.509 form of public key."
+ + " Keystore has no public certificate stored.");
+ }
+ final byte[] x509EncodedPublicKey = metadata.certificate;
+
+ String jcaKeyAlgorithm;
+ try {
+ jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
+ algorithm);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to load private key")
+ .initCause(e);
+ }
+
+ PublicKey publicKey;
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(jcaKeyAlgorithm);
+ publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPublicKey));
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException(
+ "Failed to obtain " + jcaKeyAlgorithm + " KeyFactory", e);
+ } catch (InvalidKeySpecException e) {
+ throw new ProviderException("Invalid X.509 encoding of public key", e);
+ }
+
+ KeyStoreSecurityLevel securityLevel = iSecurityLevel;
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
+
+ return new AndroidKeyStoreECPublicKey(descriptor, metadata,
+ iSecurityLevel, (ECPublicKey) publicKey);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) {
+ return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
+ iSecurityLevel, (RSAPublicKey) publicKey);
+ } else {
+ throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ + jcaKeyAlgorithm);
+ }
+ }
+
+ /** @hide **/
+ @NonNull
+ public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
+ @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
+ throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
+ AndroidKeyStoreKey key =
+ loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
+ if (key instanceof AndroidKeyStorePublicKey) {
+ return (AndroidKeyStorePublicKey) key;
+ } else {
+ throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
+ }
+ }
+
+ /** @hide **/
+ @NonNull
+ public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+ @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
+ throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
+ AndroidKeyStoreKey key =
+ loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
+ if (key instanceof AndroidKeyStorePublicKey) {
+ AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key;
+ return new KeyPair(publicKey, publicKey.getPrivateKey());
+ } else {
+ throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
+ }
+ }
+
+ /** @hide **/
+ @NonNull
+ public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
+ throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
+ AndroidKeyStoreKey key =
+ loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
+ if (key instanceof AndroidKeyStorePublicKey) {
+ return ((AndroidKeyStorePublicKey) key).getPrivateKey();
+ } else {
+ throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
+ }
+ }
+
+
+ @NonNull
+ private static AndroidKeyStoreSecretKey makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(
+ @NonNull KeyDescriptor descriptor,
+ @NonNull KeyEntryResponse response, int algorithm, int digest)
+ throws UnrecoverableKeyException {
+
+ @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
+ try {
+ keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
+ algorithm, digest);
+ } catch (IllegalArgumentException e) {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
+ }
+
+ return new AndroidKeyStoreSecretKey(descriptor,
+ response.metadata, keyAlgorithmString,
+ new KeyStoreSecurityLevel(response.iSecurityLevel));
+ }
+
+ /**
+ * Loads an an AndroidKeyStoreKey from the AndroidKeyStore backend.
+ *
+ * @param keyStore The keystore2 backend.
+ * @param alias The alias of the key in the Keystore database.
+ * @param namespace The a Keystore namespace. This is used by system api only to request
+ * Android system specific keystore namespace, which can be configured
+ * in the device's SEPolicy. Third party apps and most system components
+ * set this parameter to -1 to indicate their application specific namespace.
+ * TODO b/171806779 link to public Keystore 2.0 documentation.
+ * See bug for more details for now.
+ * @hide
+ **/
+ @NonNull
+ public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
+ @NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)
+ throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
+
+ KeyDescriptor descriptor = new KeyDescriptor();
+ if (namespace == KeyProperties.NAMESPACE_APPLICATION) {
+ descriptor.nspace = 0; // ignored;
+ descriptor.domain = Domain.APP;
+ } else {
+ descriptor.nspace = namespace;
+ descriptor.domain = Domain.SELINUX;
+ }
+ descriptor.alias = alias;
+ descriptor.blob = null;
+ KeyEntryResponse response = null;
+ try {
+ response = keyStore.getKeyEntry(descriptor);
+ } catch (android.security.KeyStoreException e) {
+ if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) {
+ throw new KeyPermanentlyInvalidatedException(
+ "User changed or deleted their auth credentials",
+ e);
+ } else {
+ throw (UnrecoverableKeyException)
+ new UnrecoverableKeyException("Failed to obtain information about key")
+ .initCause(e);
+ }
+ }
+
+ Integer keymasterAlgorithm = null;
+ // We just need one digest for the algorithm name
+ int keymasterDigest = -1;
+ for (Authorization a : response.metadata.authorizations) {
+ switch (a.keyParameter.tag) {
+ case KeymasterDefs.KM_TAG_ALGORITHM:
+ keymasterAlgorithm = a.keyParameter.integer;
+ break;
+ case KeymasterDefs.KM_TAG_DIGEST:
+ if (keymasterDigest == -1) keymasterDigest = a.keyParameter.integer;
+ break;
+ }
+ }
+ 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 makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(descriptor, response,
+ keymasterAlgorithm, keymasterDigest);
+ } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
+ keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
+ return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
+ new KeyStoreSecurityLevel(response.iSecurityLevel),
+ keymasterAlgorithm);
+ } else {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
new file mode 100644
index 000000000000..49dd77e3a3db
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keystore.ArrayUtils;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import java.security.PublicKey;
+
+/**
+ * {@link PublicKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
+ private final byte[] mCertificate;
+ private final byte[] mCertificateChain;
+
+ public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata, @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel securityLevel) {
+ super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel);
+ mCertificate = metadata.certificate;
+ mCertificateChain = metadata.certificateChain;
+ }
+
+ abstract AndroidKeyStorePrivateKey getPrivateKey();
+
+ @Override
+ public String getFormat() {
+ return "X.509";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return ArrayUtils.cloneIfNotEmpty(mCertificate);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + super.hashCode();
+ result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode());
+ result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode());
+
+ 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;
+ }
+ return true;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
new file mode 100644
index 000000000000..a6ea9723db24
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeymasterUtils;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyParameter;
+
+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 java.util.List;
+
+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(
+ @NonNull List<KeyParameter> parameters) {
+ super.addAlgorithmSpecificParametersToBegin(parameters);
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
+ ));
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters) {
+ super.loadAlgorithmSpecificParametersFromBeginResult(parameters);
+ }
+
+ @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));
+ }
+ }
+
+ long keySizeBits = -1;
+ for (Authorization a : keystoreKey.getAuthorizations()) {
+ if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
+ keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
+ }
+ }
+
+ 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 List<KeyParameter> parameters) {
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA
+ ));
+ int keymasterPadding = getKeymasterPaddingOverride();
+ if (keymasterPadding == -1) {
+ keymasterPadding = mKeymasterPadding;
+ }
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ 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.
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE
+ ));
+ }
+ }
+
+ @Override
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters) {
+ }
+
+ @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;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java
new file mode 100644
index 000000000000..ef0d3bc4d46a
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyDescriptor;
+
+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(@NonNull KeyDescriptor descriptor,
+ long keyId,
+ @NonNull Authorization[] authorizations,
+ @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus) {
+ super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_RSA, securityLevel);
+ mModulus = modulus;
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java
new file mode 100644
index 000000000000..b578ea9baa06
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+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(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus,
+ @NonNull BigInteger publicExponent) {
+ super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel);
+ mModulus = modulus;
+ mPublicExponent = publicExponent;
+ }
+
+ public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata,
+ @NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) {
+ this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent());
+ if (!"X.509".equalsIgnoreCase(info.getFormat())) {
+ throw new IllegalArgumentException(
+ "Unsupported key export format: " + info.getFormat());
+ }
+ }
+
+ @Override
+ public AndroidKeyStorePrivateKey getPrivateKey() {
+ return new AndroidKeyStoreRSAPrivateKey(getUserKeyDescriptor(), getKeyIdDescriptor().nspace,
+ getAuthorizations(), getSecurityLevel(), mModulus);
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return mModulus;
+ }
+
+ @Override
+ public BigInteger getPublicExponent() {
+ return mPublicExponent;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
new file mode 100644
index 000000000000..5f1b9c0586a1
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.KeyParameter;
+
+import java.security.InvalidKeyException;
+import java.security.SignatureSpi;
+import java.util.List;
+
+/**
+ * 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 List<KeyParameter> parameters) {
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding
+ ));
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java
new file mode 100644
index 000000000000..4e459137e875
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import javax.crypto.SecretKey;
+
+/**
+ * {@link SecretKey} backed by Android Keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey {
+
+ public AndroidKeyStoreSecretKey(@NonNull KeyDescriptor descriptor,
+ @NonNull KeyMetadata metadata, @NonNull String algorithm,
+ @NonNull KeyStoreSecurityLevel securityLevel) {
+ super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel);
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
new file mode 100644
index 000000000000..9d3b9704d711
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.GateKeeper;
+import android.security.KeyStore;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.Authorization;
+
+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;
+
+ return getKeyInfo(keystoreKey);
+ }
+
+ static @NonNull KeyInfo getKeyInfo(@NonNull AndroidKeyStoreKey key) {
+
+ @KeyProperties.SecurityLevelEnum int securityLevel =
+ KeyProperties.SECURITY_LEVEL_SOFTWARE;
+ boolean insideSecureHardware = false;
+ @KeyProperties.OriginEnum int origin = -1;
+ int keySize = -1;
+ @KeyProperties.PurposeEnum int purposes = 0;
+ String[] encryptionPaddings;
+ String[] signaturePaddings;
+ List<String> digestsList = new ArrayList<>();
+ List<String> blockModesList = new ArrayList<>();
+ int keymasterSwEnforcedUserAuthenticators = 0;
+ int keymasterHwEnforcedUserAuthenticators = 0;
+ List<BigInteger> keymasterSecureUserIds = new ArrayList<BigInteger>();
+ List<String> encryptionPaddingsList = new ArrayList<String>();
+ List<String> signaturePaddingsList = new ArrayList<String>();
+ Date keyValidityStart = null;
+ Date keyValidityForOriginationEnd = null;
+ Date keyValidityForConsumptionEnd = null;
+ long userAuthenticationValidityDurationSeconds = 0;
+ boolean userAuthenticationRequired = true;
+ boolean userAuthenticationValidWhileOnBody = false;
+ boolean trustedUserPresenceRequired = false;
+ boolean trustedUserConfirmationRequired = false;
+ try {
+ for (Authorization a : key.getAuthorizations()) {
+ switch (a.keyParameter.tag) {
+ case KeymasterDefs.KM_TAG_ORIGIN:
+ insideSecureHardware =
+ KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
+ securityLevel = a.securityLevel;
+ origin = KeyProperties.Origin.fromKeymaster(a.keyParameter.integer);
+ break;
+ case KeymasterDefs.KM_TAG_KEY_SIZE:
+ long keySizeUnsigned = KeyStore2ParameterUtils.getUnsignedInt(a);
+ if (keySizeUnsigned > Integer.MAX_VALUE) {
+ throw new ProviderException(
+ "Key too large: " + keySizeUnsigned + " bits");
+ }
+ keySize = (int) keySizeUnsigned;
+ break;
+ case KeymasterDefs.KM_TAG_PURPOSE:
+ purposes |= KeyProperties.Purpose.fromKeymaster(a.keyParameter.integer);
+ break;
+ case KeymasterDefs.KM_TAG_PADDING:
+ try {
+ if (a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN
+ || a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PSS) {
+ @KeyProperties.SignaturePaddingEnum String padding =
+ KeyProperties.SignaturePadding.fromKeymaster(
+ a.keyParameter.integer);
+ signaturePaddingsList.add(padding);
+ } else {
+ @KeyProperties.EncryptionPaddingEnum String jcaPadding =
+ KeyProperties.EncryptionPadding.fromKeymaster(
+ a.keyParameter.integer);
+ encryptionPaddingsList.add(jcaPadding);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new ProviderException("Unsupported padding: "
+ + a.keyParameter.integer);
+ }
+ break;
+ case KeymasterDefs.KM_TAG_DIGEST:
+ digestsList.add(KeyProperties.Digest.fromKeymaster(a.keyParameter.integer));
+ break;
+ case KeymasterDefs.KM_TAG_BLOCK_MODE:
+ blockModesList.add(
+ KeyProperties.BlockMode.fromKeymaster(a.keyParameter.integer)
+ );
+ break;
+ case KeymasterDefs.KM_TAG_USER_AUTH_TYPE:
+ if (KeyStore2ParameterUtils.isSecureHardware(a.securityLevel)) {
+ keymasterHwEnforcedUserAuthenticators = a.keyParameter.integer;
+ } else {
+ keymasterSwEnforcedUserAuthenticators = a.keyParameter.integer;
+ }
+ break;
+ case KeymasterDefs.KM_TAG_USER_SECURE_ID:
+ keymasterSecureUserIds.add(
+ KeymasterArguments.toUint64(a.keyParameter.longInteger));
+ break;
+ case KeymasterDefs.KM_TAG_ACTIVE_DATETIME:
+ keyValidityStart = KeyStore2ParameterUtils.getDate(a);
+ break;
+ case KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME:
+ keyValidityForOriginationEnd =
+ KeyStore2ParameterUtils.getDate(a);
+ break;
+ case KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME:
+ keyValidityForConsumptionEnd =
+ KeyStore2ParameterUtils.getDate(a);
+ break;
+ case KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED:
+ userAuthenticationRequired = false;
+ break;
+ case KeymasterDefs.KM_TAG_AUTH_TIMEOUT:
+ userAuthenticationValidityDurationSeconds =
+ KeyStore2ParameterUtils.getUnsignedInt(a);
+ if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) {
+ throw new ProviderException(
+ "User authentication timeout validity too long: "
+ + userAuthenticationValidityDurationSeconds + " seconds");
+ }
+ break;
+ case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY:
+ userAuthenticationValidWhileOnBody =
+ KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
+ break;
+ case KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
+ trustedUserPresenceRequired =
+ KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
+ break;
+ case KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
+ trustedUserConfirmationRequired =
+ KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
+ break;
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ throw new ProviderException("Unsupported key characteristic", e);
+ }
+ if (keySize == -1) {
+ throw new ProviderException("Key size not available");
+ }
+ if (origin == -1) {
+ throw new ProviderException("Key origin not available");
+ }
+
+ encryptionPaddings =
+ encryptionPaddingsList.toArray(new String[0]);
+ signaturePaddings =
+ signaturePaddingsList.toArray(new String[0]);
+
+ boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
+ && (keymasterHwEnforcedUserAuthenticators != 0)
+ && (keymasterSwEnforcedUserAuthenticators == 0);
+
+ String[] digests = digestsList.toArray(new String[0]);
+ String[] blockModes = blockModesList.toArray(new String[0]);
+
+ 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.isEmpty()
+ && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId());
+ }
+
+ return new KeyInfo(key.getUserKeyDescriptor().alias,
+ insideSecureHardware,
+ origin,
+ keySize,
+ keyValidityStart,
+ keyValidityForOriginationEnd,
+ keyValidityForConsumptionEnd,
+ purposes,
+ encryptionPaddings,
+ signaturePaddings,
+ digests,
+ blockModes,
+ userAuthenticationRequired,
+ (int) userAuthenticationValidityDurationSeconds,
+ keymasterHwEnforcedUserAuthenticators,
+ userAuthenticationRequirementEnforcedBySecureHardware,
+ userAuthenticationValidWhileOnBody,
+ trustedUserPresenceRequired,
+ invalidatedByBiometricEnrollment,
+ trustedUserConfirmationRequired,
+ securityLevel);
+ }
+
+ 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;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java
new file mode 100644
index 000000000000..55414b70d403
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+import android.security.keystore.KeyStoreCryptoOperation;
+import android.system.keystore2.KeyParameter;
+
+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;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
+ implements KeyStoreCryptoOperation {
+ private static final String TAG = "AndroidKeyStoreSignatureSpiBase";
+
+ // 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;
+
+ /**
+ * Object representing 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 KeyStoreOperation mOperation;
+ /**
+ * The operation challenge is required when an operation needs user authorization.
+ * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric
+ * authenticator, and included in the authentication token minted by this authenticator.
+ * It may be null, if the operation does not require authorization.
+ */
+ private long mOperationChallenge;
+ 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() {
+ mOperation = null;
+ mOperationChallenge = 0;
+ mSigning = false;
+ mKey = null;
+ appRandom = null;
+ mMessageStreamer = null;
+ mCachedException = null;
+ }
+
+ @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;
+ }
+
+ private void abortOperation() {
+ KeyStoreCryptoOperationUtils.abortOperation(mOperation);
+ mOperation = null;
+ }
+
+ /**
+ * 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() {
+ abortOperation();
+ mOperationChallenge = 0;
+ mSigning = false;
+ mKey = null;
+ appRandom = null;
+ 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() {
+ abortOperation();
+ mOperationChallenge = 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");
+ }
+
+ List<KeyParameter> parameters = new ArrayList<>();
+ addAlgorithmSpecificParametersToBegin(parameters);
+
+ int purpose = mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY;
+
+ parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
+
+ try {
+ mOperation = mKey.getSecurityLevel().createOperation(
+ mKey.getKeyIdDescriptor(),
+ parameters);
+ } catch (KeyStoreException keyStoreException) {
+ throw KeyStoreCryptoOperationUtils.getInvalidKeyException(
+ mKey, keyStoreException);
+ }
+
+ // Now we check if we got an operation challenge. This indicates that user authorization
+ // is required. And if we got a challenge we check if the authorization can possibly
+ // succeed.
+ mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
+ mOperation, mKey);
+
+ mMessageStreamer = createMainDataStreamer(mOperation);
+ }
+
+ /**
+ * 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(
+ @NonNull KeyStoreOperation operation) {
+ return new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ operation));
+ }
+
+ @Override
+ public final long getOperationHandle() {
+ return mOperationChallenge;
+ }
+
+ @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
+ } 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);
+ 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();
+ }
+
+ /**
+ * 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 parameters keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected abstract void addAlgorithmSpecificParametersToBegin(
+ @NonNull List<KeyParameter> parameters);
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
new file mode 100644
index 000000000000..4c26864cb02b
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -0,0 +1,1167 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricManager;
+import android.security.GateKeeper;
+import android.security.KeyStore2;
+import android.security.KeyStoreParameter;
+import android.security.KeyStoreSecurityLevel;
+import android.security.keymaster.KeymasterDefs;
+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;
+import android.system.keystore2.Domain;
+import android.system.keystore2.IKeystoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
+import android.system.keystore2.KeyMetadata;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.ResponseCode;
+import android.system.keystore2.SecurityLevel;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore.Entry;
+import java.security.KeyStore.LoadStoreParameter;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.KeyStore.ProtectionParameter;
+import java.security.KeyStore.SecretKeyEntry;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.crypto.SecretKey;
+
+/**
+ * A java.security.KeyStore interface for the Android KeyStore. An instance of
+ * it can be created via the {@link java.security.KeyStore#getInstance(String)
+ * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
+ * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
+ * <p>
+ * This is built on top of Android's keystore daemon. The convention of alias
+ * use is:
+ * <p>
+ * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
+ * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
+ * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
+ * entry which will have the rest of the chain concatenated in BER format.
+ * <p>
+ * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
+ * with a single certificate.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreSpi extends KeyStoreSpi {
+ public static final String TAG = "AndroidKeyStoreSpi";
+ public static final String NAME = "AndroidKeyStore";
+
+ private KeyStore2 mKeyStore;
+ private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+
+ @Override
+ public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
+ UnrecoverableKeyException {
+ try {
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore,
+ alias,
+ mNamespace);
+ } catch (KeyPermanentlyInvalidatedException e) {
+ throw new UnrecoverableKeyException(e.getMessage());
+ } catch (UnrecoverableKeyException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof android.security.KeyStoreException) {
+ if (((android.security.KeyStoreException) cause).getErrorCode()
+ == ResponseCode.KEY_NOT_FOUND) {
+ return null;
+ }
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Make a key descriptor from the given alias and the mNamespace member.
+ * If mNamespace is -1 it sets the domain field to {@link Domain#APP} and {@link Domain#SELINUX}
+ * otherwise. The blob field is always set to null and the alias field to {@code alias}
+ * @param alias The alias of the new key descriptor.
+ * @return A new key descriptor.
+ */
+ private KeyDescriptor makeKeyDescriptor(@NonNull String alias) {
+ KeyDescriptor descriptor = new KeyDescriptor();
+ descriptor.domain = getTargetDomain();
+ descriptor.nspace = mNamespace; // ignored if Domain.App;
+ descriptor.alias = alias;
+ descriptor.blob = null;
+ return descriptor;
+ }
+
+ private @Domain int getTargetDomain() {
+ return mNamespace == KeyProperties.NAMESPACE_APPLICATION
+ ? Domain.APP
+ : Domain.SELINUX;
+ }
+ private KeyEntryResponse getKeyMetadata(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ KeyDescriptor descriptor = makeKeyDescriptor(alias);
+
+ try {
+ return mKeyStore.getKeyEntry(descriptor);
+ } catch (android.security.KeyStoreException e) {
+ if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
+ Log.w(TAG, "Could not get key metadata from Keystore.", e);
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public Certificate[] engineGetCertificateChain(String alias) {
+ KeyEntryResponse response = getKeyMetadata(alias);
+
+ if (response == null || response.metadata.certificate == null) {
+ return null;
+ }
+
+ final X509Certificate leaf = (X509Certificate) toCertificate(response.metadata.certificate);
+ if (leaf == null) {
+ return null;
+ }
+
+ final Certificate[] caList;
+
+ final byte[] caBytes = response.metadata.certificateChain;
+
+ if (caBytes != null) {
+ final Collection<X509Certificate> caChain = toCertificates(caBytes);
+
+ caList = new Certificate[caChain.size() + 1];
+
+ final Iterator<X509Certificate> it = caChain.iterator();
+ int i = 1;
+ while (it.hasNext()) {
+ caList[i++] = it.next();
+ }
+ } else {
+ caList = new Certificate[1];
+ }
+
+ caList[0] = leaf;
+
+ return caList;
+ }
+
+ @Override
+ public Certificate engineGetCertificate(String alias) {
+ KeyEntryResponse response = getKeyMetadata(alias);
+
+ if (response == null) {
+ return null;
+ }
+
+ byte[] encodedCert = response.metadata.certificate;
+ if (encodedCert != null) {
+ return toCertificate(encodedCert);
+ }
+
+ encodedCert = response.metadata.certificateChain;
+ if (encodedCert != null) {
+ return toCertificate(encodedCert);
+ }
+
+ // This entry/alias does not contain a certificate.
+ return null;
+ }
+
+ private static X509Certificate toCertificate(byte[] bytes) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ Log.w(NAME, "Couldn't parse certificate in keystore", e);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Collection<X509Certificate> toCertificates(byte[] bytes) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (Collection<X509Certificate>) certFactory.generateCertificates(
+ new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ Log.w(NAME, "Couldn't parse certificates in keystore", e);
+ return new ArrayList<X509Certificate>();
+ }
+ }
+
+ @Override
+ public Date engineGetCreationDate(String alias) {
+ KeyEntryResponse response = getKeyMetadata(alias);
+
+ if (response == null) {
+ return null;
+ }
+
+
+ // TODO add modification time to key metadata.
+ return null;
+ // if (response.metadata.modificationTime == -1) {
+ // return null;
+ // }
+ // return new Date(response.metadata.modificationTime);
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
+ throws KeyStoreException {
+ if ((password != null) && (password.length > 0)) {
+ throw new KeyStoreException("entries cannot be protected with passwords");
+ }
+
+ if (key instanceof PrivateKey) {
+ setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
+ } else if (key instanceof SecretKey) {
+ setSecretKeyEntry(alias, (SecretKey) key, null);
+ } else {
+ throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
+ }
+ }
+
+ private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key)
+ throws KeyStoreException {
+ String keyAlgorithm = key.getAlgorithm();
+ KeyProtection.Builder specBuilder;
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ 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);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ 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);
+ } else {
+ throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ specBuilder.setUserAuthenticationRequired(false);
+
+ return specBuilder.build();
+ }
+
+ private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
+ java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
+ @SecurityLevel int securitylevel = SecurityLevel.TRUSTED_ENVIRONMENT;
+ int flags = 0;
+ KeyProtection spec;
+ if (param == null) {
+ spec = getLegacyKeyProtectionParameter(key);
+ } else if (param instanceof KeyStoreParameter) {
+ spec = getLegacyKeyProtectionParameter(key);
+ KeyStoreParameter legacySpec = (KeyStoreParameter) param;
+ } else if (param instanceof KeyProtection) {
+ spec = (KeyProtection) param;
+ if (spec.isCriticalToDeviceEncryption()) {
+ // This key is should not be bound to the LSKF even if it is auth bound.
+ // This indicates that this key is used in the derivation for of the
+ // master key, that is used for the LSKF binding of other auth bound
+ // keys. This breaks up a circular dependency while retaining logical
+ // authentication binding of the key.
+ flags |= IKeystoreSecurityLevel
+ .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
+ }
+
+ if (spec.isStrongBoxBacked()) {
+ securitylevel = SecurityLevel.STRONGBOX;
+ }
+ } else {
+ throw new KeyStoreException(
+ "Unsupported protection parameter class:" + param.getClass().getName()
+ + ". Supported: " + KeyProtection.class.getName() + ", "
+ + KeyStoreParameter.class.getName());
+ }
+
+ // Make sure the chain exists since this is a PrivateKey
+ if ((chain == null) || (chain.length == 0)) {
+ throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
+ }
+
+ // Do chain type checking.
+ X509Certificate[] x509chain = new X509Certificate[chain.length];
+ for (int i = 0; i < chain.length; i++) {
+ if (!"X.509".equals(chain[i].getType())) {
+ throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
+ + i);
+ }
+
+ if (!(chain[i] instanceof X509Certificate)) {
+ throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
+ + i);
+ }
+
+ x509chain[i] = (X509Certificate) chain[i];
+ }
+
+ final byte[] userCertBytes;
+ try {
+ userCertBytes = x509chain[0].getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException("Failed to encode certificate #0", e);
+ }
+
+ /*
+ * If we have a chain, store it in the CA certificate slot for this
+ * alias as concatenated DER-encoded certificates. These can be
+ * deserialized by {@link CertificateFactory#generateCertificates}.
+ */
+ final byte[] chainBytes;
+ if (chain.length > 1) {
+ /*
+ * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
+ * so we only need the certificates starting at index 1.
+ */
+ final byte[][] certsBytes = new byte[x509chain.length - 1][];
+ int totalCertLength = 0;
+ for (int i = 0; i < certsBytes.length; i++) {
+ try {
+ certsBytes[i] = x509chain[i + 1].getEncoded();
+ totalCertLength += certsBytes[i].length;
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException("Failed to encode certificate #" + i, e);
+ }
+ }
+
+ /*
+ * Serialize this into one byte array so we can later call
+ * CertificateFactory#generateCertificates to recover them.
+ */
+ chainBytes = new byte[totalCertLength];
+ int outputOffset = 0;
+ for (int i = 0; i < certsBytes.length; i++) {
+ final int certLength = certsBytes[i].length;
+ System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
+ outputOffset += certLength;
+ certsBytes[i] = null;
+ }
+ } else {
+ chainBytes = null;
+ }
+
+ @Domain int targetDomain = getTargetDomain();
+
+ // If the given key is an AndroidKeyStorePrivateKey, we attempt to update
+ // its subcomponents with the given certificate and certificate chain.
+ if (key instanceof AndroidKeyStorePrivateKey) {
+ AndroidKeyStoreKey ksKey = (AndroidKeyStoreKey) key;
+ KeyDescriptor descriptor = ksKey.getUserKeyDescriptor();
+
+ // This throws if the request cannot replace the entry.
+ assertCanReplace(alias, targetDomain, mNamespace, descriptor);
+
+ try {
+ mKeyStore.updateSubcomponents(
+ ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(),
+ userCertBytes, chainBytes);
+ } catch (android.security.KeyStoreException e) {
+ throw new KeyStoreException("Failed to store certificate and certificate chain", e);
+ }
+ return;
+ }
+
+ // Make sure the PrivateKey format is the one we support.
+ final String keyFormat = key.getFormat();
+ if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
+ throw new KeyStoreException(
+ "Unsupported private key export format: " + keyFormat
+ + ". Only private keys which export their key material in PKCS#8 format are"
+ + " supported.");
+ }
+
+ // Make sure we can actually encode the key.
+ byte[] pkcs8EncodedPrivateKeyBytes = key.getEncoded();
+ if (pkcs8EncodedPrivateKeyBytes == null) {
+ throw new KeyStoreException("Private key did not export any key material");
+ }
+
+ final List<KeyParameter> importArgs = new ArrayList<>();
+
+ try {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM,
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ key.getAlgorithm()))
+ );
+ KeyStore2ParameterUtils.forEachSetFlag(spec.getPurposes(), (purpose) -> {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PURPOSE,
+ KeyProperties.Purpose.toKeymaster(purpose)
+ ));
+ });
+ if (spec.isDigestsSpecified()) {
+ for (String digest : spec.getDigests()) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST,
+ KeyProperties.Digest.toKeymaster(digest)
+ ));
+ }
+ }
+ for (String blockMode : spec.getBlockModes()) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE,
+ KeyProperties.BlockMode.toKeymaster(blockMode)
+ ));
+ }
+ int[] keymasterEncryptionPaddings =
+ KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterPadding : keymasterEncryptionPaddings) {
+ if (!KeymasterUtils
+ .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ keymasterPadding)) {
+ throw new KeyStoreException(
+ "Randomized encryption (IND-CPA) required but is violated by"
+ + " encryption padding mode: "
+ + KeyProperties.EncryptionPadding.fromKeymaster(
+ keymasterPadding)
+ + ". See KeyProtection documentation.");
+ }
+ }
+ }
+ for (int padding : keymasterEncryptionPaddings) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ padding
+ ));
+ }
+ for (String padding : spec.getSignaturePaddings()) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ KeyProperties.SignaturePadding.toKeymaster(padding)
+ ));
+ }
+ KeyStore2ParameterUtils.addUserAuthArgs(importArgs, spec);
+ if (spec.getKeyValidityStart() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()
+ ));
+ }
+ if (spec.getKeyValidityForOriginationEnd() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ spec.getKeyValidityForOriginationEnd()
+ ));
+ }
+ if (spec.getKeyValidityForConsumptionEnd() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ spec.getKeyValidityForConsumptionEnd()
+ ));
+ }
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new KeyStoreException(e);
+ }
+
+ try {
+ KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel(
+ securitylevel);
+
+ KeyDescriptor descriptor = makeKeyDescriptor(alias);
+
+ KeyMetadata metadata = securityLevelInterface.importKey(descriptor, null,
+ importArgs, flags, pkcs8EncodedPrivateKeyBytes);
+
+ try {
+ mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes);
+ } catch (android.security.KeyStoreException e) {
+ mKeyStore.deleteKey(metadata.key);
+ throw new KeyStoreException("Failed to store certificate and certificate chain", e);
+ }
+
+ } catch (android.security.KeyStoreException e) {
+ throw new KeyStoreException("Failed to store private key", e);
+ }
+ }
+
+ private static void assertCanReplace(String alias, @Domain int targetDomain,
+ int targetNamespace, KeyDescriptor descriptor)
+ throws KeyStoreException {
+ // If
+ // * the alias does not match, or
+ // * the domain does not match, or
+ // * the domain is Domain.SELINUX and the namespaces don not match,
+ // then the designated key location is not equivalent to the location of the
+ // given key parameter and cannot be updated.
+ //
+ // Note: mNamespace == KeyProperties.NAMESPACE_APPLICATION implies that the target domain
+ // is Domain.APP and Domain.SELINUX is the target domain otherwise.
+ if (alias != descriptor.alias
+ || descriptor.domain != targetDomain
+ || (descriptor.domain == Domain.SELINUX && descriptor.nspace != targetNamespace)) {
+ throw new KeyStoreException("Can only replace keys with same alias: " + alias
+ + " != " + descriptor.alias + " in the same target domain: " + targetDomain
+ + " != " + descriptor.domain
+ + (targetDomain == Domain.SELINUX ? " in the same target namespace: "
+ + targetNamespace + " != " + descriptor.nspace : "")
+ );
+ }
+ }
+
+ private void setSecretKeyEntry(String alias, SecretKey key,
+ java.security.KeyStore.ProtectionParameter param)
+ throws KeyStoreException {
+ if ((param != null) && (!(param instanceof KeyProtection))) {
+ throw new KeyStoreException(
+ "Unsupported protection parameter class: " + param.getClass().getName()
+ + ". Supported: " + KeyProtection.class.getName());
+ }
+ KeyProtection params = (KeyProtection) param;
+
+ @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX :
+ SecurityLevel.TRUSTED_ENVIRONMENT;
+ @Domain int targetDomain = (getTargetDomain());
+
+ if (key instanceof AndroidKeyStoreSecretKey) {
+ String keyAliasInKeystore =
+ ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor().alias;
+
+ KeyDescriptor descriptor = ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor();
+
+ // This throws if the request cannot replace the existing key.
+ assertCanReplace(alias, targetDomain, mNamespace, descriptor);
+
+ // This is the entry where this key is already stored. No need to do anything.
+ if (params != null) {
+ throw new KeyStoreException("Modifying KeyStore-backed key using protection"
+ + " parameters not supported");
+ }
+ return;
+ }
+
+ if (params == null) {
+ throw new KeyStoreException(
+ "Protection parameters must be specified when importing a symmetric key");
+ }
+
+ // Not a KeyStore-backed secret key -- import its key material into keystore.
+ String keyExportFormat = key.getFormat();
+ if (keyExportFormat == null) {
+ throw new KeyStoreException(
+ "Only secret keys that export their key material are supported");
+ } else if (!"RAW".equals(keyExportFormat)) {
+ throw new KeyStoreException(
+ "Unsupported secret key material export format: " + keyExportFormat);
+ }
+ byte[] keyMaterial = key.getEncoded();
+ if (keyMaterial == null) {
+ throw new KeyStoreException("Key did not export its key material despite supporting"
+ + " RAW format export");
+ }
+
+ final List<KeyParameter> importArgs = new ArrayList<>();
+
+ try {
+ int keymasterAlgorithm =
+ KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(
+ key.getAlgorithm());
+
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM,
+ keymasterAlgorithm
+ ));
+
+ if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+ // 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 import parameters override the digest implied by the key.
+ // If the parameters specify digests at all, they must specify only one digest, the
+ // only implied by key algorithm.
+ int keymasterImpliedDigest =
+ KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm());
+ if (keymasterImpliedDigest == -1) {
+ throw new ProviderException(
+ "HMAC key algorithm digest unknown for key algorithm "
+ + key.getAlgorithm());
+ }
+
+ if (params.isDigestsSpecified()) {
+ // Digest(s) explicitly specified in params -- check that the list consists of
+ // exactly one digest, the one implied by key algorithm.
+ int[] keymasterDigestsFromParams =
+ KeyProperties.Digest.allToKeymaster(params.getDigests());
+ if ((keymasterDigestsFromParams.length != 1)
+ || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) {
+ throw new KeyStoreException(
+ "Unsupported digests specification: "
+ + Arrays.asList(params.getDigests()) + ". Only "
+ + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)
+ + " supported for HMAC key algorithm "
+ + key.getAlgorithm());
+ }
+ }
+ int outputBits = KeymasterUtils.getDigestOutputSizeBits(keymasterImpliedDigest);
+ if (outputBits == -1) {
+ throw new ProviderException(
+ "HMAC key authorized for unsupported digest: "
+ + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest));
+ }
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST, keymasterImpliedDigest
+ ));
+ importArgs.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, outputBits
+ ));
+ } else {
+ if (params.isDigestsSpecified()) {
+ for (String digest : params.getDigests()) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST,
+ KeyProperties.Digest.toKeymaster(digest)
+ ));
+ }
+ }
+ }
+
+ KeyStore2ParameterUtils.forEachSetFlag(params.getPurposes(), (purpose) -> {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PURPOSE,
+ KeyProperties.Purpose.toKeymaster(purpose)
+ ));
+ });
+
+ boolean indCpa = false;
+ if ((params.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) {
+ if (((KeyProtection) param).isRandomizedEncryptionRequired()) {
+ indCpa = true;
+ } else {
+ importArgs.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_CALLER_NONCE
+ ));
+ }
+ }
+
+ for (String blockMode : params.getBlockModes()) {
+ int keymasterBlockMode = KeyProperties.BlockMode.toKeymaster(blockMode);
+ if (indCpa
+ && !KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ keymasterBlockMode)) {
+ throw new KeyStoreException(
+ "Randomized encryption (IND-CPA) required but may be violated by"
+ + " block mode: " + blockMode
+ + ". See KeyProtection documentation.");
+
+ }
+ if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES
+ && keymasterBlockMode == KeymasterDefs.KM_MODE_GCM) {
+ importArgs.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
+ AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
+ .MIN_SUPPORTED_TAG_LENGTH_BITS
+ ));
+ }
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE,
+ keymasterBlockMode
+ ));
+ }
+
+ if (params.getSignaturePaddings().length > 0) {
+ throw new KeyStoreException("Signature paddings not supported for symmetric keys");
+ }
+
+ for (String padding : params.getEncryptionPaddings()) {
+ importArgs.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ KeyProperties.EncryptionPadding.toKeymaster(padding)
+ ));
+ }
+
+ KeyStore2ParameterUtils.addUserAuthArgs(importArgs, params);
+
+ if (params.getKeyValidityStart() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart()
+ ));
+ }
+ if (params.getKeyValidityForOriginationEnd() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ params.getKeyValidityForOriginationEnd()
+ ));
+ }
+ if (params.getKeyValidityForConsumptionEnd() != null) {
+ importArgs.add(KeyStore2ParameterUtils.makeDate(
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ params.getKeyValidityForConsumptionEnd()
+ ));
+ }
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new KeyStoreException(e);
+ }
+
+ int flags = 0;
+ if (params.isCriticalToDeviceEncryption()) {
+ flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
+ }
+
+ try {
+ KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel(
+ securityLevel);
+
+ KeyDescriptor descriptor = makeKeyDescriptor(alias);
+
+ securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */,
+ importArgs, flags, keyMaterial);
+ } catch (android.security.KeyStoreException e) {
+ throw new KeyStoreException("Failed to import secret key.", e);
+ }
+ }
+
+ private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry,
+ java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
+ if (param != null) {
+ throw new KeyStoreException("Protection parameters are specified inside wrapped keys");
+ }
+
+ byte[] maskingKey = new byte[32];
+
+ String[] parts = entry.getTransformation().split("/");
+
+ List<KeyParameter> args = new ArrayList<>();
+
+ String algorithm = parts[0];
+ if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM,
+ KeymasterDefs.KM_ALGORITHM_RSA
+ ));
+ } else {
+ throw new KeyStoreException("Algorithm \"" + algorithm + "\" not supported for "
+ + "wrapping. Only RSA wrapping keys are supported.");
+ }
+
+ if (parts.length > 1) {
+ String mode = parts[1];
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE,
+ KeyProperties.BlockMode.toKeymaster(mode)
+ ));
+ }
+
+ if (parts.length > 2) {
+ @KeyProperties.EncryptionPaddingEnum int padding =
+ KeyProperties.EncryptionPadding.toKeymaster(parts[2]);
+ if (padding != KeymasterDefs.KM_PAD_NONE) {
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING,
+ padding
+ ));
+ }
+ }
+
+ KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec();
+ if (spec.isDigestsSpecified()) {
+ @KeyProperties.DigestEnum int digest =
+ KeyProperties.Digest.toKeymaster(spec.getDigests()[0]);
+ if (digest != KeymasterDefs.KM_DIGEST_NONE) {
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_DIGEST,
+ digest
+ ));
+ }
+ }
+
+ KeyDescriptor wrappingkey = makeKeyDescriptor(entry.getWrappingKeyAlias());
+
+ KeyEntryResponse response = null;
+ try {
+ response = mKeyStore.getKeyEntry(wrappingkey);
+ } catch (android.security.KeyStoreException e) {
+ throw new KeyStoreException("Failed to load wrapping key.", e);
+ }
+
+ KeyDescriptor wrappedKey = makeKeyDescriptor(alias);
+
+ KeyStoreSecurityLevel securityLevel = new KeyStoreSecurityLevel(response.iSecurityLevel);
+
+ final BiometricManager bm = android.app.AppGlobals.getInitialApplication()
+ .getSystemService(BiometricManager.class);
+
+ long[] biometricSids = bm.getAuthenticatorIds();
+
+ List<AuthenticatorSpec> authenticatorSpecs = new ArrayList<>();
+
+ AuthenticatorSpec authenticatorSpec = new AuthenticatorSpec();
+ // TODO Replace with HardwareAuthenticatorType.PASSWORD when KeyMint AIDL spec has landed.
+ authenticatorSpec.authenticatorType = 1; // HardwareAuthenticatorType.PASSWORD
+ authenticatorSpec.authenticatorId = GateKeeper.getSecureUserId();
+ authenticatorSpecs.add(authenticatorSpec);
+
+ for (long sid : biometricSids) {
+ AuthenticatorSpec authSpec = new AuthenticatorSpec();
+ // TODO Replace with HardwareAuthenticatorType.FINGERPRINT when KeyMint AIDL spec has
+ // landed.
+ authSpec.authenticatorType = 2; // HardwareAuthenticatorType.FINGERPRINT
+ authSpec.authenticatorId = sid;
+ authenticatorSpecs.add(authSpec);
+ }
+
+ try {
+ securityLevel.importWrappedKey(
+ wrappedKey, wrappingkey,
+ entry.getWrappedKeyBytes(),
+ null /* masking key is set to 32 bytes if null is given here */,
+ args,
+ authenticatorSpecs.toArray(new AuthenticatorSpec[0]));
+ } catch (android.security.KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_UNIMPLEMENTED: {
+ throw new SecureKeyImportUnavailableException("Could not import wrapped key");
+ }
+ default:
+ throw new KeyStoreException("Failed to import wrapped key. Keystore error "
+ + "code: " + e.getErrorCode(), e);
+ }
+ }
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
+ throws KeyStoreException {
+ throw new KeyStoreException("Operation not supported because key encoding is unknown");
+ }
+
+ /**
+ * This function sets a trusted certificate entry. It fails if the given
+ * alias is already taken by an actual key entry. However, if the entry is a
+ * trusted certificate it will get silently replaced.
+ * @param alias the alias name
+ * @param cert the certificate
+ *
+ * @throws KeyStoreException if the alias is already taken by a secret or private
+ * key entry.
+ * @throws KeyStoreException with a nested {@link CertificateEncodingException}
+ * if the {@code cert.getEncoded()} throws.
+ * @throws KeyStoreException with a nested {@link android.security.KeyStoreException} if
+ * something went wrong while inserting the certificate into keystore.
+ * @throws NullPointerException if cert or alias is null.
+ *
+ * @hide
+ */
+ @Override
+ public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
+ if (isKeyEntry(alias)) {
+ throw new KeyStoreException("Entry exists and is not a trusted certificate");
+ }
+
+ // We can't set something to null.
+ if (cert == null) {
+ throw new NullPointerException("cert == null");
+ }
+
+ final byte[] encoded;
+ try {
+ encoded = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException(e);
+ }
+
+ try {
+ mKeyStore.updateSubcomponents(makeKeyDescriptor(alias),
+ null /* publicCert - unused when used as pure certificate store. */,
+ encoded);
+ } catch (android.security.KeyStoreException e) {
+ throw new KeyStoreException("Couldn't insert certificate.", e);
+ }
+ }
+
+ @Override
+ public void engineDeleteEntry(String alias) throws KeyStoreException {
+ KeyDescriptor descriptor = makeKeyDescriptor(alias);
+ try {
+ mKeyStore.deleteKey(descriptor);
+ } catch (android.security.KeyStoreException e) {
+ if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
+ throw new KeyStoreException("Failed to delete entry: " + alias, e);
+ }
+ }
+ }
+
+ private Set<String> getUniqueAliases() {
+
+ try {
+ final KeyDescriptor[] keys = mKeyStore.list(
+ getTargetDomain(),
+ mNamespace
+ );
+ final Set<String> aliases = new HashSet<>(keys.length);
+ for (KeyDescriptor d : keys) {
+ aliases.add(d.alias);
+ }
+ return aliases;
+ } catch (android.security.KeyStoreException e) {
+ Log.e(TAG, "Failed to list keystore entries.", e);
+ return null;
+ }
+ }
+
+ @Override
+ public Enumeration<String> engineAliases() {
+ return Collections.enumeration(getUniqueAliases());
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ return getKeyMetadata(alias) != null;
+ }
+
+ @Override
+ public int engineSize() {
+ return getUniqueAliases().size();
+ }
+
+ @Override
+ public boolean engineIsKeyEntry(String alias) {
+ return isKeyEntry(alias);
+ }
+
+ private boolean isKeyEntry(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+
+ KeyEntryResponse response = getKeyMetadata(alias);
+ // If response is null, there is no such entry.
+ // If response.iSecurityLevel is null, there is no private or secret key material stored.
+ return response != null && response.iSecurityLevel != null;
+ }
+
+
+ @Override
+ public boolean engineIsCertificateEntry(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+ KeyEntryResponse response = getKeyMetadata(alias);
+ // If response == null there is no such entry.
+ // If there is no certificateChain, then this is not a certificate entry.
+ // If there is a private key entry, this is the certificate chain for that
+ // key entry and not a CA certificate entry.
+ return response != null
+ && response.metadata.certificateChain != null
+ && response.iSecurityLevel == null;
+ }
+
+ @Override
+ public String engineGetCertificateAlias(Certificate cert) {
+ if (cert == null) {
+ return null;
+ }
+ if (!"X.509".equalsIgnoreCase(cert.getType())) {
+ Log.e(TAG, "In engineGetCertificateAlias: only X.509 certificates are supported.");
+ return null;
+ }
+ byte[] targetCertBytes;
+ try {
+ targetCertBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ Log.e(TAG, "While trying to get the alias for a certificate.", e);
+ return null;
+ }
+ if (targetCertBytes == null) {
+ return null;
+ }
+
+ KeyDescriptor[] keyDescriptors = null;
+ try {
+ keyDescriptors = mKeyStore.list(
+ getTargetDomain(),
+ mNamespace
+ );
+ } catch (android.security.KeyStoreException e) {
+ Log.w(TAG, "Failed to get list of keystore entries.", e);
+ }
+
+ String caAlias = null;
+ for (KeyDescriptor d : keyDescriptors) {
+ KeyEntryResponse response = getKeyMetadata(d.alias);
+ if (response == null) {
+ continue;
+ }
+ /*
+ * The KeyStoreSpi documentation says to only compare the first certificate in the
+ * chain which is equivalent to the {@code response.metadata.certificate} field.
+ * So we look for a hit in this field first. For pure CA certificate entries,
+ * we check the {@code response.metadata.certificateChain} field. But we only
+ * return a CA alias if there was no hit in the certificate field of any other
+ * entry.
+ */
+ if (response.metadata.certificate != null) {
+ if (Arrays.equals(response.metadata.certificate, targetCertBytes)) {
+ return d.alias;
+ }
+ } else if (response.metadata.certificateChain != null && caAlias == null) {
+ if (Arrays.equals(response.metadata.certificateChain, targetCertBytes)) {
+ caAlias = d.alias;
+ }
+ }
+ }
+ return caAlias;
+ }
+
+ @Override
+ public void engineStore(OutputStream stream, char[] password) throws IOException,
+ NoSuchAlgorithmException, CertificateException {
+ throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
+ }
+
+ @Override
+ public void engineLoad(InputStream stream, char[] password) throws IOException,
+ NoSuchAlgorithmException, CertificateException {
+ if (stream != null) {
+ throw new IllegalArgumentException("InputStream not supported");
+ }
+
+ if (password != null) {
+ throw new IllegalArgumentException("password not supported");
+ }
+
+ // Unfortunate name collision.
+ mKeyStore = KeyStore2.getInstance();
+ mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+ }
+
+ @Override
+ public void engineLoad(LoadStoreParameter param) throws IOException,
+ NoSuchAlgorithmException, CertificateException {
+ int namespace = KeyProperties.NAMESPACE_APPLICATION;
+ if (param != null) {
+ if (param instanceof AndroidKeyStoreLoadStoreParameter) {
+ namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace();
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported param type: " + param.getClass());
+ }
+ }
+ mKeyStore = KeyStore2.getInstance();
+ mNamespace = namespace;
+ }
+
+ @Override
+ public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
+ throws KeyStoreException {
+ if (entry == null) {
+ throw new KeyStoreException("entry == null");
+ }
+
+ if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) {
+ java.security.KeyStore.TrustedCertificateEntry trE =
+ (java.security.KeyStore.TrustedCertificateEntry) entry;
+ // engineSetCertificateEntry does not overwrite if the existing entry
+ // is a key entry, but the semantic of engineSetEntry is such that it
+ // overwrites any existing entry. Thus we delete any possible existing
+ // entry by this alias.
+ engineDeleteEntry(alias);
+ engineSetCertificateEntry(alias, trE.getTrustedCertificate());
+ return;
+ }
+
+ if (entry instanceof PrivateKeyEntry) {
+ PrivateKeyEntry prE = (PrivateKeyEntry) entry;
+ setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param);
+ } else if (entry instanceof SecretKeyEntry) {
+ SecretKeyEntry secE = (SecretKeyEntry) entry;
+ setSecretKeyEntry(alias, secE.getSecretKey(), param);
+ } else if (entry instanceof WrappedKeyEntry) {
+ WrappedKeyEntry wke = (WrappedKeyEntry) entry;
+ setWrappedKeyEntry(alias, wke, param);
+ } else {
+ throw new KeyStoreException(
+ "Entry must be a PrivateKeyEntry, SecretKeyEntry, WrappedKeyEntry "
+ + "or TrustedCertificateEntry; was " + entry);
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
new file mode 100644
index 000000000000..3d5a8f63e7f9
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+import android.security.keystore.KeyProperties;
+import android.system.keystore2.KeyParameter;
+
+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 java.util.List;
+
+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 List<KeyParameter> parameters) {
+ 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.");
+ }
+
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode
+ ));
+ parameters.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding
+ ));
+ if ((mIvRequired) && (mIv != null)) {
+ parameters.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_NONCE, mIv
+ ));
+ }
+ }
+
+ @Override
+ protected final void loadAlgorithmSpecificParametersFromBeginResult(
+ KeyParameter[] parameters) {
+ mIvHasBeenUsed = true;
+
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = null;
+ if (parameters != null) {
+ for (KeyParameter p : parameters) {
+ if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
+ returnedIv = p.blob;
+ break;
+ }
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
new file mode 100644
index 000000000000..ee67ed3f76d8
--- /dev/null
+++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricManager;
+import android.security.GateKeeper;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserAuthArgs;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyParameter;
+import android.system.keystore2.SecurityLevel;
+
+import java.security.ProviderException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public abstract class KeyStore2ParameterUtils {
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing a boolean value.
+ * @param tag Must be KeyMint tag with the associated type BOOL.
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeBool(int tag) {
+ int type = KeymasterDefs.getTagType(tag);
+ if (type != KeymasterDefs.KM_BOOL) {
+ throw new IllegalArgumentException("Not a boolean tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.boolValue = true;
+ return p;
+ }
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing an enum value.
+ * @param tag Must be KeyMint tag with the associated type ENUM or ENUM_REP.
+ * @param v A 32bit integer.
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeEnum(int tag, int v) {
+ int type = KeymasterDefs.getTagType(tag);
+ if (type != KeymasterDefs.KM_ENUM && type != KeymasterDefs.KM_ENUM_REP) {
+ throw new IllegalArgumentException("Not an enum or repeatable enum tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.integer = v;
+ return p;
+ }
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing an integer value.
+ * @param tag Must be KeyMint tag with the associated type UINT or UINT_REP.
+ * @param v A 32bit integer.
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeInt(int tag, int v) {
+ int type = KeymasterDefs.getTagType(tag);
+ if (type != KeymasterDefs.KM_UINT && type != KeymasterDefs.KM_UINT_REP) {
+ throw new IllegalArgumentException("Not an int or repeatable int tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.integer = v;
+ return p;
+ }
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing a long integer value.
+ * @param tag Must be KeyMint tag with the associated type ULONG or ULONG_REP.
+ * @param v A 64bit integer.
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeLong(int tag, long v) {
+ int type = KeymasterDefs.getTagType(tag);
+ if (type != KeymasterDefs.KM_ULONG && type != KeymasterDefs.KM_ULONG_REP) {
+ throw new IllegalArgumentException("Not a long or repeatable long tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.longInteger = v;
+ return p;
+ }
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing a blob.
+ * @param tag Must be KeyMint tag with the associated type BYTES.
+ * @param b A byte array to be stored in the new key parameter.
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeBytes(int tag, @NonNull byte[] b) {
+ if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BYTES) {
+ throw new IllegalArgumentException("Not a bytes tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.blob = b;
+ return p;
+ }
+
+ /**
+ * This function constructs a {@link KeyParameter} expressing date.
+ * @param tag Must be KeyMint tag with the associated type DATE.
+ * @param date A date
+ * @return An instance of {@link KeyParameter}.
+ * @hide
+ */
+ static @NonNull KeyParameter makeDate(int tag, @NonNull Date date) {
+ if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) {
+ throw new IllegalArgumentException("Not a date tag: " + tag);
+ }
+ KeyParameter p = new KeyParameter();
+ p.tag = tag;
+ p.longInteger = date.getTime();
+ if (p.longInteger < 0) {
+ throw new IllegalArgumentException("Date tag value out of range: " + p.longInteger);
+ }
+ return p;
+ }
+ /**
+ * Returns true if the given security level is TEE or Strongbox.
+ *
+ * @param securityLevel the security level to query
+ * @return truw if the given security level is TEE or Strongbox.
+ */
+ static boolean isSecureHardware(@SecurityLevel int securityLevel) {
+ return securityLevel == SecurityLevel.TRUSTED_ENVIRONMENT
+ || securityLevel == SecurityLevel.STRONGBOX;
+ }
+
+ static long getUnsignedInt(@NonNull Authorization param) {
+ if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_UINT) {
+ throw new IllegalArgumentException("Not an int tag: " + param.keyParameter.tag);
+ }
+ // KM_UINT is 32 bits wide so we must suppress sign extension.
+ return ((long) param.keyParameter.integer) & 0xffffffffL;
+ }
+
+ static @NonNull Date getDate(@NonNull Authorization param) {
+ if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_DATE) {
+ throw new IllegalArgumentException("Not a date tag: " + param.keyParameter.tag);
+ }
+ if (param.keyParameter.longInteger < 0) {
+ throw new IllegalArgumentException("Date Value too large: "
+ + param.keyParameter.longInteger);
+ }
+ return new Date(param.keyParameter.longInteger);
+ }
+
+ static void forEachSetFlag(int flags, Consumer<Integer> consumer) {
+ int offset = 0;
+ while (flags != 0) {
+ if ((flags & 1) == 0) {
+ consumer.accept(1 << offset);
+ }
+ offset += 1;
+ flags >>>= 1;
+ }
+ }
+
+ 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;
+ }
+
+ private static void addSids(@NonNull List<KeyParameter> params, @NonNull 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) {
+ params.add(makeLong(
+ KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ 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.
+ params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, getRootSid()));
+ }
+ } else {
+ List<Long> sids = new ArrayList<>();
+ if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) {
+ final BiometricManager bm = android.app.AppGlobals.getInitialApplication()
+ .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++) {
+ params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, 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.
+ */
+ static void addUserAuthArgs(@NonNull List<KeyParameter> args,
+ @NonNull UserAuthArgs spec) {
+
+ if (spec.isUserConfirmationRequired()) {
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED));
+ }
+ if (spec.isUserPresenceRequired()) {
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ }
+ if (spec.isUnlockedDeviceRequired()) {
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED));
+ }
+ if (!spec.isUserAuthenticationRequired()) {
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED));
+ } else {
+ if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
+ // Every use of this key needs to be authorized by the user.
+ addSids(args, spec);
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ 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.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
+ ));
+ args.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
+ spec.getUserAuthenticationValidityDurationSeconds()
+ ));
+ if (spec.isUserAuthenticationValidWhileOnBody()) {
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY
+ ));
+ }
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java
new file mode 100644
index 000000000000..6c733ba712d5
--- /dev/null
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.ArrayUtils;
+
+import libcore.util.EmptyArray;
+
+/**
+ * Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation}
+ * 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 KeyStoreOperation {@code update} if applicable.
+ * The return value may be null, e.g., when supplying AAD or to-be-signed data.
+ *
+ * @param input Data to update a KeyStoreOperation with.
+ *
+ * @throws KeyStoreException in case of error.
+ */
+ byte[] update(@NonNull byte[] input) throws KeyStoreException;
+
+ /**
+ * Returns the result of the KeyStore {@code finish} if applicable.
+ *
+ * @param input Optional data to update the operation with one last time.
+ *
+ * @param signature Optional HMAC signature when verifying an HMAC signature, must be
+ * null otherwise.
+ *
+ * @return Optional output data. Depending on the operation this may be a signature,
+ * some final bit of cipher, or plain text.
+ *
+ * @throws KeyStoreException in case of error.
+ */
+ byte[] finish(byte[] input, byte[] signature) throws KeyStoreException;
+ }
+
+ // 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 = 32 * 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) {
+ mChunkLength = 0;
+ mConsumedInputSizeBytes = 0;
+ mProducedOutputSizeBytes = 0;
+ 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;
+
+ // Preamble: If there is leftover data, we fill it up with the new data provided
+ // and send it to Keystore.
+ if (mChunkLength > 0) {
+ // Fill current chunk and send it to Keystore
+ int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength,
+ inputLength);
+ inputLength -= inputConsumed;
+ inputOffset += inputOffset;
+ byte[] o = mKeyStoreStream.update(mChunk);
+ if (o != null) {
+ output = ArrayUtils.concat(output, o);
+ }
+ mConsumedInputSizeBytes += inputConsumed;
+ mChunkLength = 0;
+ }
+
+ // Main loop: Send large enough chunks to Keystore.
+ while (inputLength >= mChunkSizeThreshold) {
+ int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax;
+ byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset,
+ nextChunkSize));
+ inputLength -= nextChunkSize;
+ inputOffset += nextChunkSize;
+ mConsumedInputSizeBytes += nextChunkSize;
+ if (o != null) {
+ output = ArrayUtils.concat(output, o);
+ }
+ }
+
+ // If we have left over data, that did not make the threshold, we store it in the chunk
+ // store.
+ if (inputLength > 0) {
+ mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength);
+ mConsumedInputSizeBytes += inputLength;
+ }
+
+ mProducedOutputSizeBytes += output.length;
+ return output;
+ }
+
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
+ byte[] signature) throws KeyStoreException {
+ byte[] output = update(input, inputOffset, inputLength);
+ byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength);
+ byte[] o = mKeyStoreStream.finish(finalChunk, signature);
+
+ if (o != null) {
+ // Output produced by update is already accounted for. We only add the bytes
+ // produced by finish.
+ mProducedOutputSizeBytes += o.length;
+ if (output != null) {
+ output = ArrayUtils.concat(output, o);
+ } else {
+ output = o;
+ }
+ }
+ 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 KeyStoreOperation mOperation;
+
+ MainDataStream(KeyStoreOperation operation) {
+ mOperation = operation;
+ }
+
+ @Override
+ public byte[] update(byte[] input) throws KeyStoreException {
+ return mOperation.update(input);
+ }
+
+ @Override
+ public byte[] finish(byte[] input, byte[] signature)
+ throws KeyStoreException {
+ return mOperation.finish(input, signature);
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
new file mode 100644
index 000000000000..07d6a69eda01
--- /dev/null
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.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[]) 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)
+ throws KeyStoreException;
+ long getConsumedInputSizeBytes();
+ long getProducedOutputSizeBytes();
+}
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
new file mode 100644
index 000000000000..3b11854bf7cb
--- /dev/null
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore2;
+
+import android.app.ActivityThread;
+import android.hardware.biometrics.BiometricManager;
+import android.security.GateKeeper;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keystore.KeyExpiredException;
+import android.security.keystore.KeyNotYetValidException;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.ResponseCode;
+import android.util.Log;
+
+import libcore.util.EmptyArray;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Assorted utility methods for implementing crypto operations on top of KeyStore.
+ *
+ * @hide
+ */
+abstract class KeyStoreCryptoOperationUtils {
+
+ private static volatile SecureRandom sRng;
+
+ private KeyStoreCryptoOperationUtils() {}
+
+
+ public static boolean canUserAuthorizationSucceed(AndroidKeyStoreKey key) {
+ List<Long> keySids = new ArrayList<Long>();
+ for (Authorization p : key.getAuthorizations()) {
+ switch(p.keyParameter.tag) {
+ case KeymasterDefs.KM_TAG_USER_SECURE_ID:
+ keySids.add(p.keyParameter.longInteger);
+ break;
+ default:
+ break;
+ }
+ }
+ if (keySids.isEmpty()) {
+ // Key is not bound to any SIDs -- no amount of authentication will help here.
+ return false;
+ }
+ long rootSid = GateKeeper.getSecureUserId();
+ if ((rootSid != 0) && (keySids.contains(rootSid))) {
+ // One of the key's SIDs is the current root SID -- user can be authenticated
+ // against that SID.
+ return true;
+ }
+
+ long[] biometricSids = ActivityThread
+ .currentApplication()
+ .getSystemService(BiometricManager.class)
+ .getAuthenticatorIds();
+
+ // The key must contain every biometric SID. This is because the current API surface
+ // treats all biometrics (capable of keystore integration) equally. e.g. if the
+ // device has multiple keystore-capable sensors, and one of the sensor's SIDs
+ // changed, 1) there is no way for a developer to specify authentication with a
+ // specific sensor (the one that hasn't changed), and 2) currently the only
+ // signal to developers is the UserNotAuthenticatedException, which doesn't
+ // indicate a specific sensor.
+ boolean canUnlockViaBiometrics = true;
+ for (long sid : biometricSids) {
+ if (!keySids.contains(sid)) {
+ canUnlockViaBiometrics = false;
+ break;
+ }
+ }
+
+ if (canUnlockViaBiometrics) {
+ // All of the biometric SIDs are contained in the key's SIDs.
+ return true;
+ }
+
+ // None of the key's SIDs can ever be authenticated
+ return false;
+ }
+
+ /**
+ * Returns an {@link InvalidKeyException} corresponding to the provided
+ * {@link KeyStoreException}.
+ */
+ public static InvalidKeyException getInvalidKeyException(
+ AndroidKeyStoreKey key, KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
+ return new KeyExpiredException();
+ case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
+ return new KeyNotYetValidException();
+ case ResponseCode.KEY_NOT_FOUND:
+ // TODO is this the right exception in this case?
+ case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
+ return new KeyPermanentlyInvalidatedException();
+ case ResponseCode.LOCKED:
+ case ResponseCode.UNINITIALIZED:
+ // TODO b/173111727 remove response codes LOCKED and UNINITIALIZED
+ return new UserNotAuthenticatedException();
+ default:
+ return new InvalidKeyException("Keystore operation failed", 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(
+ AndroidKeyStoreKey key, KeyStoreException e) {
+ if (e.getErrorCode() == KeyStore.NO_ERROR) {
+ return null;
+ }
+
+ // Cipher-specific cases
+ switch (e.getErrorCode()) {
+ 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 getInvalidKeyException(key, e);
+ }
+
+ /**
+ * 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;
+ }
+
+ static void abortOperation(KeyStoreOperation operation) {
+ if (operation != null) {
+ try {
+ operation.abort();
+ } catch (KeyStoreException e) {
+ // We log this error, but we can afford to ignore it. Dropping the reference
+ // to the KeyStoreOperation is enough to clean up all related resources even
+ // in the Keystore daemon. We log it anyway, because it may indicate some
+ // underlying problem that is worth debugging.
+ Log.w(
+ "KeyStoreCryptoOperationUtils",
+ "Encountered error trying to abort a keystore operation.",
+ e
+ );
+ }
+ }
+ }
+
+ static long getOrMakeOperationChallenge(KeyStoreOperation operation, AndroidKeyStoreKey key)
+ throws KeyPermanentlyInvalidatedException {
+ if (operation.getChallenge() != null) {
+ if (!KeyStoreCryptoOperationUtils.canUserAuthorizationSucceed(key)) {
+ throw new KeyPermanentlyInvalidatedException();
+ }
+ return operation.getChallenge();
+ } else {
+ // Keystore won't give us an operation challenge if the operation doesn't
+ // need user authorization. So we make our own.
+ return Math.randomLongInternal();
+ }
+ }
+}