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 static java.lang.Math.min; 20 21 import android.content.Context; 22 import android.os.RemoteException; 23 import android.os.ServiceManager; 24 import android.security.remoteprovisioning.AttestationPoolStatus; 25 import android.security.remoteprovisioning.IRemoteProvisioning; 26 import android.security.remoteprovisioning.ImplInfo; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.work.Worker; 31 import androidx.work.WorkerParameters; 32 33 import java.time.Duration; 34 35 /** 36 * A class that extends Worker in order to be scheduled to check the status of the attestation 37 * key pool at regular intervals. If the job determines that more keys need to be generated and 38 * signed, it drives that process. 39 */ 40 public class PeriodicProvisioner extends Worker { 41 42 private static final int FAILURE_MAXIMUM = 5; 43 private static final int SAFE_CSR_BATCH_SIZE = 20; 44 45 // How long to wait in between key pair generations to avoid flooding keystore with requests. 46 private static final Duration KEY_GENERATION_PAUSE = Duration.ofMillis(1000); 47 48 private static final String SERVICE = "android.security.remoteprovisioning"; 49 private static final String TAG = "RemoteProvisioningService"; 50 private Context mContext; 51 PeriodicProvisioner(@onNull Context context, @NonNull WorkerParameters params)52 public PeriodicProvisioner(@NonNull Context context, @NonNull WorkerParameters params) { 53 super(context, params); 54 mContext = context; 55 } 56 57 /** 58 * Overrides the default doWork method to handle checking and provisioning the device. 59 */ 60 @Override doWork()61 public Result doWork() { 62 Log.i(TAG, "Waking up; checking provisioning state."); 63 try (ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics( 64 mContext)) { 65 IRemoteProvisioning binder = 66 IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); 67 if (binder == null) { 68 Log.e(TAG, "Binder returned null pointer to RemoteProvisioning service."); 69 metrics.setStatus(ProvisionerMetrics.Status.INTERNAL_ERROR); 70 return Result.failure(); 71 } 72 ImplInfo[] implInfos = binder.getImplementationInfo(); 73 if (implInfos == null) { 74 Log.e(TAG, "No instances of IRemotelyProvisionedComponent registered in " 75 + SERVICE); 76 metrics.setStatus(ProvisionerMetrics.Status.NO_PROVISIONING_NEEDED); 77 return Result.failure(); 78 } 79 int[] keysNeededForSecLevel = new int[implInfos.length]; 80 GeekResponse resp = null; 81 if (SettingsManager.getExtraSignedKeysAvailable(mContext) == 0) { 82 // Provisioning has been purposefully disabled in the past. Go ahead and grab 83 // an EEK just to see if provisioning should resume. 84 resp = fetchGeekAndUpdate(binder, metrics); 85 if (resp.numExtraAttestationKeys == 0) { 86 metrics.setEnablement(ProvisionerMetrics.Enablement.DISABLED); 87 metrics.setStatus(ProvisionerMetrics.Status.PROVISIONING_DISABLED); 88 return Result.success(); 89 } 90 } 91 boolean provisioningNeeded = 92 isProvisioningNeeded(binder, 93 SettingsManager.getExpirationTime(mContext).toEpochMilli(), 94 implInfos, keysNeededForSecLevel, metrics); 95 if (!provisioningNeeded) { 96 metrics.setStatus(ProvisionerMetrics.Status.NO_PROVISIONING_NEEDED); 97 return Result.success(); 98 } 99 // Resp may already be populated in the extremely rare case that this job is executing 100 // to resume provisioning for the first time after a server-induced RKP shutdown. Grab 101 // a fresh response anyways to refresh the challenge. 102 resp = fetchGeekAndUpdate(binder, metrics); 103 if (resp.numExtraAttestationKeys == 0) { 104 metrics.setEnablement(ProvisionerMetrics.Enablement.DISABLED); 105 metrics.setStatus(ProvisionerMetrics.Status.PROVISIONING_DISABLED); 106 return Result.success(); 107 } else { 108 // Just in case we got an updated config, let's recalculate how many keys need to 109 // be provisioned. 110 if (!isProvisioningNeeded(binder, 111 SettingsManager.getExpirationTime(mContext).toEpochMilli(), 112 implInfos, keysNeededForSecLevel, metrics)) { 113 metrics.setStatus(ProvisionerMetrics.Status.NO_PROVISIONING_NEEDED); 114 return Result.success(); 115 } 116 } 117 for (int i = 0; i < implInfos.length; i++) { 118 // Break very large CSR requests into chunks, so as not to overwhelm the 119 // backend. 120 int keysToProvision = keysNeededForSecLevel[i]; 121 batchProvision(binder, mContext, keysToProvision, implInfos[i].secLevel, 122 resp.getGeekChain(implInfos[i].supportedCurve), resp.getChallenge(), 123 metrics); 124 } 125 return Result.success(); 126 } catch (RemoteException e) { 127 Log.e(TAG, "Error on the binder side during provisioning.", e); 128 return Result.failure(); 129 } catch (InterruptedException e) { 130 Log.e(TAG, "Provisioner thread interrupted.", e); 131 return Result.failure(); 132 } catch (RemoteProvisioningException e) { 133 Log.e(TAG, "Encountered RemoteProvisioningException", e); 134 if (SettingsManager.getFailureCounter(mContext) > FAILURE_MAXIMUM) { 135 Log.e(TAG, "Too many failures, resetting defaults."); 136 SettingsManager.resetDefaultConfig(mContext); 137 } 138 return Result.failure(); 139 } 140 } 141 142 /** 143 * Fetch a GEEK from the server and update SettingsManager appropriately with the return 144 * values. This will also delete all keys in the attestation key pool if the server has 145 * indicated that RKP should be turned off. 146 */ fetchGeekAndUpdate(IRemoteProvisioning binder, ProvisionerMetrics metrics)147 private GeekResponse fetchGeekAndUpdate(IRemoteProvisioning binder, 148 ProvisionerMetrics metrics) 149 throws RemoteException, RemoteProvisioningException { 150 GeekResponse resp = ServerInterface.fetchGeek(mContext, metrics); 151 SettingsManager.setDeviceConfig(mContext, 152 resp.numExtraAttestationKeys, 153 resp.timeToRefresh, 154 resp.provisioningUrl); 155 156 if (resp.numExtraAttestationKeys == 0) { 157 // The server has indicated that provisioning is disabled. 158 try (ProvisionerMetrics.StopWatch ignored = metrics.startBinderWait()) { 159 binder.deleteAllKeys(); 160 } 161 } 162 return resp; 163 } 164 batchProvision(IRemoteProvisioning binder, Context context, int keysToProvision, int secLevel, byte[] geekChain, byte[] challenge, ProvisionerMetrics metrics)165 public static void batchProvision(IRemoteProvisioning binder, Context context, 166 int keysToProvision, int secLevel, 167 byte[] geekChain, byte[] challenge, 168 ProvisionerMetrics metrics) 169 throws RemoteException, RemoteProvisioningException { 170 while (keysToProvision != 0) { 171 int batchSize = min(keysToProvision, SAFE_CSR_BATCH_SIZE); 172 Log.i(TAG, "Requesting " + batchSize + " keys to be provisioned."); 173 Provisioner.provisionCerts(batchSize, 174 secLevel, 175 geekChain, 176 challenge, 177 binder, 178 context, 179 metrics); 180 keysToProvision -= batchSize; 181 } 182 metrics.setStatus(ProvisionerMetrics.Status.KEYS_SUCCESSFULLY_PROVISIONED); 183 } 184 isProvisioningNeeded( IRemoteProvisioning binder, long expiringBy, ImplInfo[] implInfos, int[] keysNeededForSecLevel, ProvisionerMetrics metrics)185 private boolean isProvisioningNeeded( 186 IRemoteProvisioning binder, long expiringBy, ImplInfo[] implInfos, 187 int[] keysNeededForSecLevel, ProvisionerMetrics metrics) 188 throws InterruptedException, RemoteException { 189 if (implInfos == null || keysNeededForSecLevel == null 190 || keysNeededForSecLevel.length != implInfos.length) { 191 Log.e(TAG, "Invalid argument."); 192 return false; 193 } 194 boolean provisioningNeeded = false; 195 for (int i = 0; i < implInfos.length; i++) { 196 keysNeededForSecLevel[i] = 197 generateNumKeysNeeded(binder, 198 mContext, 199 expiringBy, 200 implInfos[i].secLevel, 201 metrics); 202 if (keysNeededForSecLevel[i] > 0) { 203 provisioningNeeded = true; 204 } 205 } 206 return provisioningNeeded; 207 } 208 209 /** 210 * This method will generate and bundle up keys for signing to make sure that there will be 211 * enough keys available for use by the system when current keys expire. 212 * 213 * Enough keys is defined by checking how many keys are currently assigned to apps and 214 * generating enough keys to cover any expiring certificates plus a bit of buffer room 215 * defined by {@code sExtraSignedKeysAvailable}. 216 * 217 * This allows devices to dynamically resize their key pools as the user downloads and 218 * removes apps that may also use attestation. 219 */ generateNumKeysNeeded(IRemoteProvisioning binder, Context context, long expiringBy, int secLevel, ProvisionerMetrics metrics)220 public static int generateNumKeysNeeded(IRemoteProvisioning binder, Context context, 221 long expiringBy, int secLevel, 222 ProvisionerMetrics metrics) 223 throws InterruptedException, RemoteException { 224 AttestationPoolStatus pool = 225 SystemInterface.getPoolStatus(expiringBy, secLevel, binder, metrics); 226 if (pool == null) { 227 Log.e(TAG, "Failed to fetch pool status."); 228 return 0; 229 } 230 Log.i(TAG, "Pool status.\nTotal: " + pool.total 231 + "\nAttested: " + pool.attested 232 + "\nUnassigned: " + pool.unassigned 233 + "\nExpiring: " + pool.expiring); 234 StatsProcessor.PoolStats stats = StatsProcessor.processPool( 235 pool, SettingsManager.getExtraSignedKeysAvailable(context)); 236 if (!stats.provisioningNeeded) { 237 Log.i(TAG, "No provisioning needed."); 238 return 0; 239 } 240 Log.i(TAG, "Need to generate " + stats.keysToGenerate + " keys."); 241 int generated; 242 for (generated = 0; generated < stats.keysToGenerate; generated++) { 243 SystemInterface.generateKeyPair(SettingsManager.isTestMode(), secLevel, binder, 244 metrics); 245 // Prioritize provisioning if there are no keys available. No keys being available 246 // indicates that this is the first time a device is being brought online. 247 if (pool.total != 0) { 248 Thread.sleep(KEY_GENERATION_PAUSE.toMillis()); 249 } 250 } 251 Log.i(TAG, "Generated " + generated + " keys. " + stats.unattestedKeys 252 + " keys were also available for signing previous to generation."); 253 return stats.idealTotalSignedKeys; 254 } 255 256 257 } 258