1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.security.rkp; 18 19 import android.content.Context; 20 import android.hardware.security.keymint.DeviceInfo; 21 import android.hardware.security.keymint.IRemotelyProvisionedComponent; 22 import android.hardware.security.keymint.MacedPublicKey; 23 import android.hardware.security.keymint.ProtectedData; 24 import android.hardware.security.keymint.RpcHardwareInfo; 25 import android.os.CancellationSignal; 26 import android.os.OutcomeReceiver; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.os.ShellCommand; 30 import android.security.rkp.service.RegistrationProxy; 31 import android.security.rkp.service.RemotelyProvisionedKey; 32 import android.util.IndentingPrintWriter; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.ByteArrayOutputStream; 36 import java.io.PrintWriter; 37 import java.security.cert.Certificate; 38 import java.security.cert.CertificateFactory; 39 import java.time.Duration; 40 import java.util.Base64; 41 import java.util.concurrent.CompletableFuture; 42 import java.util.concurrent.Executor; 43 44 import co.nstant.in.cbor.CborDecoder; 45 import co.nstant.in.cbor.CborEncoder; 46 import co.nstant.in.cbor.CborException; 47 import co.nstant.in.cbor.model.Array; 48 import co.nstant.in.cbor.model.ByteString; 49 import co.nstant.in.cbor.model.DataItem; 50 import co.nstant.in.cbor.model.Map; 51 import co.nstant.in.cbor.model.SimpleValue; 52 import co.nstant.in.cbor.model.UnsignedInteger; 53 54 class RemoteProvisioningShellCommand extends ShellCommand { 55 private static final String USAGE = "usage: cmd remote_provisioning SUBCOMMAND [ARGS]\n" 56 + "help\n" 57 + " Show this message.\n" 58 + "dump\n" 59 + " Dump service diagnostics.\n" 60 + "list\n" 61 + " List the names of the IRemotelyProvisionedComponent instances.\n" 62 + "csr [--challenge CHALLENGE] NAME\n" 63 + " Generate and print a base64-encoded CSR from the named\n" 64 + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" 65 + " or else it defaults to an empty challenge.\n" 66 + "certify NAME\n" 67 + " Output the PEM-encoded certificate chain provisioned for the named\n" 68 + " IRemotelyProvisionedComponent.\n"; 69 70 static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" 71 + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" 72 + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" 73 + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" 74 + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; 75 76 static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" 77 + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" 78 + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" 79 + "tz+gioCJsSZn6ct8daGvAmH8bmUDkTvTS30UlD5GAzgYIAEhWCDgQc8vDzQPHDMsQbDP1wwwVTXSHmpHE0su" 80 + "0UiWfiScaCJYIB/ORcX7YbqBIfnlBZubOQ52hoZHuB4vRfHOr9o/gGjbWECMs7p+ID4ysGjfYNEdffCsOI5R" 81 + "vP9s4Wc7Snm8Vnizmdh8igfY2rW1f3H02GvfMyc0e2XRKuuGmZirOrSAqr1Q"; 82 83 private static final int ERROR = -1; 84 private static final int SUCCESS = 0; 85 86 private static final Duration BIND_TIMEOUT = Duration.ofSeconds(10); 87 private static final int KEY_ID = 452436; 88 89 private final Context mContext; 90 private final int mCallerUid; 91 private final Injector mInjector; 92 RemoteProvisioningShellCommand(Context context, int callerUid)93 RemoteProvisioningShellCommand(Context context, int callerUid) { 94 this(context, callerUid, new Injector()); 95 } 96 RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector)97 RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector) { 98 mContext = context; 99 mCallerUid = callerUid; 100 mInjector = injector; 101 } 102 103 @Override onHelp()104 public void onHelp() { 105 getOutPrintWriter().print(USAGE); 106 } 107 108 @Override 109 @SuppressWarnings("CatchAndPrintStackTrace") onCommand(String cmd)110 public int onCommand(String cmd) { 111 if (cmd == null) { 112 return handleDefaultCommands(cmd); 113 } 114 try { 115 switch (cmd) { 116 case "list": 117 return list(); 118 case "csr": 119 return csr(); 120 case "certify": 121 return certify(); 122 default: 123 return handleDefaultCommands(cmd); 124 } 125 } catch (Exception e) { 126 e.printStackTrace(getErrPrintWriter()); 127 return ERROR; 128 } 129 } 130 131 @SuppressWarnings("CatchAndPrintStackTrace") dump(PrintWriter pw)132 void dump(PrintWriter pw) { 133 try { 134 IndentingPrintWriter ipw = new IndentingPrintWriter(pw); 135 for (String name : mInjector.getIrpcNames()) { 136 ipw.println(name + ":"); 137 ipw.increaseIndent(); 138 dumpRpcInstance(ipw, name); 139 ipw.decreaseIndent(); 140 } 141 } catch (Exception e) { 142 e.printStackTrace(pw); 143 } 144 } 145 dumpRpcInstance(PrintWriter pw, String name)146 private void dumpRpcInstance(PrintWriter pw, String name) throws RemoteException { 147 RpcHardwareInfo info = mInjector.getIrpcBinder(name).getHardwareInfo(); 148 pw.println("hwVersion=" + info.versionNumber); 149 pw.println("rpcAuthorName=" + info.rpcAuthorName); 150 if (info.versionNumber < 3) { 151 pw.println("supportedEekCurve=" + info.supportedEekCurve); 152 } 153 pw.println("uniqueId=" + info.uniqueId); 154 if (info.versionNumber >= 3) { 155 pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr); 156 } 157 } 158 list()159 private int list() throws RemoteException { 160 for (String name : mInjector.getIrpcNames()) { 161 getOutPrintWriter().println(name); 162 } 163 return SUCCESS; 164 } 165 csr()166 private int csr() throws RemoteException, CborException { 167 byte[] challenge = {}; 168 String opt; 169 while ((opt = getNextOption()) != null) { 170 switch (opt) { 171 case "--challenge": 172 challenge = Base64.getDecoder().decode(getNextArgRequired()); 173 break; 174 default: 175 getErrPrintWriter().println("error: unknown option " + opt); 176 return ERROR; 177 } 178 } 179 String name = getNextArgRequired(); 180 181 IRemotelyProvisionedComponent binder = mInjector.getIrpcBinder(name); 182 RpcHardwareInfo info = binder.getHardwareInfo(); 183 MacedPublicKey[] emptyKeys = new MacedPublicKey[] {}; 184 byte[] csrBytes; 185 switch (info.versionNumber) { 186 case 1: 187 case 2: 188 DeviceInfo deviceInfo = new DeviceInfo(); 189 ProtectedData protectedData = new ProtectedData(); 190 byte[] eek = getEekChain(info.supportedEekCurve); 191 byte[] keysToSignMac = binder.generateCertificateRequest( 192 /*testMode=*/false, emptyKeys, eek, challenge, deviceInfo, protectedData); 193 csrBytes = composeCertificateRequestV1( 194 deviceInfo, challenge, protectedData, keysToSignMac); 195 break; 196 case 3: 197 csrBytes = binder.generateCertificateRequestV2(emptyKeys, challenge); 198 break; 199 default: 200 getErrPrintWriter().println("error: unsupported hwVersion: " + info.versionNumber); 201 return ERROR; 202 } 203 getOutPrintWriter().println(Base64.getEncoder().encodeToString(csrBytes)); 204 return SUCCESS; 205 } 206 getEekChain(int supportedEekCurve)207 private byte[] getEekChain(int supportedEekCurve) { 208 switch (supportedEekCurve) { 209 case RpcHardwareInfo.CURVE_25519: 210 return Base64.getDecoder().decode(EEK_ED25519_BASE64); 211 case RpcHardwareInfo.CURVE_P256: 212 return Base64.getDecoder().decode(EEK_P256_BASE64); 213 default: 214 throw new IllegalArgumentException("unsupported EEK curve: " + supportedEekCurve); 215 } 216 } 217 composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge, ProtectedData protectedData, byte[] keysToSignMac)218 private byte[] composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge, 219 ProtectedData protectedData, byte[] keysToSignMac) throws CborException { 220 Array info = new Array() 221 .add(decode(deviceInfo.deviceInfo)) 222 .add(new Map()); 223 224 // COSE_Signature with the hmac-sha256 algorithm and without a payload. 225 Array mac = new Array() 226 .add(new ByteString(encode( 227 new Map().put(new UnsignedInteger(1), new UnsignedInteger(5))))) 228 .add(new Map()) 229 .add(SimpleValue.NULL) 230 .add(new ByteString(keysToSignMac)); 231 232 Array csr = new Array() 233 .add(info) 234 .add(new ByteString(challenge)) 235 .add(decode(protectedData.protectedData)) 236 .add(mac); 237 238 return encode(csr); 239 } 240 encode(DataItem item)241 private byte[] encode(DataItem item) throws CborException { 242 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 243 new CborEncoder(baos).encode(item); 244 return baos.toByteArray(); 245 } 246 decode(byte[] data)247 private DataItem decode(byte[] data) throws CborException { 248 ByteArrayInputStream bais = new ByteArrayInputStream(data); 249 return new CborDecoder(bais).decodeNext(); 250 } 251 certify()252 private int certify() throws Exception { 253 String name = getNextArgRequired(); 254 255 Executor executor = mContext.getMainExecutor(); 256 CancellationSignal cancellationSignal = new CancellationSignal(); 257 OutcomeFuture<RemotelyProvisionedKey> key = new OutcomeFuture<>(); 258 mInjector.getRegistrationProxy(mContext, mCallerUid, name, executor) 259 .getKeyAsync(KEY_ID, cancellationSignal, executor, key); 260 byte[] encodedCertChain = key.join().getEncodedCertChain(); 261 ByteArrayInputStream is = new ByteArrayInputStream(encodedCertChain); 262 PrintWriter pw = getOutPrintWriter(); 263 for (Certificate cert : CertificateFactory.getInstance("X.509").generateCertificates(is)) { 264 String encoded = Base64.getEncoder().encodeToString(cert.getEncoded()); 265 pw.println("-----BEGIN CERTIFICATE-----"); 266 pw.println(encoded.replaceAll("(.{64})", "$1\n").stripTrailing()); 267 pw.println("-----END CERTIFICATE-----"); 268 } 269 return SUCCESS; 270 } 271 272 /** Treat an OutcomeReceiver as a future for use in synchronous code. */ 273 private static class OutcomeFuture<T> implements OutcomeReceiver<T, Exception> { 274 private CompletableFuture<T> mFuture = new CompletableFuture<>(); 275 276 @Override onResult(T result)277 public void onResult(T result) { 278 mFuture.complete(result); 279 } 280 281 @Override onError(Exception e)282 public void onError(Exception e) { 283 mFuture.completeExceptionally(e); 284 } 285 join()286 public T join() { 287 return mFuture.join(); 288 } 289 } 290 291 static class Injector { getIrpcNames()292 String[] getIrpcNames() { 293 return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); 294 } 295 getIrpcBinder(String name)296 IRemotelyProvisionedComponent getIrpcBinder(String name) { 297 String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; 298 IRemotelyProvisionedComponent binder = 299 IRemotelyProvisionedComponent.Stub.asInterface( 300 ServiceManager.waitForDeclaredService(irpc)); 301 if (binder == null) { 302 throw new IllegalArgumentException("failed to find " + irpc); 303 } 304 return binder; 305 } 306 getRegistrationProxy( Context context, int callerUid, String name, Executor executor)307 RegistrationProxy getRegistrationProxy( 308 Context context, int callerUid, String name, Executor executor) { 309 String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; 310 OutcomeFuture<RegistrationProxy> registration = new OutcomeFuture<>(); 311 RegistrationProxy.createAsync( 312 context, callerUid, irpc, BIND_TIMEOUT, executor, registration); 313 return registration.join(); 314 } 315 } 316 } 317