Map RkpProxyException error codes into get key errors

This allows clients of the system service to handle specific errors
returned up the stack from rkpd.

Test: RemoteProvisioningServiceTests
Test: keystore2_test
Change-Id: I4b8399c0c8693533631c3a816e494bd1a2ab3239
This commit is contained in:
Seth Moore
2023-02-01 10:47:31 -08:00
parent 8ccb2f122c
commit 89c91ae699
3 changed files with 94 additions and 8 deletions

View File

@@ -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);
}

View File

@@ -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()));
}
}

View File

@@ -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<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);
@@ -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);
}