Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 071a5cc3 authored by Seth Moore's avatar Seth Moore Committed by Automerger Merge Worker
Browse files

Merge "Map RkpProxyException error codes into get key errors" am: 5cd5d98e am: edb504eb

parents a0349424 edb504eb
Loading
Loading
Loading
Loading
+33 −2
Original line number Diff line number Diff line
@@ -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);
}
+26 −2
Original line number Diff line number Diff line
@@ -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()));
        }
    }

+35 −4
Original line number Diff line number Diff line
@@ -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);
    }