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