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.hardware.security.keymint.DeviceInfo; 20 import android.hardware.security.keymint.IRemotelyProvisionedComponent; 21 import android.hardware.security.keymint.MacedPublicKey; 22 import android.hardware.security.keymint.ProtectedData; 23 import android.hardware.security.keymint.RpcHardwareInfo; 24 import android.os.RemoteException; 25 import android.os.ServiceManager; 26 import android.os.ShellCommand; 27 import android.util.IndentingPrintWriter; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import java.io.ByteArrayInputStream; 32 import java.io.ByteArrayOutputStream; 33 import java.io.PrintWriter; 34 import java.util.Base64; 35 36 import co.nstant.in.cbor.CborDecoder; 37 import co.nstant.in.cbor.CborEncoder; 38 import co.nstant.in.cbor.CborException; 39 import co.nstant.in.cbor.model.Array; 40 import co.nstant.in.cbor.model.ByteString; 41 import co.nstant.in.cbor.model.DataItem; 42 import co.nstant.in.cbor.model.Map; 43 import co.nstant.in.cbor.model.SimpleValue; 44 import co.nstant.in.cbor.model.UnsignedInteger; 45 46 class RemoteProvisioningShellCommand extends ShellCommand { 47 private static final String USAGE = "usage: cmd remote_provisioning SUBCOMMAND [ARGS]\n" 48 + "help\n" 49 + " Show this message.\n" 50 + "dump\n" 51 + " Dump service diagnostics.\n" 52 + "list [--min-version MIN_VERSION]\n" 53 + " List the names of the IRemotelyProvisionedComponent instances.\n" 54 + "csr [--challenge CHALLENGE] NAME\n" 55 + " Generate and print a base64-encoded CSR from the named\n" 56 + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" 57 + " or else it defaults to an empty challenge.\n"; 58 59 @VisibleForTesting 60 static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" 61 + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" 62 + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" 63 + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" 64 + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; 65 66 @VisibleForTesting 67 static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" 68 + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" 69 + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" 70 + "tz+gioCJsSZn6ct8daGvAmH8bmUDkTvTS30UlD5GAzgYIAEhWCDgQc8vDzQPHDMsQbDP1wwwVTXSHmpHE0su" 71 + "0UiWfiScaCJYIB/ORcX7YbqBIfnlBZubOQ52hoZHuB4vRfHOr9o/gGjbWECMs7p+ID4ysGjfYNEdffCsOI5R" 72 + "vP9s4Wc7Snm8Vnizmdh8igfY2rW1f3H02GvfMyc0e2XRKuuGmZirOrSAqr1Q"; 73 74 private static final int ERROR = -1; 75 private static final int SUCCESS = 0; 76 77 private final Injector mInjector; 78 RemoteProvisioningShellCommand()79 RemoteProvisioningShellCommand() { 80 this(new Injector()); 81 } 82 83 @VisibleForTesting RemoteProvisioningShellCommand(Injector injector)84 RemoteProvisioningShellCommand(Injector injector) { 85 mInjector = injector; 86 } 87 88 @Override onHelp()89 public void onHelp() { 90 getOutPrintWriter().print(USAGE); 91 } 92 93 @Override 94 @SuppressWarnings("CatchAndPrintStackTrace") onCommand(String cmd)95 public int onCommand(String cmd) { 96 if (cmd == null) { 97 return handleDefaultCommands(cmd); 98 } 99 try { 100 switch (cmd) { 101 case "list": 102 return list(); 103 case "csr": 104 return csr(); 105 default: 106 return handleDefaultCommands(cmd); 107 } 108 } catch (Exception e) { 109 e.printStackTrace(getErrPrintWriter()); 110 return ERROR; 111 } 112 } 113 114 @SuppressWarnings("CatchAndPrintStackTrace") dump(PrintWriter pw)115 void dump(PrintWriter pw) { 116 try { 117 IndentingPrintWriter ipw = new IndentingPrintWriter(pw); 118 for (String name : mInjector.getIrpcNames()) { 119 ipw.println(name + ":"); 120 ipw.increaseIndent(); 121 dumpRpcInstance(ipw, name); 122 ipw.decreaseIndent(); 123 } 124 } catch (Exception e) { 125 e.printStackTrace(pw); 126 } 127 } 128 dumpRpcInstance(PrintWriter pw, String name)129 private void dumpRpcInstance(PrintWriter pw, String name) throws RemoteException { 130 RpcHardwareInfo info = mInjector.getIrpcBinder(name).getHardwareInfo(); 131 pw.println("hwVersion=" + info.versionNumber); 132 pw.println("rpcAuthorName=" + info.rpcAuthorName); 133 if (info.versionNumber < 3) { 134 pw.println("supportedEekCurve=" + info.supportedEekCurve); 135 } 136 pw.println("uniqueId=" + info.uniqueId); 137 pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr); 138 } 139 list()140 private int list() throws RemoteException { 141 for (String name : mInjector.getIrpcNames()) { 142 getOutPrintWriter().println(name); 143 } 144 return SUCCESS; 145 } 146 csr()147 private int csr() throws RemoteException, CborException { 148 byte[] challenge = {}; 149 String opt; 150 while ((opt = getNextOption()) != null) { 151 switch (opt) { 152 case "--challenge": 153 challenge = Base64.getDecoder().decode(getNextArgRequired()); 154 break; 155 default: 156 getErrPrintWriter().println("error: unknown option"); 157 return ERROR; 158 } 159 } 160 String name = getNextArgRequired(); 161 162 IRemotelyProvisionedComponent binder = mInjector.getIrpcBinder(name); 163 RpcHardwareInfo info = binder.getHardwareInfo(); 164 MacedPublicKey[] emptyKeys = new MacedPublicKey[] {}; 165 byte[] csrBytes; 166 switch (info.versionNumber) { 167 case 1: 168 case 2: 169 DeviceInfo deviceInfo = new DeviceInfo(); 170 ProtectedData protectedData = new ProtectedData(); 171 byte[] eek = getEekChain(info.supportedEekCurve); 172 byte[] keysToSignMac = binder.generateCertificateRequest( 173 /*testMode=*/false, emptyKeys, eek, challenge, deviceInfo, protectedData); 174 csrBytes = composeCertificateRequestV1( 175 deviceInfo, challenge, protectedData, keysToSignMac); 176 break; 177 case 3: 178 csrBytes = binder.generateCertificateRequestV2(emptyKeys, challenge); 179 break; 180 default: 181 getErrPrintWriter().println("error: unsupported hwVersion: " + info.versionNumber); 182 return ERROR; 183 } 184 getOutPrintWriter().println(Base64.getEncoder().encodeToString(csrBytes)); 185 return SUCCESS; 186 } 187 getEekChain(int supportedEekCurve)188 private byte[] getEekChain(int supportedEekCurve) { 189 switch (supportedEekCurve) { 190 case RpcHardwareInfo.CURVE_25519: 191 return Base64.getDecoder().decode(EEK_ED25519_BASE64); 192 case RpcHardwareInfo.CURVE_P256: 193 return Base64.getDecoder().decode(EEK_P256_BASE64); 194 default: 195 throw new IllegalArgumentException("unsupported EEK curve: " + supportedEekCurve); 196 } 197 } 198 composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge, ProtectedData protectedData, byte[] keysToSignMac)199 private byte[] composeCertificateRequestV1(DeviceInfo deviceInfo, byte[] challenge, 200 ProtectedData protectedData, byte[] keysToSignMac) throws CborException { 201 Array info = new Array() 202 .add(decode(deviceInfo.deviceInfo)) 203 .add(new Map()); 204 205 // COSE_Signature with the hmac-sha256 algorithm and without a payload. 206 Array mac = new Array() 207 .add(new ByteString(encode( 208 new Map().put(new UnsignedInteger(1), new UnsignedInteger(5))))) 209 .add(new Map()) 210 .add(SimpleValue.NULL) 211 .add(new ByteString(keysToSignMac)); 212 213 Array csr = new Array() 214 .add(info) 215 .add(new ByteString(challenge)) 216 .add(decode(protectedData.protectedData)) 217 .add(mac); 218 219 return encode(csr); 220 } 221 encode(DataItem item)222 private byte[] encode(DataItem item) throws CborException { 223 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 224 new CborEncoder(baos).encode(item); 225 return baos.toByteArray(); 226 } 227 decode(byte[] data)228 private DataItem decode(byte[] data) throws CborException { 229 ByteArrayInputStream bais = new ByteArrayInputStream(data); 230 return new CborDecoder(bais).decodeNext(); 231 } 232 233 @VisibleForTesting 234 static class Injector { getIrpcNames()235 String[] getIrpcNames() { 236 return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); 237 } 238 getIrpcBinder(String name)239 IRemotelyProvisionedComponent getIrpcBinder(String name) { 240 String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; 241 IRemotelyProvisionedComponent binder = 242 IRemotelyProvisionedComponent.Stub.asInterface( 243 ServiceManager.waitForDeclaredService(irpc)); 244 if (binder == null) { 245 throw new IllegalArgumentException("failed to find " + irpc); 246 } 247 return binder; 248 } 249 } 250 } 251