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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user