diff options
Diffstat (limited to 'keystore/java/android/security/KeyStoreSecurityLevel.java')
-rw-r--r-- | keystore/java/android/security/KeyStoreSecurityLevel.java | 217 |
1 files changed, 217 insertions, 0 deletions
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(); + } + } +} |