1 /* 2 * Copyright (C) 2022 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.rkpdapp.provisioner; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.util.Log; 22 23 import androidx.work.WorkManager; 24 import androidx.work.Worker; 25 import androidx.work.WorkerParameters; 26 27 import com.android.rkpdapp.GeekResponse; 28 import com.android.rkpdapp.RkpdException; 29 import com.android.rkpdapp.database.ProvisionedKeyDao; 30 import com.android.rkpdapp.database.RkpdDatabase; 31 import com.android.rkpdapp.interfaces.ServerInterface; 32 import com.android.rkpdapp.interfaces.ServiceManagerInterface; 33 import com.android.rkpdapp.interfaces.SystemInterface; 34 import com.android.rkpdapp.metrics.ProvisioningAttempt; 35 import com.android.rkpdapp.metrics.RkpdStatsLog; 36 import com.android.rkpdapp.utils.Settings; 37 38 import java.time.Instant; 39 40 import co.nstant.in.cbor.CborException; 41 42 /** 43 * A class that extends Worker in order to be scheduled to maintain the attestation key pool at 44 * regular intervals. If the job determines that more keys need to be generated and signed, it would 45 * drive that process. 46 */ 47 public class PeriodicProvisioner extends Worker { 48 public static final String UNIQUE_WORK_NAME = "ProvisioningJob"; 49 private static final String TAG = "RkpdPeriodicProvisioner"; 50 51 private final Context mContext; 52 private final ProvisionedKeyDao mKeyDao; 53 PeriodicProvisioner(@onNull Context context, @NonNull WorkerParameters params)54 public PeriodicProvisioner(@NonNull Context context, @NonNull WorkerParameters params) { 55 super(context, params); 56 mContext = context; 57 mKeyDao = RkpdDatabase.getDatabase(context).provisionedKeyDao(); 58 } 59 60 /** 61 * Overrides the default doWork method to handle checking and provisioning the device. 62 */ 63 @Override doWork()64 public Result doWork() { 65 Log.i(TAG, "Waking up; checking provisioning state."); 66 67 SystemInterface[] irpcs = ServiceManagerInterface.getAllInstances(); 68 if (irpcs.length == 0) { 69 Log.i(TAG, "Stopping periodic provisioner: there are no IRPC HALs"); 70 WorkManager.getInstance(mContext).cancelWorkById(getId()); 71 return Result.success(); 72 } 73 74 if (Settings.getDefaultUrl().isEmpty()) { 75 Log.i(TAG, "Stopping periodic provisioner: system has no configured server endpoint"); 76 WorkManager.getInstance(mContext).cancelWorkById(getId()); 77 return Result.success(); 78 } 79 80 try (ProvisioningAttempt metrics = ProvisioningAttempt.createScheduledAttemptMetrics( 81 mContext)) { 82 // Clean up the expired keys 83 mKeyDao.deleteExpiringKeys(Instant.now()); 84 85 // Fetch geek from the server and figure out whether provisioning needs to be stopped. 86 GeekResponse response; 87 try { 88 response = new ServerInterface(mContext).fetchGeekAndUpdate(metrics); 89 } catch (InterruptedException | RkpdException e) { 90 Log.e(TAG, "Error fetching configuration from the RKP server", e); 91 return Result.failure(); 92 } 93 94 if (response.numExtraAttestationKeys == 0) { 95 Log.i(TAG, "Disable provisioning and delete all keys."); 96 metrics.setEnablement(ProvisioningAttempt.Enablement.DISABLED); 97 metrics.setStatus(ProvisioningAttempt.Status.PROVISIONING_DISABLED); 98 99 mKeyDao.deleteAllKeys(); 100 metrics.setIsKeyPoolEmpty(true); 101 return Result.success(); 102 } 103 104 Log.i(TAG, "Total services found implementing IRPC: " + irpcs.length); 105 Provisioner provisioner = new Provisioner(mContext, mKeyDao); 106 Result result = Result.success(); 107 for (SystemInterface irpc : irpcs) { 108 Log.i(TAG, "Starting provisioning for " + irpc); 109 try { 110 provisioner.provisionKeys(metrics, irpc, response); 111 recordKeyPoolStatsAtom(irpc); 112 Log.i(TAG, "Successfully provisioned " + irpc); 113 } catch (CborException e) { 114 Log.e(TAG, "Error parsing CBOR for " + irpc, e); 115 result = Result.failure(); 116 } catch (InterruptedException | RkpdException e) { 117 Log.e(TAG, "Error provisioning keys for " + irpc, e); 118 result = Result.failure(); 119 } 120 } 121 return result; 122 } 123 } 124 recordKeyPoolStatsAtom(SystemInterface irpc)125 private void recordKeyPoolStatsAtom(SystemInterface irpc) { 126 String halName = irpc.getServiceName(); 127 final int numExpiring = mKeyDao.getTotalExpiringKeysForIrpc(halName, 128 Settings.getExpirationTime(mContext)); 129 final int numUnassigned = mKeyDao.getTotalUnassignedKeysForIrpc(halName); 130 final int total = mKeyDao.getTotalKeysForIrpc(halName); 131 Log.i(TAG, "Logging atom metric for pool status, total: " + total + ", numExpiring: " 132 + numExpiring + ", numUnassigned: " + numUnassigned); 133 RkpdStatsLog.write(RkpdStatsLog.RKPD_POOL_STATS, irpc.getServiceName(), numExpiring, 134 numUnassigned, total); 135 } 136 } 137