Merge "Map RkpProxyException error codes into get key errors"
diff --git a/core/java/android/security/rkp/IGetKeyCallback.aidl b/core/java/android/security/rkp/IGetKeyCallback.aidl
index 85ceae62..a36d054 100644
--- a/core/java/android/security/rkp/IGetKeyCallback.aidl
+++ b/core/java/android/security/rkp/IGetKeyCallback.aidl
@@ -25,6 +25,36 @@
* @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 @@
/**
* 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 0e92709..2d3ede0 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.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 @@
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 @@
} 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 7b361d3..1dcd0b9 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.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.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.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 @@
}
@Test
- public void getKeyHandlesError() throws Exception {
+ public void getKeyHandlesArbitraryException() throws Exception {
Exception expectedException = new Exception("oops!");
doAnswer(
answerGetKeyAsync((keyId, cancelSignal, executor, receiver) ->
@@ -112,11 +115,38 @@
.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<Byte, Integer> 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);
doAnswer(
@@ -179,7 +209,8 @@
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);
}