Merge "Make generateKey() return a status" am: d6dbf31c61 am: 34eb18107d am: c6030f0765

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2048789

Change-Id: Ib86d332d64f0fae0d83d52a6e55d9dfc25c4f06b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Seth Moore
2022-04-21 18:25:10 +00:00
committed by Automerger Merge Worker
4 changed files with 113 additions and 25 deletions

View File

@@ -16,6 +16,8 @@
package android.security;
import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +26,8 @@ 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;
@@ -57,6 +61,21 @@ public class GenerateRkpKey {
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) {
@@ -81,12 +100,14 @@ public class GenerateRkpKey {
mContext = context;
}
private void bindAndSendCommand(int command, int securityLevel) throws RemoteException {
@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;
return returnCode;
}
intent.setComponent(comp);
mCountDownLatch = new CountDownLatch(1);
@@ -102,7 +123,7 @@ public class GenerateRkpKey {
if (mBinder != null) {
switch (command) {
case NOTIFY_EMPTY:
mBinder.generateKey(securityLevel);
returnCode = mBinder.generateKey(securityLevel);
break;
case NOTIFY_KEY_GENERATED:
mBinder.notifyKeyGenerated(securityLevel);
@@ -112,16 +133,21 @@ public class GenerateRkpKey {
}
} 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
*/
public void notifyEmpty(int securityLevel) throws RemoteException {
bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
@CheckResult
@Status
public int notifyEmpty(int securityLevel) throws RemoteException {
return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
}
/**

View File

@@ -26,11 +26,35 @@ package android.security;
* @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. */
void generateKey(in int securityLevel);
/**
* Ping the provisioner service to indicate there are no remaining attestation keys left.
*/
Status generateKey(in int securityLevel);
}

View File

@@ -345,6 +345,12 @@ public class KeyStore2 {
case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
return new KeyStoreException(errorCode, "Key permanently invalidated",
serviceErrorMessage);
case ResponseCode.OUT_OF_KEYS:
// Getting a more specific RKP status requires the security level, which we
// don't have here. Higher layers of the stack can interpret this exception
// and add more flavor.
return new KeyStoreException(errorCode, serviceErrorMessage,
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
default:
return new KeyStoreException(errorCode, String.valueOf(errorCode),
serviceErrorMessage);

View File

@@ -28,6 +28,7 @@ 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;
@@ -624,7 +625,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
* GenerateRkpKey.notifyEmpty() will delay for a while before returning.
*/
result = generateKeyPairHelper();
if (result.rkpStatus == KeyStoreException.RKP_SUCCESS) {
if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
return result.keyPair;
}
}
@@ -706,27 +707,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
success = true;
KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
return new GenerateKeyPairHelperResult(0, kp);
} catch (android.security.KeyStoreException e) {
} 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:
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
.currentApplication());
try {
//TODO: When detailed error information is available from the remote
//provisioner, propagate it up.
keyGen.notifyEmpty(securityLevel);
} catch (RemoteException f) {
KeyStoreException ksException = new KeyStoreException(
ResponseCode.OUT_OF_KEYS,
"Remote exception: " + f.getMessage(),
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
throw new ProviderException("Failed to talk to RemoteProvisioner",
ksException);
}
return new GenerateKeyPairHelperResult(
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
throw makeOutOfKeysException(e, securityLevel);
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
@@ -752,6 +738,52 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
// In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
// some keys.
private ProviderException makeOutOfKeysException(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:
// This will actually retry once immediately, so on "OK" go ahead and return
// "temporarily unavailable". @see generateKeyPair
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);
return new ProviderException("Failed to talk to RemoteProvisioner", ksException);
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();