summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt6
-rw-r--r--core/api/current.txt6
-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/BackendBusyException.java52
7 files changed, 732 insertions, 0 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/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/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);
+ }
+
+}