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