• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2021 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.remoteprovisioner;
18 
19 import android.annotation.NonNull;
20 import android.hardware.security.keymint.DeviceInfo;
21 import android.hardware.security.keymint.ProtectedData;
22 import android.os.RemoteException;
23 import android.os.ServiceSpecificException;
24 import android.security.remoteprovisioning.AttestationPoolStatus;
25 import android.security.remoteprovisioning.IRemoteProvisioning;
26 import android.util.Log;
27 
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.util.List;
31 
32 import co.nstant.in.cbor.CborBuilder;
33 import co.nstant.in.cbor.CborDecoder;
34 import co.nstant.in.cbor.CborEncoder;
35 import co.nstant.in.cbor.CborException;
36 import co.nstant.in.cbor.builder.ArrayBuilder;
37 import co.nstant.in.cbor.model.Array;
38 import co.nstant.in.cbor.model.DataItem;
39 
40 /**
41  * Provides convenience methods for interfacing with the android.security.remoteprovisioning system
42  * service. Since the remoteprovisioning API is internal only and subject to change, it is handy
43  * to have an abstraction layer to reduce the impact of these changes on the app.
44  */
45 public class SystemInterface {
46 
47     private static final String TAG = "RemoteProvisioner";
48 
makeProtectedHeaders()49     private static byte[] makeProtectedHeaders() throws CborException {
50         ByteArrayOutputStream baos = new ByteArrayOutputStream();
51         new CborEncoder(baos).encode(new CborBuilder()
52                 .addMap()
53                     .put(1, 5)
54                 .end()
55                 .build());
56         return baos.toByteArray();
57     }
58 
encodePayload(List<DataItem> keys)59     private static byte[] encodePayload(List<DataItem> keys) throws CborException {
60         ByteArrayOutputStream baos = new ByteArrayOutputStream();
61         ArrayBuilder<CborBuilder> builder = new CborBuilder().addArray();
62         for (int i = 1; i < keys.size(); i++) {
63             builder = builder.add(keys.get(i));
64         }
65         new CborEncoder(baos).encode(builder.end().build());
66         return baos.toByteArray();
67     }
68 
69     /**
70      * Sends a generateCsr request over the binder interface. `dataBlob` is an out parameter that
71      * will be populated by the underlying binder service.
72      */
generateCsr(boolean testMode, int numKeys, int secLevel, byte[] geekChain, byte[] challenge, ProtectedData protectedData, DeviceInfo deviceInfo, @NonNull IRemoteProvisioning binder, ProvisionerMetrics metrics)73     public static byte[] generateCsr(boolean testMode, int numKeys, int secLevel,
74             byte[] geekChain, byte[] challenge, ProtectedData protectedData, DeviceInfo deviceInfo,
75             @NonNull IRemoteProvisioning binder,
76             ProvisionerMetrics metrics) {
77         try {
78             Log.i(TAG, "Packaging " + numKeys + " keys into a CSR. Test: " + testMode);
79             ProtectedData dataBundle = new ProtectedData();
80             byte[] macedPublicKeys = null;
81             try (ProvisionerMetrics.StopWatch ignored = metrics.startBinderWait()) {
82                 macedPublicKeys = binder.generateCsr(testMode,
83                                                      numKeys,
84                                                      geekChain,
85                                                      challenge,
86                                                      secLevel,
87                                                      protectedData,
88                                                      deviceInfo);
89             }
90 
91             if (macedPublicKeys == null) {
92                 Log.e(TAG, "Keystore didn't generate a CSR successfully.");
93                 metrics.setStatus(ProvisionerMetrics.Status.GENERATE_CSR_FAILED);
94                 return null;
95             }
96             ByteArrayInputStream bais = new ByteArrayInputStream(macedPublicKeys);
97             List<DataItem> dataItems = new CborDecoder(bais).decode();
98             List<DataItem> macInfo = ((Array) dataItems.get(0)).getDataItems();
99             ByteArrayOutputStream baos = new ByteArrayOutputStream();
100             new CborEncoder(baos).encode(new CborBuilder()
101                     .addArray()
102                         .add(makeProtectedHeaders())
103                         .addMap() //unprotected headers
104                             .end()
105                         .add(encodePayload(macInfo))
106                         .add(macInfo.get(0))
107                         .end()
108                     .build());
109             return baos.toByteArray();
110         } catch (RemoteException e) {
111             Log.e(TAG, "Failed to generate CSR blob", e);
112         } catch (ServiceSpecificException e) {
113             Log.e(TAG, "Failure in Keystore or Keymint to facilitate blob generation.", e);
114         } catch (CborException e) {
115             Log.e(TAG, "Failed to parse/build CBOR", e);
116         }
117         metrics.setStatus(ProvisionerMetrics.Status.GENERATE_CSR_FAILED);
118         return null;
119     }
120 
121     /**
122      * Sends a provisionCertChain request down to the underlying remote provisioning binder service.
123      */
provisionCertChain(byte[] rawPublicKey, byte[] encodedCert, byte[] certChain, long expirationDate, int secLevel, IRemoteProvisioning binder, ProvisionerMetrics metrics)124     public static boolean provisionCertChain(byte[] rawPublicKey, byte[] encodedCert,
125             byte[] certChain,
126             long expirationDate, int secLevel,
127             IRemoteProvisioning binder,
128             ProvisionerMetrics metrics) {
129         try (ProvisionerMetrics.StopWatch ignored = metrics.startBinderWait()) {
130             binder.provisionCertChain(rawPublicKey, encodedCert, certChain,
131                     expirationDate, secLevel);
132             return true;
133         } catch (RemoteException e) {
134             Log.e(TAG, "Error on the binder side when attempting to provision the signed chain",
135                     e);
136         } catch (ServiceSpecificException e) {
137             Log.e(TAG, "Error on the Keystore side", e);
138         }
139         metrics.setStatus(ProvisionerMetrics.Status.INSERT_CHAIN_INTO_POOL_FAILED);
140         return false;
141     }
142 
143     /**
144      * Returns the pool status for a given {@code secLevel}, with the expiration date configured to
145      * the value passed in as {@code expiringBy}.
146      */
getPoolStatus(long expiringBy, int secLevel, IRemoteProvisioning binder, ProvisionerMetrics metrics)147     public static AttestationPoolStatus getPoolStatus(long expiringBy,
148                                                       int secLevel,
149                                                       IRemoteProvisioning binder,
150                                                       ProvisionerMetrics metrics)
151             throws RemoteException {
152         try (ProvisionerMetrics.StopWatch ignored = metrics.startBinderWait()) {
153             AttestationPoolStatus status = binder.getPoolStatus(expiringBy, secLevel);
154             Log.i(TAG, "Pool status " + status.attested + ", " + status.unassigned + ", "
155                     + status.expiring + ", " + status.total);
156             metrics.setIsKeyPoolEmpty(status.unassigned == 0);
157             return status;
158         } catch (ServiceSpecificException e) {
159             Log.e(TAG, "Failure in Keystore", e);
160             metrics.setStatus(ProvisionerMetrics.Status.GET_POOL_STATUS_FAILED);
161             throw new RemoteException(e);
162         }
163     }
164 
165     /**
166      * Generates an attestation key pair.
167      *
168      * @param isTestMode Whether or not to generate a test key, which would accept any EEK later.
169      * @param secLevel Which security level to generate the key for.
170      */
generateKeyPair(boolean isTestMode, int secLevel, IRemoteProvisioning binder, ProvisionerMetrics metrics)171     public static void generateKeyPair(boolean isTestMode, int secLevel, IRemoteProvisioning binder,
172             ProvisionerMetrics metrics)
173             throws RemoteException {
174         try (ProvisionerMetrics.StopWatch ignored = metrics.startBinderWait()) {
175             binder.generateKeyPair(isTestMode, secLevel);
176         } catch (ServiceSpecificException e) {
177             Log.e(TAG, "Failure in Keystore or KeyMint.", e);
178             metrics.setStatus(ProvisionerMetrics.Status.GENERATE_KEYPAIR_FAILED);
179             throw new RemoteException(e);
180         }
181     }
182 }
183