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