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