blob: 3ef4aa5b7ec30c34659f4321b2d2d88c205ddd25 [file] [log] [blame]
/*
* 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.hardware.security.keymint.KeyParameter;
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.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();
}
}
}