Skip to content
Snippets Groups Projects
Commit 651ffe62 authored by Seth Moore's avatar Seth Moore
Browse files

Remove the GenerateRkpKey service

With the move to rkpd, we no longer need to make calls from framework
into the remote provisioner to tell it that a key was consumed.

Bug: 274823784
Test: atest KeystoreTests
Test: atest CtsKeystoreTestCases:android.keystore.cts.KeyAttestationTest
Change-Id: I510d471a980c62e5798e459729f73c231321d2a9
parent 932fe8ac
No related branches found
No related tags found
No related merge requests found
......@@ -21,10 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.GenerateRkpKey;
import android.security.keymaster.KeymasterDefs;
class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
......@@ -125,18 +122,7 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
@NonNull String docType) throws AlreadyPersonalizedException,
DocTypeNotSupportedException {
try {
IWritableCredential wc;
wc = mStore.createCredential(credentialName, docType);
try {
GenerateRkpKey keyGen = new GenerateRkpKey(mContext);
// We don't know what the security level is for the backing keymint, so go ahead and
// poke the provisioner for both TEE and SB.
keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX);
} catch (RemoteException e) {
// Not really an error state. Does not apply at all if RKP is unsupported or
// disabled on a given device.
}
IWritableCredential wc = mStore.createCredential(credentialName, docType);
return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
......
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security;
import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
* app. There are two cases where Keystore should use this class.
*
* (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
* RemoteProvisioner app check if the state of the attestation key pool is getting low enough
* to warrant provisioning more attestation certificates early.
*
* (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
* attestation key pairs and cannot provide one for the given application. Keystore can then
* make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
* attestation certificate chain provisioned.
*
* In most cases, the proper usage of (1) should preclude the need for (2).
*
* @hide
*/
public class GenerateRkpKey {
private static final String TAG = "GenerateRkpKey";
private static final int NOTIFY_EMPTY = 0;
private static final int NOTIFY_KEY_GENERATED = 1;
private static final int TIMEOUT_MS = 1000;
private IGenerateRkpKeyService mBinder;
private Context mContext;
private CountDownLatch mCountDownLatch;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
IGenerateRkpKeyService.Status.OK,
IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY,
IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR,
IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED,
IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR,
IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR,
IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR,
IGenerateRkpKeyService.Status.INTERNAL_ERROR,
})
public @interface Status {
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
mCountDownLatch.countDown();
}
@Override public void onBindingDied(ComponentName className) {
mCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName className) {
mBinder = null;
}
};
/**
* Constructor which takes a Context object.
*/
public GenerateRkpKey(Context context) {
mContext = context;
}
@Status
private int bindAndSendCommand(int command, int securityLevel) throws RemoteException {
Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
int returnCode = IGenerateRkpKeyService.Status.OK;
if (comp == null) {
// On a system that does not use RKP, the RemoteProvisioner app won't be installed.
return returnCode;
}
intent.setComponent(comp);
mCountDownLatch = new CountDownLatch(1);
Executor executor = Executors.newCachedThreadPool();
if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) {
throw new RemoteException("Failed to bind to GenerateRkpKeyService");
}
try {
mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted: ", e);
}
if (mBinder != null) {
switch (command) {
case NOTIFY_EMPTY:
returnCode = mBinder.generateKey(securityLevel);
break;
case NOTIFY_KEY_GENERATED:
mBinder.notifyKeyGenerated(securityLevel);
break;
default:
Log.e(TAG, "Invalid case for command");
}
} else {
Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
}
mContext.unbindService(mConnection);
return returnCode;
}
/**
* Fulfills the use case of (2) described in the class documentation. Blocks until the
* RemoteProvisioner application can get new attestation keys signed by the server.
* @return the status of the key generation
*/
@CheckResult
@Status
public int notifyEmpty(int securityLevel) throws RemoteException {
return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
}
/**
* Fulfills the use case of (1) described in the class documentation. Non blocking call.
*/
public void notifyKeyGenerated(int securityLevel) throws RemoteException {
bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
}
}
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security;
/**
* Thrown on problems in attempting to attest to a key using a remotely provisioned key.
*
* @hide
*/
public class GenerateRkpKeyException extends Exception {
/**
* Constructs a new {@code GenerateRkpKeyException}.
*/
public GenerateRkpKeyException() {
}
}
/**
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security;
/**
* Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
* will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
* attestation request. The framework can then synchronously call generateKey() to get more
* attestation keys generated and signed. Upon return, the caller can be certain an attestation key
* is available.
*
* @hide
*/
interface IGenerateRkpKeyService {
@JavaDerive(toString=true)
@Backing(type="int")
enum Status {
/** No error(s) occurred */
OK = 0,
/** Unable to provision keys due to a lack of internet connectivity. */
NO_NETWORK_CONNECTIVITY = 1,
/** An error occurred while communicating with the RKP server. */
NETWORK_COMMUNICATION_ERROR = 2,
/** The given device was not registered with the RKP backend. */
DEVICE_NOT_REGISTERED = 4,
/** The RKP server returned an HTTP client error, indicating a misbehaving client. */
HTTP_CLIENT_ERROR = 5,
/** The RKP server returned an HTTP server error, indicating something went wrong on the server. */
HTTP_SERVER_ERROR = 6,
/** The RKP server returned an HTTP status that is unknown. This should never happen. */
HTTP_UNKNOWN_ERROR = 7,
/** An unexpected internal error occurred. This should never happen. */
INTERNAL_ERROR = 8,
}
/**
* Ping the provisioner service to let it know an app generated a key. This may or may not have
* consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
*/
oneway void notifyKeyGenerated(in int securityLevel);
/**
* Ping the provisioner service to indicate there are no remaining attestation keys left.
*/
Status generateKey(in int securityLevel);
}
......@@ -20,7 +20,6 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
......@@ -28,9 +27,6 @@ import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
import android.os.RemoteException;
import android.security.GenerateRkpKey;
import android.security.IGenerateRkpKeyService;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
......@@ -621,45 +617,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
@Override
public KeyPair generateKeyPair() {
GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
for (int i = 0; i < 2; i++) {
/**
* NOTE: There is no need to delay between re-tries because the call to
* GenerateRkpKey.notifyEmpty() will delay for a while before returning.
*/
result = generateKeyPairHelper();
if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
return result.keyPair;
}
}
// RKP failure
if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
"Could not get RKP keys", result.rkpStatus);
throw new ProviderException("Failed to provision new attestation keys.", ksException);
}
return result.keyPair;
}
private static class GenerateKeyPairHelperResult {
// Zero indicates success, non-zero indicates failure. Values should be
// {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
// {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
// {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
// {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
public final int rkpStatus;
@Nullable
public final KeyPair keyPair;
private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
this.rkpStatus = rkpStatus;
this.keyPair = keyPair;
}
}
private GenerateKeyPairHelperResult generateKeyPairHelper() {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
......@@ -697,26 +654,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
if (mSpec.getAttestationChallenge() != null) {
keyGen.notifyKeyGenerated(securityLevel);
}
} catch (RemoteException e) {
// This is not really an error state, and necessarily does not apply to non RKP
// systems or hybrid systems where RKP is not currently turned on.
Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
}
success = true;
KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
return new GenerateKeyPairHelperResult(0, kp);
return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
case ResponseCode.OUT_OF_KEYS:
return checkIfRetryableOrThrow(e, securityLevel);
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
......@@ -742,55 +685,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
// In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
// some keys.
GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) {
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
KeyStoreException ksException;
try {
final int keyGenStatus = keyGen.notifyEmpty(securityLevel);
// Default stance: temporary error. This is a hint to the caller to try again with
// exponential back-off.
int rkpStatus;
switch (keyGenStatus) {
case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY:
rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY;
break;
case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED:
rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE;
break;
case IGenerateRkpKeyService.Status.OK:
// Explicitly return not-OK here so we retry in generateKeyPair. All other cases
// should throw because a retry doesn't make sense if we didn't actually
// provision fresh keys.
return new GenerateKeyPairHelperResult(
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR:
case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR:
case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR:
case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR:
case IGenerateRkpKeyService.Status.INTERNAL_ERROR:
default:
// These errors really should never happen. The best we can do is assume they
// are transient and hint to the caller to retry with back-off.
rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE;
break;
}
ksException = new KeyStoreException(
ResponseCode.OUT_OF_KEYS,
"Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus,
rkpStatus);
} catch (RemoteException f) {
ksException = new KeyStoreException(
ResponseCode.OUT_OF_KEYS,
"Remote exception: " + f.getMessage(),
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
}
ksException.initCause(e);
throw new ProviderException("Failed to provision new attestation keys.", ksException);
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment