Cancel periodic provisioner on unsupported devices
If a device is either not configured for RKP (not hostname) or it
has no IRemotelyProvisionedComponent HALs, then we should stop trying
to run the background provisioning job.
Bug: 272304888
Test: RkpdAppIntegrationTests RkpdAppUnitTests RkpdAppStressTests
Change-Id: I3555a40b0f0b5e4f65212acda380b5cd1df041df
diff --git a/app/src/com/android/rkpdapp/BootReceiver.java b/app/src/com/android/rkpdapp/BootReceiver.java
index 6d610cc..1048572 100644
--- a/app/src/com/android/rkpdapp/BootReceiver.java
+++ b/app/src/com/android/rkpdapp/BootReceiver.java
@@ -57,7 +57,7 @@
.build();
WorkManager
.getInstance(context)
- .enqueueUniquePeriodicWork("ProvisioningJob",
+ .enqueueUniquePeriodicWork(PeriodicProvisioner.UNIQUE_WORK_NAME,
ExistingPeriodicWorkPolicy.UPDATE, // Replace on reboot.
workRequest);
diff --git a/app/src/com/android/rkpdapp/provisioner/PeriodicProvisioner.java b/app/src/com/android/rkpdapp/provisioner/PeriodicProvisioner.java
index 6097906..40c26c9 100644
--- a/app/src/com/android/rkpdapp/provisioner/PeriodicProvisioner.java
+++ b/app/src/com/android/rkpdapp/provisioner/PeriodicProvisioner.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.util.Log;
+import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
@@ -31,6 +32,7 @@
import com.android.rkpdapp.interfaces.ServerInterface;
import com.android.rkpdapp.interfaces.ServiceManagerInterface;
import com.android.rkpdapp.interfaces.SystemInterface;
+import com.android.rkpdapp.utils.Settings;
import java.time.Instant;
@@ -42,6 +44,7 @@
* drive that process.
*/
public class PeriodicProvisioner extends Worker {
+ public static final String UNIQUE_WORK_NAME = "ProvisioningJob";
private static final String TAG = "RkpdPeriodicProvisioner";
private final Context mContext;
private final ProvisionedKeyDao mKeyDao;
@@ -58,6 +61,20 @@
@Override
public Result doWork() {
Log.i(TAG, "Waking up; checking provisioning state.");
+
+ SystemInterface[] irpcs = ServiceManagerInterface.getAllInstances();
+ if (irpcs.length == 0) {
+ Log.i(TAG, "Stopping periodic provisioner: there are no IRPC HALs");
+ WorkManager.getInstance(mContext).cancelWorkById(getId());
+ return Result.success();
+ }
+
+ if (Settings.getDefaultUrl().isEmpty()) {
+ Log.i(TAG, "Stopping periodic provisioner: system has no configured server endpoint");
+ WorkManager.getInstance(mContext).cancelWorkById(getId());
+ return Result.success();
+ }
+
try (ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(
mContext)) {
// Clean up the expired keys
@@ -82,8 +99,6 @@
return Result.success();
}
- // Figure out each of the IRPCs and get SystemInterface instance for each.
- SystemInterface[] irpcs = ServiceManagerInterface.getAllInstances();
Log.i(TAG, "Total services found implementing IRPC: " + irpcs.length);
Provisioner provisioner = new Provisioner(mContext, mKeyDao);
Result result = Result.success();
diff --git a/app/tests/unit/src/com/android/rkpdapp/unittest/PeriodicProvisionerTests.java b/app/tests/unit/src/com/android/rkpdapp/unittest/PeriodicProvisionerTests.java
index 8d376a8..9d7c584 100644
--- a/app/tests/unit/src/com/android/rkpdapp/unittest/PeriodicProvisionerTests.java
+++ b/app/tests/unit/src/com/android/rkpdapp/unittest/PeriodicProvisionerTests.java
@@ -30,9 +30,15 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.work.Configuration;
import androidx.work.ListenableWorker;
+import androidx.work.WorkInfo;
+import androidx.work.WorkManager;
+import androidx.work.testing.SynchronousExecutor;
import androidx.work.testing.TestWorkerBuilder;
+import androidx.work.testing.WorkManagerTestInitHelper;
+import com.android.rkpdapp.BootReceiver;
import com.android.rkpdapp.database.ProvisionedKey;
import com.android.rkpdapp.database.ProvisionedKeyDao;
import com.android.rkpdapp.database.RkpKey;
@@ -41,6 +47,7 @@
import com.android.rkpdapp.interfaces.SystemInterface;
import com.android.rkpdapp.provisioner.PeriodicProvisioner;
import com.android.rkpdapp.testutil.FakeRkpServer;
+import com.android.rkpdapp.testutil.SystemPropertySetter;
import com.android.rkpdapp.utils.Settings;
import org.junit.After;
@@ -51,6 +58,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import co.nstant.in.cbor.CborException;
@@ -72,6 +80,12 @@
mContext,
PeriodicProvisioner.class,
Executors.newSingleThreadExecutor()).build();
+
+ Configuration config = new Configuration.Builder()
+ .setExecutor(new SynchronousExecutor())
+ .build();
+ WorkManagerTestInitHelper.initializeTestWorkManager(mContext, config);
+
}
@After
@@ -81,16 +95,54 @@
Settings.clearPreferences(mContext);
}
+ private WorkInfo getProvisionerWorkInfo() throws ExecutionException, InterruptedException {
+ WorkManager workManager = WorkManager.getInstance(mContext);
+ List<WorkInfo> infos = workManager.getWorkInfosForUniqueWork(
+ PeriodicProvisioner.UNIQUE_WORK_NAME).get();
+ assertThat(infos.size()).isEqualTo(1);
+ return infos.get(0);
+ }
+
@Test
- public void provisionNoop() throws Exception {
- try (FakeRkpServer fakeRkpServer = new FakeRkpServer(
- FakeRkpServer.Response.FETCH_EEK_OK,
- // return error here, because signCerts should never be called
- FakeRkpServer.Response.INTERNAL_ERROR)) {
- saveUrlInSettings(fakeRkpServer);
- ServiceManagerInterface.setInstances(new SystemInterface[0]);
- assertThat(mProvisioner.doWork()).isEqualTo(ListenableWorker.Result.success());
+ public void provisionWithNoHals() throws Exception {
+ // setup work with boot receiver
+ new BootReceiver().onReceive(mContext, null);
+
+ WorkInfo worker = getProvisionerWorkInfo();
+ assertThat(worker.getState()).isEqualTo(WorkInfo.State.ENQUEUED);
+ assertThat(worker.getRunAttemptCount()).isEqualTo(0);
+
+ ServiceManagerInterface.setInstances(new SystemInterface[0]);
+ WorkManagerTestInitHelper.getTestDriver(mContext).setAllConstraintsMet(worker.getId());
+
+ // the worker should uninstall itself once it realizes it's not needed on this system
+ worker = getProvisionerWorkInfo();
+ assertThat(worker.getState()).isEqualTo(WorkInfo.State.CANCELLED);
+ assertThat(worker.getRunAttemptCount()).isEqualTo(1);
+
+ // verify the worker doesn't run again
+ WorkManagerTestInitHelper.getTestDriver(mContext).setAllConstraintsMet(worker.getId());
+ worker = getProvisionerWorkInfo();
+ assertThat(worker.getState()).isEqualTo(WorkInfo.State.CANCELLED);
+ assertThat(worker.getRunAttemptCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void provisionWithNoHostName() throws Exception {
+ // setup work with boot receiver
+ new BootReceiver().onReceive(mContext, null);
+
+ try (SystemPropertySetter ignored = SystemPropertySetter.setHostname("")) {
+ SystemInterface mockHal = mock(SystemInterface.class);
+ ServiceManagerInterface.setInstances(new SystemInterface[]{mockHal});
+
+ WorkInfo worker = getProvisionerWorkInfo();
+ WorkManagerTestInitHelper.getTestDriver(mContext).setAllConstraintsMet(worker.getId());
}
+
+ WorkInfo worker = getProvisionerWorkInfo();
+ assertThat(worker.getState()).isEqualTo(WorkInfo.State.CANCELLED);
+ assertThat(worker.getRunAttemptCount()).isEqualTo(1);
}
@Test
@@ -161,7 +213,10 @@
FakeRkpServer.Response.FETCH_EEK_OK,
FakeRkpServer.Response.SIGN_CERTS_OK_VALID_CBOR)) {
saveUrlInSettings(fakeRkpServer);
- ServiceManagerInterface.setInstances(new SystemInterface[]{});
+ SystemInterface mockHal = mock(SystemInterface.class);
+ doReturn("test-irpc").when(mockHal).getServiceName();
+ doReturn(new byte[1]).when(mockHal).generateCsr(any(), any(), any());
+ ServiceManagerInterface.setInstances(new SystemInterface[]{mockHal});
assertThat(mProvisioner.doWork()).isEqualTo(ListenableWorker.Result.success());
}