• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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