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