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

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

Change-Id: Ia29166a47d7ed4cdde3df5df28322120e684d9de
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Seth Moore
2022-04-21 17:53:03 +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; package android.security;
import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -24,6 +26,8 @@ import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -57,6 +61,21 @@ public class GenerateRkpKey {
private Context mContext; private Context mContext;
private CountDownLatch mCountDownLatch; 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() { private ServiceConnection mConnection = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
@@ -81,12 +100,14 @@ public class GenerateRkpKey {
mContext = context; 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()); Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
int returnCode = IGenerateRkpKeyService.Status.OK;
if (comp == null) { if (comp == null) {
// On a system that does not use RKP, the RemoteProvisioner app won't be installed. // On a system that does not use RKP, the RemoteProvisioner app won't be installed.
return; return returnCode;
} }
intent.setComponent(comp); intent.setComponent(comp);
mCountDownLatch = new CountDownLatch(1); mCountDownLatch = new CountDownLatch(1);
@@ -102,7 +123,7 @@ public class GenerateRkpKey {
if (mBinder != null) { if (mBinder != null) {
switch (command) { switch (command) {
case NOTIFY_EMPTY: case NOTIFY_EMPTY:
mBinder.generateKey(securityLevel); returnCode = mBinder.generateKey(securityLevel);
break; break;
case NOTIFY_KEY_GENERATED: case NOTIFY_KEY_GENERATED:
mBinder.notifyKeyGenerated(securityLevel); mBinder.notifyKeyGenerated(securityLevel);
@@ -112,16 +133,21 @@ public class GenerateRkpKey {
} }
} else { } else {
Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService."); Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
} }
mContext.unbindService(mConnection); mContext.unbindService(mConnection);
return returnCode;
} }
/** /**
* Fulfills the use case of (2) described in the class documentation. Blocks until the * 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. * 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 { @CheckResult
bindAndSendCommand(NOTIFY_EMPTY, securityLevel); @Status
public int notifyEmpty(int securityLevel) throws RemoteException {
return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
} }
/** /**

View File

@@ -26,11 +26,35 @@ package android.security;
* @hide * @hide
*/ */
interface IGenerateRkpKeyService { 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 * 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. * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
*/ */
oneway void notifyKeyGenerated(in int securityLevel); 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: case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
return new KeyStoreException(errorCode, "Key permanently invalidated", return new KeyStoreException(errorCode, "Key permanently invalidated",
serviceErrorMessage); 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: default:
return new KeyStoreException(errorCode, String.valueOf(errorCode), return new KeyStoreException(errorCode, String.valueOf(errorCode),
serviceErrorMessage); serviceErrorMessage);

View File

@@ -28,6 +28,7 @@ import android.hardware.security.keymint.Tag;
import android.os.Build; import android.os.Build;
import android.os.RemoteException; import android.os.RemoteException;
import android.security.GenerateRkpKey; import android.security.GenerateRkpKey;
import android.security.IGenerateRkpKeyService;
import android.security.KeyPairGeneratorSpec; import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2; import android.security.KeyStore2;
import android.security.KeyStoreException; import android.security.KeyStoreException;
@@ -624,7 +625,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
* GenerateRkpKey.notifyEmpty() will delay for a while before returning. * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
*/ */
result = generateKeyPairHelper(); result = generateKeyPairHelper();
if (result.rkpStatus == KeyStoreException.RKP_SUCCESS) { if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
return result.keyPair; return result.keyPair;
} }
} }
@@ -706,27 +707,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
success = true; success = true;
KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey()); KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
return new GenerateKeyPairHelperResult(0, kp); return new GenerateKeyPairHelperResult(0, kp);
} catch (android.security.KeyStoreException e) { } catch (KeyStoreException e) {
switch (e.getErrorCode()) { switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e); throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
case ResponseCode.OUT_OF_KEYS: case ResponseCode.OUT_OF_KEYS:
GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread throw makeOutOfKeysException(e, securityLevel);
.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);
default: default:
ProviderException p = new ProviderException("Failed to generate key pair.", e); ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { 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) private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge(); byte[] challenge = mSpec.getAttestationChallenge();