Loading services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +16 −3 Original line number Diff line number Diff line Loading @@ -105,14 +105,27 @@ public class RemoteProvisioningService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; new RemoteProvisioningShellCommand().dump(pw); final int callerUid = Binder.getCallingUidOrThrow(); final long callingIdentity = Binder.clearCallingIdentity(); try { new RemoteProvisioningShellCommand(getContext(), callerUid).dump(pw); } finally { Binder.restoreCallingIdentity(callingIdentity); } } @Override public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args) { return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); final int callerUid = Binder.getCallingUidOrThrow(); final long callingIdentity = Binder.clearCallingIdentity(); try { return new RemoteProvisioningShellCommand(getContext(), callerUid).exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } } services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java +74 −10 Original line number Diff line number Diff line Loading @@ -16,22 +16,30 @@ package com.android.server.security.rkp; import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.CancellationSignal; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ShellCommand; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.time.Duration; import java.util.Base64; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import co.nstant.in.cbor.CborDecoder; import co.nstant.in.cbor.CborEncoder; Loading @@ -54,16 +62,17 @@ class RemoteProvisioningShellCommand extends ShellCommand { + "csr [--challenge CHALLENGE] NAME\n" + " Generate and print a base64-encoded CSR from the named\n" + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" + " or else it defaults to an empty challenge.\n"; + " or else it defaults to an empty challenge.\n" + "certify NAME\n" + " Output the PEM-encoded certificate chain provisioned for the named\n" + " IRemotelyProvisionedComponent.\n"; @VisibleForTesting static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; @VisibleForTesting static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" Loading @@ -74,14 +83,20 @@ class RemoteProvisioningShellCommand extends ShellCommand { private static final int ERROR = -1; private static final int SUCCESS = 0; private static final Duration BIND_TIMEOUT = Duration.ofSeconds(10); private static final int KEY_ID = 452436; private final Context mContext; private final int mCallerUid; private final Injector mInjector; RemoteProvisioningShellCommand() { this(new Injector()); RemoteProvisioningShellCommand(Context context, int callerUid) { this(context, callerUid, new Injector()); } @VisibleForTesting RemoteProvisioningShellCommand(Injector injector) { RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector) { mContext = context; mCallerUid = callerUid; mInjector = injector; } Loading @@ -102,6 +117,8 @@ class RemoteProvisioningShellCommand extends ShellCommand { return list(); case "csr": return csr(); case "certify": return certify(); default: return handleDefaultCommands(cmd); } Loading Loading @@ -232,7 +249,45 @@ class RemoteProvisioningShellCommand extends ShellCommand { return new CborDecoder(bais).decodeNext(); } @VisibleForTesting private int certify() throws Exception { String name = getNextArgRequired(); Executor executor = mContext.getMainExecutor(); CancellationSignal cancellationSignal = new CancellationSignal(); OutcomeFuture<RemotelyProvisionedKey> key = new OutcomeFuture<>(); mInjector.getRegistrationProxy(mContext, mCallerUid, name, executor) .getKeyAsync(KEY_ID, cancellationSignal, executor, key); byte[] encodedCertChain = key.join().getEncodedCertChain(); ByteArrayInputStream is = new ByteArrayInputStream(encodedCertChain); PrintWriter pw = getOutPrintWriter(); for (Certificate cert : CertificateFactory.getInstance("X.509").generateCertificates(is)) { String encoded = Base64.getEncoder().encodeToString(cert.getEncoded()); pw.println("-----BEGIN CERTIFICATE-----"); pw.println(encoded.replaceAll("(.{64})", "$1\n").stripTrailing()); pw.println("-----END CERTIFICATE-----"); } return SUCCESS; } /** Treat an OutcomeReceiver as a future for use in synchronous code. */ private static class OutcomeFuture<T> implements OutcomeReceiver<T, Exception> { private CompletableFuture<T> mFuture = new CompletableFuture<>(); @Override public void onResult(T result) { mFuture.complete(result); } @Override public void onError(Exception e) { mFuture.completeExceptionally(e); } public T join() { return mFuture.join(); } } static class Injector { String[] getIrpcNames() { return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); Loading @@ -248,5 +303,14 @@ class RemoteProvisioningShellCommand extends ShellCommand { } return binder; } RegistrationProxy getRegistrationProxy( Context context, int callerUid, String name, Executor executor) { String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; OutcomeFuture<RegistrationProxy> registration = new OutcomeFuture<>(); RegistrationProxy.createAsync( context, callerUid, irpc, BIND_TIMEOUT, executor, registration); return registration.join(); } } } services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java +126 −14 Original line number Diff line number Diff line Loading @@ -21,12 +21,14 @@ import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; Loading @@ -34,28 +36,35 @@ import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.Binder; import android.os.FileUtils; import android.os.OutcomeReceiver; import android.os.Process; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Arrays; import java.util.Base64; import java.util.Map; import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class RemoteProvisioningShellCommandTest { private static class Injector extends RemoteProvisioningShellCommand.Injector { private Context mContext; private final Map<String, IRemotelyProvisionedComponent> mIrpcs; private static class Injector extends RemoteProvisioningShellCommand.Injector { Injector(Map irpcs) { mIrpcs = irpcs; } Map<String, IRemotelyProvisionedComponent> mIrpcs; Map<String, RegistrationProxy> mRegistrationProxies; @Override String[] getIrpcNames() { Loading @@ -70,6 +79,12 @@ public class RemoteProvisioningShellCommandTest { } return irpc; } @Override RegistrationProxy getRegistrationProxy( Context context, int callerUid, String name, Executor executor) { return mRegistrationProxies.get(name); } } private static class CommandResult { Loading Loading @@ -111,10 +126,17 @@ public class RemoteProvisioningShellCommandTest { code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null)); } @Before public void setUp() { mContext = ApplicationProvider.getApplicationContext(); } @Test public void list_zeroInstances() throws Exception { Injector injector = new Injector(); injector.mIrpcs = Map.of(); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of())); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -124,8 +146,10 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_oneInstances() throws Exception { Injector injector = new Injector(); injector.mIrpcs = Map.of("default", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class)))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -134,10 +158,12 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_twoInstances() throws Exception { RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of( Injector injector = new Injector(); injector.mIrpcs = Map.of( "default", mock(IRemotelyProvisionedComponent.class), "strongbox", mock(IRemotelyProvisionedComponent.class)))); "strongbox", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -158,8 +184,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( Loading Loading @@ -189,8 +217,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( Loading @@ -215,8 +245,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f}); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "default"}); verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]); assertThat(res.getErr()).isEmpty(); Loading @@ -233,8 +265,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x69}); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"}); verify(defaultMock).generateCertificateRequestV2( new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c}); Loading @@ -242,4 +276,82 @@ public class RemoteProvisioningShellCommandTest { assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo("aGk=\n"); } @Test public void certify_sameOrderAsReceived() throws Exception { String cert1 = "MIIBqDCCAU2gAwIBAgIUI3FFU7xZno/2Xf/wZzKKquP0ov0wCgYIKoZIzj0EAwIw\n" + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + "MDgyMjE5MzgxMFoXDTMzMDgxOTE5MzgxMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + "czOpG6NKOdDjV/yrKjuy0q0jEJvsVLGgTeY+vyKRBJS59OhyRWG6n3aza21bNg5d\n" + "WE9ruz+bcT0IP4kDbiS0y6NTMFEwHQYDVR0OBBYEFHYfJxCUipNI7qRqvczcWsOb\n" + "FIDPMB8GA1UdIwQYMBaAFHYfJxCUipNI7qRqvczcWsObFIDPMA8GA1UdEwEB/wQF\n" + "MAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKm/kpJwlnWkjoLCAddBiSnxbT4EfJIK\n" + "H0j58tg5VazHAiEAnS/kRzU9AbstOZyD7el/ws3gLXkbUNey3pLFutBWsSU=\n"; String cert2 = "MIIBpjCCAU2gAwIBAgIUdSzfZzeGr+h70JPO7Sxwdkw99iMwCgYIKoZIzj0EAwIw\n" + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + "MDgyMjIwMTcyMFoXDTMzMDgxOTIwMTcyMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + "voGJi4DxuqH8rzPV6Eq0OVULc0xFzaM0500VBqiQEB7Qt0Ktk2d+3bUrFAb3SZV4\n" + "6TIdb7SkynvaDtr0x45Ng6NTMFEwHQYDVR0OBBYEFMeGjvGV0ADPBJk5/FPoW9HQ\n" + "uTc6MB8GA1UdIwQYMBaAFMeGjvGV0ADPBJk5/FPoW9HQuTc6MA8GA1UdEwEB/wQF\n" + "MAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgd1gu7iiNOQXaQUn5BT3WwWR0Yk78ndWt\n" + "ew7tRiTOhFcCIFURi6WcNH0oWa6IbwBSMC9aZlo98Fbg+dTwhLAAw+PW\n"; byte[] cert1Bytes = Base64.getDecoder().decode(cert1.replaceAll("\\s+", "")); byte[] cert2Bytes = Base64.getDecoder().decode(cert2.replaceAll("\\s+", "")); byte[] certChain = Arrays.copyOf(cert1Bytes, cert1Bytes.length + cert2Bytes.length); System.arraycopy(cert2Bytes, 0, certChain, cert1Bytes.length, cert2Bytes.length); RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); when(keyMock.getEncodedCertChain()).thenReturn(certChain); RegistrationProxy defaultMock = mock(RegistrationProxy.class); doAnswer(invocation -> { ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) .onResult(keyMock); return null; }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); Injector injector = new Injector(); injector.mRegistrationProxies = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"certify", "default"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo( "-----BEGIN CERTIFICATE-----\n" + cert1 + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + cert2 + "-----END CERTIFICATE-----\n"); } @Test public void certify_noBlankLineBeforeTrailer() throws Exception { String cert = "MIIB2zCCAYGgAwIBAgIRAOpN7Em1k7gaqLAB2dzXUTYwCgYIKoZIzj0EAwIwKTET\n" + "MBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMB4XDTIzMDgx\n" + "ODIzMzI1MloXDTIzMDkyMTIzMzI1MlowOTEMMAoGA1UEChMDVEVFMSkwJwYDVQQD\n" + "EyBlYTRkZWM0OWI1OTNiODFhYThiMDAxZDlkY2Q3NTEzNjBZMBMGByqGSM49AgEG\n" + "CCqGSM49AwEHA0IABHM/cKZblmlw8bdGbDXnX+ZiLiGjSjaLHXYOoHDrVArAMXUi\n" + "L6brhcUPaqSGcVLcfFZbaFMOxXW6TsGdQiwJ0iyjejB4MB0GA1UdDgQWBBTYzft+\n" + "X32TH/Hh+ngwQF6aPhnfXDAfBgNVHSMEGDAWgBQT4JObI9mzNNW2FRsHRcw4zVn2\n" + "8jAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAVBgorBgEEAdZ5AgEe\n" + "BAehARoABAAAMAoGCCqGSM49BAMCA0gAMEUCIDc0OR7CzIYw0myTr0y/Brl1nZyk\n" + "eGSQp615WpTwYhwxAiEApM10gSIKBIo7Z4/FNzkuiz1zZwW9+Dcqisqxkfe6icQ=\n"; byte[] certBytes = Base64.getDecoder().decode(cert.replaceAll("\\s+", "")); RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); when(keyMock.getEncodedCertChain()).thenReturn(certBytes); RegistrationProxy defaultMock = mock(RegistrationProxy.class); doAnswer(invocation -> { ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) .onResult(keyMock); return null; }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); Injector injector = new Injector(); injector.mRegistrationProxies = Map.of("strongbox", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"certify", "strongbox"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo( "-----BEGIN CERTIFICATE-----\n" + cert + "-----END CERTIFICATE-----\n"); } } Loading
services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +16 −3 Original line number Diff line number Diff line Loading @@ -105,14 +105,27 @@ public class RemoteProvisioningService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; new RemoteProvisioningShellCommand().dump(pw); final int callerUid = Binder.getCallingUidOrThrow(); final long callingIdentity = Binder.clearCallingIdentity(); try { new RemoteProvisioningShellCommand(getContext(), callerUid).dump(pw); } finally { Binder.restoreCallingIdentity(callingIdentity); } } @Override public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args) { return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); final int callerUid = Binder.getCallingUidOrThrow(); final long callingIdentity = Binder.clearCallingIdentity(); try { return new RemoteProvisioningShellCommand(getContext(), callerUid).exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } }
services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java +74 −10 Original line number Diff line number Diff line Loading @@ -16,22 +16,30 @@ package com.android.server.security.rkp; import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.CancellationSignal; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ShellCommand; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.time.Duration; import java.util.Base64; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import co.nstant.in.cbor.CborDecoder; import co.nstant.in.cbor.CborEncoder; Loading @@ -54,16 +62,17 @@ class RemoteProvisioningShellCommand extends ShellCommand { + "csr [--challenge CHALLENGE] NAME\n" + " Generate and print a base64-encoded CSR from the named\n" + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" + " or else it defaults to an empty challenge.\n"; + " or else it defaults to an empty challenge.\n" + "certify NAME\n" + " Output the PEM-encoded certificate chain provisioned for the named\n" + " IRemotelyProvisionedComponent.\n"; @VisibleForTesting static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; @VisibleForTesting static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" Loading @@ -74,14 +83,20 @@ class RemoteProvisioningShellCommand extends ShellCommand { private static final int ERROR = -1; private static final int SUCCESS = 0; private static final Duration BIND_TIMEOUT = Duration.ofSeconds(10); private static final int KEY_ID = 452436; private final Context mContext; private final int mCallerUid; private final Injector mInjector; RemoteProvisioningShellCommand() { this(new Injector()); RemoteProvisioningShellCommand(Context context, int callerUid) { this(context, callerUid, new Injector()); } @VisibleForTesting RemoteProvisioningShellCommand(Injector injector) { RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector) { mContext = context; mCallerUid = callerUid; mInjector = injector; } Loading @@ -102,6 +117,8 @@ class RemoteProvisioningShellCommand extends ShellCommand { return list(); case "csr": return csr(); case "certify": return certify(); default: return handleDefaultCommands(cmd); } Loading Loading @@ -232,7 +249,45 @@ class RemoteProvisioningShellCommand extends ShellCommand { return new CborDecoder(bais).decodeNext(); } @VisibleForTesting private int certify() throws Exception { String name = getNextArgRequired(); Executor executor = mContext.getMainExecutor(); CancellationSignal cancellationSignal = new CancellationSignal(); OutcomeFuture<RemotelyProvisionedKey> key = new OutcomeFuture<>(); mInjector.getRegistrationProxy(mContext, mCallerUid, name, executor) .getKeyAsync(KEY_ID, cancellationSignal, executor, key); byte[] encodedCertChain = key.join().getEncodedCertChain(); ByteArrayInputStream is = new ByteArrayInputStream(encodedCertChain); PrintWriter pw = getOutPrintWriter(); for (Certificate cert : CertificateFactory.getInstance("X.509").generateCertificates(is)) { String encoded = Base64.getEncoder().encodeToString(cert.getEncoded()); pw.println("-----BEGIN CERTIFICATE-----"); pw.println(encoded.replaceAll("(.{64})", "$1\n").stripTrailing()); pw.println("-----END CERTIFICATE-----"); } return SUCCESS; } /** Treat an OutcomeReceiver as a future for use in synchronous code. */ private static class OutcomeFuture<T> implements OutcomeReceiver<T, Exception> { private CompletableFuture<T> mFuture = new CompletableFuture<>(); @Override public void onResult(T result) { mFuture.complete(result); } @Override public void onError(Exception e) { mFuture.completeExceptionally(e); } public T join() { return mFuture.join(); } } static class Injector { String[] getIrpcNames() { return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); Loading @@ -248,5 +303,14 @@ class RemoteProvisioningShellCommand extends ShellCommand { } return binder; } RegistrationProxy getRegistrationProxy( Context context, int callerUid, String name, Executor executor) { String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; OutcomeFuture<RegistrationProxy> registration = new OutcomeFuture<>(); RegistrationProxy.createAsync( context, callerUid, irpc, BIND_TIMEOUT, executor, registration); return registration.join(); } } }
services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java +126 −14 Original line number Diff line number Diff line Loading @@ -21,12 +21,14 @@ import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; Loading @@ -34,28 +36,35 @@ import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.Binder; import android.os.FileUtils; import android.os.OutcomeReceiver; import android.os.Process; import android.security.rkp.service.RegistrationProxy; import android.security.rkp.service.RemotelyProvisionedKey; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Arrays; import java.util.Base64; import java.util.Map; import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class RemoteProvisioningShellCommandTest { private static class Injector extends RemoteProvisioningShellCommand.Injector { private Context mContext; private final Map<String, IRemotelyProvisionedComponent> mIrpcs; private static class Injector extends RemoteProvisioningShellCommand.Injector { Injector(Map irpcs) { mIrpcs = irpcs; } Map<String, IRemotelyProvisionedComponent> mIrpcs; Map<String, RegistrationProxy> mRegistrationProxies; @Override String[] getIrpcNames() { Loading @@ -70,6 +79,12 @@ public class RemoteProvisioningShellCommandTest { } return irpc; } @Override RegistrationProxy getRegistrationProxy( Context context, int callerUid, String name, Executor executor) { return mRegistrationProxies.get(name); } } private static class CommandResult { Loading Loading @@ -111,10 +126,17 @@ public class RemoteProvisioningShellCommandTest { code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null)); } @Before public void setUp() { mContext = ApplicationProvider.getApplicationContext(); } @Test public void list_zeroInstances() throws Exception { Injector injector = new Injector(); injector.mIrpcs = Map.of(); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of())); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -124,8 +146,10 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_oneInstances() throws Exception { Injector injector = new Injector(); injector.mIrpcs = Map.of("default", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class)))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -134,10 +158,12 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_twoInstances() throws Exception { RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of( Injector injector = new Injector(); injector.mIrpcs = Map.of( "default", mock(IRemotelyProvisionedComponent.class), "strongbox", mock(IRemotelyProvisionedComponent.class)))); "strongbox", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); Loading @@ -158,8 +184,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( Loading Loading @@ -189,8 +217,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( Loading @@ -215,8 +245,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f}); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "default"}); verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]); assertThat(res.getErr()).isEmpty(); Loading @@ -233,8 +265,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x69}); Injector injector = new Injector(); injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( new Injector(Map.of("default", defaultMock))); mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"}); verify(defaultMock).generateCertificateRequestV2( new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c}); Loading @@ -242,4 +276,82 @@ public class RemoteProvisioningShellCommandTest { assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo("aGk=\n"); } @Test public void certify_sameOrderAsReceived() throws Exception { String cert1 = "MIIBqDCCAU2gAwIBAgIUI3FFU7xZno/2Xf/wZzKKquP0ov0wCgYIKoZIzj0EAwIw\n" + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + "MDgyMjE5MzgxMFoXDTMzMDgxOTE5MzgxMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + "czOpG6NKOdDjV/yrKjuy0q0jEJvsVLGgTeY+vyKRBJS59OhyRWG6n3aza21bNg5d\n" + "WE9ruz+bcT0IP4kDbiS0y6NTMFEwHQYDVR0OBBYEFHYfJxCUipNI7qRqvczcWsOb\n" + "FIDPMB8GA1UdIwQYMBaAFHYfJxCUipNI7qRqvczcWsObFIDPMA8GA1UdEwEB/wQF\n" + "MAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKm/kpJwlnWkjoLCAddBiSnxbT4EfJIK\n" + "H0j58tg5VazHAiEAnS/kRzU9AbstOZyD7el/ws3gLXkbUNey3pLFutBWsSU=\n"; String cert2 = "MIIBpjCCAU2gAwIBAgIUdSzfZzeGr+h70JPO7Sxwdkw99iMwCgYIKoZIzj0EAwIw\n" + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + "MDgyMjIwMTcyMFoXDTMzMDgxOTIwMTcyMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + "voGJi4DxuqH8rzPV6Eq0OVULc0xFzaM0500VBqiQEB7Qt0Ktk2d+3bUrFAb3SZV4\n" + "6TIdb7SkynvaDtr0x45Ng6NTMFEwHQYDVR0OBBYEFMeGjvGV0ADPBJk5/FPoW9HQ\n" + "uTc6MB8GA1UdIwQYMBaAFMeGjvGV0ADPBJk5/FPoW9HQuTc6MA8GA1UdEwEB/wQF\n" + "MAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgd1gu7iiNOQXaQUn5BT3WwWR0Yk78ndWt\n" + "ew7tRiTOhFcCIFURi6WcNH0oWa6IbwBSMC9aZlo98Fbg+dTwhLAAw+PW\n"; byte[] cert1Bytes = Base64.getDecoder().decode(cert1.replaceAll("\\s+", "")); byte[] cert2Bytes = Base64.getDecoder().decode(cert2.replaceAll("\\s+", "")); byte[] certChain = Arrays.copyOf(cert1Bytes, cert1Bytes.length + cert2Bytes.length); System.arraycopy(cert2Bytes, 0, certChain, cert1Bytes.length, cert2Bytes.length); RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); when(keyMock.getEncodedCertChain()).thenReturn(certChain); RegistrationProxy defaultMock = mock(RegistrationProxy.class); doAnswer(invocation -> { ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) .onResult(keyMock); return null; }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); Injector injector = new Injector(); injector.mRegistrationProxies = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"certify", "default"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo( "-----BEGIN CERTIFICATE-----\n" + cert1 + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + cert2 + "-----END CERTIFICATE-----\n"); } @Test public void certify_noBlankLineBeforeTrailer() throws Exception { String cert = "MIIB2zCCAYGgAwIBAgIRAOpN7Em1k7gaqLAB2dzXUTYwCgYIKoZIzj0EAwIwKTET\n" + "MBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMB4XDTIzMDgx\n" + "ODIzMzI1MloXDTIzMDkyMTIzMzI1MlowOTEMMAoGA1UEChMDVEVFMSkwJwYDVQQD\n" + "EyBlYTRkZWM0OWI1OTNiODFhYThiMDAxZDlkY2Q3NTEzNjBZMBMGByqGSM49AgEG\n" + "CCqGSM49AwEHA0IABHM/cKZblmlw8bdGbDXnX+ZiLiGjSjaLHXYOoHDrVArAMXUi\n" + "L6brhcUPaqSGcVLcfFZbaFMOxXW6TsGdQiwJ0iyjejB4MB0GA1UdDgQWBBTYzft+\n" + "X32TH/Hh+ngwQF6aPhnfXDAfBgNVHSMEGDAWgBQT4JObI9mzNNW2FRsHRcw4zVn2\n" + "8jAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAVBgorBgEEAdZ5AgEe\n" + "BAehARoABAAAMAoGCCqGSM49BAMCA0gAMEUCIDc0OR7CzIYw0myTr0y/Brl1nZyk\n" + "eGSQp615WpTwYhwxAiEApM10gSIKBIo7Z4/FNzkuiz1zZwW9+Dcqisqxkfe6icQ=\n"; byte[] certBytes = Base64.getDecoder().decode(cert.replaceAll("\\s+", "")); RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); when(keyMock.getEncodedCertChain()).thenReturn(certBytes); RegistrationProxy defaultMock = mock(RegistrationProxy.class); doAnswer(invocation -> { ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) .onResult(keyMock); return null; }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); Injector injector = new Injector(); injector.mRegistrationProxies = Map.of("strongbox", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"certify", "strongbox"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo( "-----BEGIN CERTIFICATE-----\n" + cert + "-----END CERTIFICATE-----\n"); } }