diff --git a/core/java/android/security/rkp/IGetKeyCallback.aidl b/core/java/android/security/rkp/IGetKeyCallback.aidl index 85ceae62aadc0..a36d054f7ed4c 100644 --- a/core/java/android/security/rkp/IGetKeyCallback.aidl +++ b/core/java/android/security/rkp/IGetKeyCallback.aidl @@ -25,6 +25,36 @@ import android.security.rkp.RemotelyProvisionedKey; * @hide */ oneway interface IGetKeyCallback { + enum ErrorCode { + /** + * An unexpected error occurred and there's no standard way to describe it. See the + * corresponding error string for more information. + */ + ERROR_UNKNOWN = 1, + + /** + * Device will not receive remotely provisioned keys because it's running vulnerable + * code. The device needs to be updated to a fixed build to recover. + */ + ERROR_REQUIRES_SECURITY_PATCH = 2, + + /** + * Indicates that the attestation key pool has been exhausted, and the remote key + * provisioning server cannot currently be reached. Clients should wait for the + * device to have connectivity, then retry. + */ + ERROR_PENDING_INTERNET_CONNECTIVITY = 3, + + /** + * Indicates that this device will never be able to provision attestation keys using + * the remote provsisioning server. This may be due to multiple causes, such as the + * device is not registered with the remote provisioning backend or the device has + * been permanently revoked. Clients who receive this error should not attempt to + * retry key creation. + */ + ERROR_PERMANENT = 5, + } + /** * Called in response to {@link IRegistration.getKey}, indicating * a remotely-provisioned key is available. @@ -42,8 +72,9 @@ oneway interface IGetKeyCallback { /** * Called when an error has occurred while trying to get a remotely provisioned key. * - * @param error A description of what failed, suitable for logging. + * @param error allows code to handle certain errors, if desired + * @param description human-readable explanation of what failed, suitable for logging. */ - void onError(String error); + void onError(ErrorCode error, String description); } diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java index 0e92709e25f6c..2d3ede02a6a4f 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java @@ -24,6 +24,7 @@ import android.security.rkp.IRegistration; import android.security.rkp.IStoreUpgradedKeyCallback; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; +import android.security.rkp.service.RkpProxyException; import android.util.Log; import java.util.Set; @@ -68,13 +69,35 @@ final class RemoteProvisioningRegistration extends IRegistration.Stub { if (e instanceof OperationCanceledException) { Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode()); wrapCallback(mCallback::onCancel); + } else if (e instanceof RkpProxyException) { + Log.e(TAG, "RKP error fetching key for client " + mCallback.hashCode(), e); + RkpProxyException rkpException = (RkpProxyException) e; + wrapCallback(() -> mCallback.onError(toGetKeyError(rkpException), + e.getMessage())); } else { Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e); - wrapCallback(() -> mCallback.onError(e.getMessage())); + wrapCallback(() -> mCallback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN, + e.getMessage())); } } } + private byte toGetKeyError(RkpProxyException exception) { + switch (exception.getError()) { + case RkpProxyException.ERROR_UNKNOWN: + return IGetKeyCallback.ErrorCode.ERROR_UNKNOWN; + case RkpProxyException.ERROR_REQUIRES_SECURITY_PATCH: + return IGetKeyCallback.ErrorCode.ERROR_REQUIRES_SECURITY_PATCH; + case RkpProxyException.ERROR_PENDING_INTERNET_CONNECTIVITY: + return IGetKeyCallback.ErrorCode.ERROR_PENDING_INTERNET_CONNECTIVITY; + case RkpProxyException.ERROR_PERMANENT: + return IGetKeyCallback.ErrorCode.ERROR_PERMANENT; + default: + Log.e(TAG, "Unexpected error code in RkpProxyException", exception); + return IGetKeyCallback.ErrorCode.ERROR_UNKNOWN; + } + } + RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) { mRegistration = registration; mExecutor = executor; @@ -97,7 +120,8 @@ final class RemoteProvisioningRegistration extends IRegistration.Stub { } catch (Exception e) { Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e); mGetKeyOperations.remove(callback); - wrapCallback(() -> callback.onError(e.getMessage())); + wrapCallback(() -> callback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN, + e.getMessage())); } } diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java index 7b361d3e08329..1dcd0b936c86f 100644 --- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java +++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java @@ -23,6 +23,7 @@ import static org.mockito.AdditionalAnswers.answerVoid; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.contains; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; @@ -39,6 +40,7 @@ import android.security.rkp.IGetKeyCallback; import android.security.rkp.IStoreUpgradedKeyCallback; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; +import android.security.rkp.service.RkpProxyException; import androidx.test.runner.AndroidJUnit4; @@ -48,8 +50,9 @@ import org.junit.runner.RunWith; import org.mockito.stubbing.Answer; import org.mockito.stubbing.VoidAnswer4; -import java.time.Duration; +import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Map; import java.util.concurrent.Executor; /** @@ -104,7 +107,7 @@ public class RemoteProvisioningRegistrationTest { } @Test - public void getKeyHandlesError() throws Exception { + public void getKeyHandlesArbitraryException() throws Exception { Exception expectedException = new Exception("oops!"); doAnswer( answerGetKeyAsync((keyId, cancelSignal, executor, receiver) -> @@ -112,10 +115,37 @@ public class RemoteProvisioningRegistrationTest { .when(mRegistrationProxy).getKeyAsync(eq(0), any(), any(), any()); IGetKeyCallback callback = mock(IGetKeyCallback.class); mRegistration.getKey(0, callback); - verify(callback).onError(eq(expectedException.getMessage())); + verify(callback).onError(eq(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN), eq("oops!")); verifyNoMoreInteractions(callback); } + @Test + public void getKeyMapsRkpErrorsCorrectly() throws Exception { + Map expectedConversions = Map.of( + IGetKeyCallback.ErrorCode.ERROR_UNKNOWN, + RkpProxyException.ERROR_UNKNOWN, + IGetKeyCallback.ErrorCode.ERROR_REQUIRES_SECURITY_PATCH, + RkpProxyException.ERROR_REQUIRES_SECURITY_PATCH, + IGetKeyCallback.ErrorCode.ERROR_PENDING_INTERNET_CONNECTIVITY, + RkpProxyException.ERROR_PENDING_INTERNET_CONNECTIVITY, + IGetKeyCallback.ErrorCode.ERROR_PERMANENT, + RkpProxyException.ERROR_PERMANENT); + + for (Field errorField: IGetKeyCallback.ErrorCode.class.getFields()) { + byte error = (Byte) errorField.get(null); + Exception expectedException = new RkpProxyException(expectedConversions.get(error), + errorField.getName()); + doAnswer( + answerGetKeyAsync((keyId, cancelSignal, executor, receiver) -> + executor.execute(() -> receiver.onError(expectedException)))) + .when(mRegistrationProxy).getKeyAsync(eq(0), any(), any(), any()); + IGetKeyCallback callback = mock(IGetKeyCallback.class); + mRegistration.getKey(0, callback); + verify(callback).onError(eq(error), contains(errorField.getName())); + verifyNoMoreInteractions(callback); + } + } + @Test public void getKeyCancelDuringProxyOperation() throws Exception { IGetKeyCallback callback = mock(IGetKeyCallback.class); @@ -179,7 +209,8 @@ public class RemoteProvisioningRegistrationTest { IGetKeyCallback callback = mock(IGetKeyCallback.class); mRegistration.getKey(0, callback); - verify(callback).onError(eq(expectedException.getMessage())); + verify(callback).onError(eq(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN), + eq(expectedException.getMessage())); assertThrows(IllegalArgumentException.class, () -> mRegistration.cancelGetKey(callback)); verifyNoMoreInteractions(callback); }