• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2020 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.content.Context;
21 import android.hardware.security.keymint.DeviceInfo;
22 import android.hardware.security.keymint.ProtectedData;
23 import android.security.IGenerateRkpKeyService;
24 import android.security.remoteprovisioning.IRemoteProvisioning;
25 import android.util.Log;
26 
27 import java.security.cert.CertificateEncodingException;
28 import java.security.cert.CertificateException;
29 import java.security.cert.X509Certificate;
30 import java.util.List;
31 
32 /**
33  * Provides an easy package to run the provisioning process from start to finish, interfacing
34  * with the remote provisioning system service and the server backend in order to provision
35  * attestation certificates to the device.
36  */
37 public class Provisioner {
38     protected static final String TAG = "RemoteProvisioningService";
39 
40     /**
41      * Drives the process of provisioning certs. The method passes the data fetched from the
42      * provisioning server along with the requested number of keys to the remote provisioning
43      * system backend. The backend will talk to the underlying IRemotelyProvisionedComponent
44      * interface in order to get a CSR bundle generated, along with an encrypted package containing
45      * metadata that the server needs in order to make decisions about provisioning.
46      *
47      * This method then passes that bundle back out to the server backend, waits for the response,
48      * and, if successful, passes the certificate chains back to the remote provisioning service to
49      * be stored and later assigned to apps requesting a key attestation.
50      *
51      * @param numKeys The number of keys to be signed. The service will do a best-effort to
52      *                provision the number requested, but if the number requested is larger
53      *                than the number of unsigned attestation key pairs available, it will
54      *                only sign the number that is available at time of calling.
55      * @param secLevel Which KM instance should be used to provision certs.
56      * @param geekChain The certificate chain that signs the endpoint encryption key.
57      * @param challenge A server provided challenge to ensure freshness of the response.
58      * @param binder The IRemoteProvisioning binder interface needed by the method to handle talking
59      *               to the remote provisioning system component.
60      * @param context The application context object which enables this method to make use of
61      *                SettingsManager.
62      * @param metrics Datapoint collector for logging metrics.
63      * @return The number of certificates provisioned. Ideally, this should equal {@code numKeys}.
64      */
provisionCerts(int numKeys, int secLevel, byte[] geekChain, byte[] challenge, @NonNull IRemoteProvisioning binder, Context context, ProvisionerMetrics metrics)65     public static int provisionCerts(int numKeys, int secLevel, byte[] geekChain, byte[] challenge,
66             @NonNull IRemoteProvisioning binder, Context context,
67             ProvisionerMetrics metrics) throws
68             RemoteProvisioningException {
69         Log.i(TAG, "Request for " + numKeys + " keys to be provisioned.");
70         if (numKeys < 1) {
71             throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
72                     "Request at least 1 key to be signed. Num requested: " + numKeys);
73         }
74         DeviceInfo deviceInfo = new DeviceInfo();
75         ProtectedData protectedData = new ProtectedData();
76         byte[] macedKeysToSign = SystemInterface.generateCsr(SettingsManager.isTestMode(), numKeys,
77                 secLevel, geekChain, challenge, protectedData, deviceInfo, binder, metrics);
78         if (macedKeysToSign == null || protectedData.protectedData == null
79                 || deviceInfo.deviceInfo == null) {
80             throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
81                     "Keystore failed to generate a payload");
82         }
83         byte[] certificateRequest =
84                 CborUtils.buildCertificateRequest(deviceInfo.deviceInfo,
85                                                   challenge,
86                                                   protectedData.protectedData,
87                                                   macedKeysToSign,
88                                                   CborUtils.buildUnverifiedDeviceInfo());
89         if (certificateRequest == null) {
90             throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
91                     "Failed to serialize the payload generated by keystore.");
92         }
93         List<byte[]> certChains = ServerInterface.requestSignedCertificates(context,
94                         certificateRequest, challenge, metrics);
95         if (certChains == null) {
96             // This is marked as an internal error, because ServerInterface should never return
97             // null, and if it does it's indicative of a bug.
98             throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
99                     "Server response failed on provisioning attempt.");
100         }
101         Log.i(TAG, "Received " + certChains.size() + " certificate chains from the server.");
102         int provisioned = 0;
103         for (byte[] certChain : certChains) {
104             // DER encoding specifies leaf to root ordering. Pull the public key and expiration
105             // date from the leaf.
106             X509Certificate cert;
107             try {
108                 cert = X509Utils.formatX509Certs(certChain)[0];
109             } catch (CertificateException e) {
110                 throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
111                         "Failed to interpret DER encoded certificate chain", e);
112             }
113             // getTime returns the time in *milliseconds* since the epoch.
114             long expirationDate = cert.getNotAfter().getTime();
115             byte[] rawPublicKey = X509Utils.getAndFormatRawPublicKey(cert);
116             if (rawPublicKey == null) {
117                 Log.e(TAG, "Skipping malformed public key.");
118                 continue;
119             }
120             try {
121                 if (SystemInterface.provisionCertChain(rawPublicKey, cert.getEncoded(), certChain,
122                                                        expirationDate, secLevel, binder, metrics)) {
123                     provisioned++;
124                 }
125             } catch (CertificateEncodingException e) {
126                 throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
127                         "Error re-encoding the decoded batch cert", e);
128             }
129         }
130 
131         // In lieu of metrics, do our best to spot when things are "going really badly" by looking
132         // for edge cases where fewer than half of the key requests succeed. In the future, we
133         // should add metrics to get a picture of how healthy this process is.
134         if (provisioned < (numKeys / 2)) {
135             throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR,
136                     "Requested " + numKeys + " keys, provisioned " + provisioned);
137         }
138 
139         Log.i(TAG, "In provisionCerts: Requested " + numKeys + " keys. "
140                    + provisioned + " were provisioned.");
141         return provisioned;
142     }
143 }
144