Merge "Make generateKey() return a status" am: d6dbf31c61
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2048789 Change-Id: Ie70204d5eb098d57f5238f7da105b84365ee7c7c 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;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user