Loading keystore/java/android/security/GenerateRkpKey.java +31 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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); Loading @@ -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); } /** Loading keystore/java/android/security/IGenerateRkpKeyService.aidl +26 −2 Original line number Diff line number Diff line Loading @@ -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); } keystore/java/android/security/KeyStore2.java +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +50 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } } Loading Loading @@ -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) { Loading @@ -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(); Loading Loading
keystore/java/android/security/GenerateRkpKey.java +31 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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); Loading @@ -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); } /** Loading
keystore/java/android/security/IGenerateRkpKeyService.aidl +26 −2 Original line number Diff line number Diff line Loading @@ -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); }
keystore/java/android/security/KeyStore2.java +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +50 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } } Loading Loading @@ -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) { Loading @@ -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(); Loading