Skip to content
Snippets Groups Projects
Commit 5cd5d98e authored by Seth Moore's avatar Seth Moore Committed by Gerrit Code Review
Browse files

Merge "Map RkpProxyException error codes into get key errors"

parents bff6b3b9 89c91ae6
No related branches found
No related tags found
No related merge requests found
......@@ -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);
}
......@@ -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()));
}
}
......
......@@ -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);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment