1 /*
2  * Copyright (C) 2022 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.rkpdapp.interfaces;
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.ServiceSpecificException;
26 import android.util.Log;
27 
28 import com.android.rkpdapp.GeekResponse;
29 import com.android.rkpdapp.RkpdException;
30 import com.android.rkpdapp.database.RkpKey;
31 import com.android.rkpdapp.metrics.ProvisioningAttempt;
32 import com.android.rkpdapp.utils.CborUtils;
33 import com.android.rkpdapp.utils.StopWatch;
34 
35 import java.util.List;
36 
37 import co.nstant.in.cbor.CborException;
38 import co.nstant.in.cbor.model.Array;
39 import co.nstant.in.cbor.model.ByteString;
40 import co.nstant.in.cbor.model.MajorType;
41 import co.nstant.in.cbor.model.Map;
42 
43 /**
44  * Provides convenience methods for interfacing with the IRemotelyProvisionedComponent
45  * implementations. Since these APIs are internal only and subject to change, it is handy
46  * to have an abstraction layer to reduce the impact of these changes on the app.
47  */
48 public class SystemInterface {
49     private static final String TAG = "RkpdSystemInterface";
50     private final IRemotelyProvisionedComponent mBinder;
51     private final String mServiceName;
52     private final int mSupportedCurve;
53 
SystemInterface(IRemotelyProvisionedComponent binder, String serviceName)54     public SystemInterface(IRemotelyProvisionedComponent binder, String serviceName) {
55         mServiceName = serviceName;
56         mBinder = binder;
57         try {
58             mSupportedCurve = mBinder.getHardwareInfo().supportedEekCurve;
59         } catch (RemoteException e) {
60             Log.e(TAG, "Failed to call getHardwareInfo", e);
61             throw e.rethrowAsRuntimeException();
62         }
63     }
64 
65     /**
66      * @return the fully qualified name of the underlying IRemotelyProvisionedComponent
67      */
getServiceName()68     public String getServiceName() {
69         return mServiceName;
70     }
71 
72     /**
73      * @return human readable string describing this object
74      */
toString()75     public String toString() {
76         return getClass().getName() + "{" + mServiceName + "}";
77     }
78 
79     /**
80      * Generates attestation keys pair through binder service.
81      * Returns generated key in {@link RkpKey} format.
82      */
generateKey(ProvisioningAttempt metrics)83     public RkpKey generateKey(ProvisioningAttempt metrics) throws CborException, RkpdException {
84         MacedPublicKey macedPublicKey = new MacedPublicKey();
85         try (StopWatch ignored = metrics.startBinderWait()) {
86             byte[] privKey = mBinder.generateEcdsaP256KeyPair(false, macedPublicKey);
87             return CborUtils.extractRkpKeyFromMacedKey(privKey, mServiceName, macedPublicKey);
88         } catch (RemoteException e) {
89             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_KEYPAIR_FAILED);
90             Log.e(TAG, "Failed to generate key.", e);
91             throw e.rethrowAsRuntimeException();
92         } catch (ServiceSpecificException e) {
93             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_KEYPAIR_FAILED);
94             Log.e(TAG, "Failed to generate key. Failed with " + e.errorCode, e);
95             throw e;
96         } catch (CborException e) {
97             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_KEYPAIR_FAILED);
98             throw e;
99         }
100     }
101 
102     /**
103      * Sends a generateCsr request over the binder interface to generate the request to be sent to
104      * Remote provisioning servers.
105      * @param geekResponse Contains the challenge and GEEK chain for older implementations. Only has
106      *                    challenge for the newer ones.
107      * @param keysToSign array of keys to be signed.
108      */
generateCsr(ProvisioningAttempt metrics, GeekResponse geekResponse, List<RkpKey> keysToSign)109     public byte[] generateCsr(ProvisioningAttempt metrics, GeekResponse geekResponse,
110             List<RkpKey> keysToSign) throws CborException, RkpdException {
111         byte[] challenge = geekResponse.getChallenge();
112         byte[] csrTag;
113         MacedPublicKey[] macedKeysToSign = keysToSign.stream()
114                 .map(x -> {
115                     MacedPublicKey key = new MacedPublicKey();
116                     key.macedKey = x.getMacedPublicKey();
117                     return key;
118                 }).toArray(MacedPublicKey[]::new);
119         try (StopWatch ignored = metrics.startBinderWait()) {
120             if (getVersion() < 3) {
121                 DeviceInfo deviceInfo = new DeviceInfo();
122                 ProtectedData protectedData = new ProtectedData();
123                 byte[] geekChain = geekResponse.getGeekChain(mSupportedCurve);
124                 csrTag = mBinder.generateCertificateRequest(false, macedKeysToSign, geekChain,
125                         challenge, deviceInfo, protectedData);
126                 Array macedKeys = new Array();
127                 for (RkpKey key: keysToSign) {
128                     macedKeys.add(key.getCoseKey());
129                 }
130                 try {
131                     Array mac0Message = new Array()
132                             .add(new ByteString(CborUtils.encodeCbor(
133                                     CborUtils.makeProtectedHeaders())))
134                             .add(new Map())
135                             .add(new ByteString(CborUtils.encodeCbor(macedKeys)))
136                             .add(new ByteString(csrTag));
137                     return CborUtils.buildCertificateRequest(deviceInfo.deviceInfo,
138                             challenge,
139                             protectedData.protectedData,
140                             CborUtils.encodeCbor(mac0Message),
141                             CborUtils.buildUnverifiedDeviceInfo());
142                 } catch (CborException | RkpdException e) {
143                     Log.e(TAG, "Failed to parse/build CBOR", e);
144                     metrics.setStatus(ProvisioningAttempt.Status.GENERATE_CSR_FAILED);
145                     throw e;
146                 }
147             } else {
148                 byte[] csrBytes = mBinder.generateCertificateRequestV2(macedKeysToSign, challenge);
149                 Array array = (Array) CborUtils.decodeCbor(csrBytes, "CSR request",
150                         MajorType.ARRAY);
151                 array.add(CborUtils.buildUnverifiedDeviceInfo());
152                 return CborUtils.encodeCbor(array);
153             }
154         } catch (RemoteException e) {
155             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_CSR_FAILED);
156             Log.e(TAG, "Failed to generate CSR blob", e);
157             throw e.rethrowAsRuntimeException();
158         } catch (ServiceSpecificException ex) {
159             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_CSR_FAILED);
160             Log.e(TAG, "Failed to generate CSR blob. Failed with " + ex.errorCode, ex);
161             throw ex;
162         } catch (CborException e) {
163             metrics.setStatus(ProvisioningAttempt.Status.GENERATE_CSR_FAILED);
164             throw e;
165         }
166     }
167 
168     /**
169      * Gets the implemented version for IRemotelyProvisionedComponent.
170      */
getVersion()171     public int getVersion() throws RemoteException {
172         return mBinder.getHardwareInfo().versionNumber;
173     }
174 
175     /**
176      * Gets the maximum size of a batch that's supported by the underlying implementation. Since
177      * memory may be tightly constrained in the trusted runtime, the maximum batch size may be
178      * quite small. The HAL mandates an absolute minimum of 20, which is enforced via VTS.
179      */
getBatchSize()180     public int getBatchSize() throws RemoteException {
181         final int maxBatchSize = 512;
182 
183         int batchSize = mBinder.getHardwareInfo().supportedNumKeysInCsr;
184 
185         if (batchSize < RpcHardwareInfo.MIN_SUPPORTED_NUM_KEYS_IN_CSR) {
186             Log.w(TAG, "HAL returned a batch size that's too small (" + batchSize
187                     + "), defaulting to " + RpcHardwareInfo.MIN_SUPPORTED_NUM_KEYS_IN_CSR);
188             return RpcHardwareInfo.MIN_SUPPORTED_NUM_KEYS_IN_CSR;
189         }
190 
191         if (batchSize > maxBatchSize) {
192             Log.w(TAG, "HAL returned a batch size that's too large (" + batchSize
193                     + "), defaulting to " + maxBatchSize);
194             return maxBatchSize;
195         }
196 
197         return batchSize;
198     }
199 
200 }
201