1 /*
2  * Copyright (C) 2024 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.avf.rkpdapp.e2etest;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 import static com.google.common.truth.TruthJUnit.assume;
22 
23 import android.hardware.security.keymint.IRemotelyProvisionedComponent;
24 import android.os.Process;
25 import android.os.SystemProperties;
26 
27 import androidx.work.ListenableWorker;
28 import androidx.work.testing.TestWorkerBuilder;
29 
30 import com.android.compatibility.common.util.CddTest;
31 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
32 import com.android.rkpdapp.database.ProvisionedKey;
33 import com.android.rkpdapp.database.ProvisionedKeyDao;
34 import com.android.rkpdapp.database.RkpdDatabase;
35 import com.android.rkpdapp.interfaces.ServerInterface;
36 import com.android.rkpdapp.interfaces.ServiceManagerInterface;
37 import com.android.rkpdapp.interfaces.SystemInterface;
38 import com.android.rkpdapp.provisioner.PeriodicProvisioner;
39 import com.android.rkpdapp.testutil.SystemInterfaceSelector;
40 import com.android.rkpdapp.utils.Settings;
41 import com.android.rkpdapp.utils.X509Utils;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Rule;
46 import org.junit.Test;
47 import org.junit.rules.Timeout;
48 import org.junit.runner.RunWith;
49 import org.junit.runners.BlockJUnit4ClassRunner;
50 
51 import java.security.cert.X509Certificate;
52 import java.time.Instant;
53 import java.util.concurrent.Executors;
54 
55 /**
56  * End-to-end test for the pVM remote attestation (key provisioning/VM attestation).
57  *
58  * <p>To run this test, you need to:
59  *
60  * - Have an arm64 device supporting protected VMs.
61  * - Have a stable network connection on the device.
62  * - Have the RKP server hostname configured in the device. If not, you can set it using:
63  * $ adb shell setprop remote_provisioning.hostname remoteprovisioning.googleapis.com
64  */
65 @RunWith(BlockJUnit4ClassRunner.class)
66 public class AvfIntegrationTest extends MicrodroidDeviceTestBase {
67     private static final String SERVICE_NAME = IRemotelyProvisionedComponent.DESCRIPTOR + "/avf";
68 
69     private ProvisionedKeyDao mKeyDao;
70     private PeriodicProvisioner mProvisioner;
71     private AutoCloseable mPeriodicProvisionerLock;
72 
73     @Rule public final Timeout mTimeout = Timeout.seconds(30);
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         assume().withMessage("AVF key provisioning is not supported on CF.")
78                 .that(isCuttlefish())
79                 .isFalse();
80         assume().withMessage("The RKP server hostname is not configured -- assume RKP disabled.")
81                 .that(SystemProperties.get("remote_provisioning.hostname"))
82                 .isNotEmpty();
83         assume().withMessage("RKP Integration tests rely on network availability.")
84                 .that(ServerInterface.isNetworkConnected(getContext()))
85                 .isTrue();
86 
87         mPeriodicProvisionerLock = PeriodicProvisioner.lock();
88         Settings.clearPreferences(getContext());
89         mKeyDao = RkpdDatabase.getDatabase(getContext()).provisionedKeyDao();
90         mKeyDao.deleteAllKeys();
91 
92         mProvisioner =
93                 TestWorkerBuilder.from(
94                                 getContext(),
95                                 PeriodicProvisioner.class,
96                                 Executors.newSingleThreadExecutor())
97                         .build();
98 
99         SystemInterface systemInterface =
100                 SystemInterfaceSelector.getSystemInterfaceForServiceName(SERVICE_NAME);
101         ServiceManagerInterface.setInstances(new SystemInterface[] {systemInterface});
102     }
103 
104     @After
tearDown()105     public void tearDown() throws Exception {
106         ServiceManagerInterface.setInstances(null);
107         if (mKeyDao != null) {
108             mKeyDao.deleteAllKeys();
109         }
110         Settings.clearPreferences(getContext());
111         if (mPeriodicProvisionerLock != null) {
112             mPeriodicProvisionerLock.close();
113         }
114     }
115 
116     @Test
117     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
provisioningSucceeds()118     public void provisioningSucceeds() throws Exception {
119         assertWithMessage("There should be no keys in the database before provisioning")
120                 .that(mKeyDao.getTotalKeysForIrpc(SERVICE_NAME))
121                 .isEqualTo(0);
122 
123         // Check provisioning succeeds.
124         assertThat(mProvisioner.doWork()).isEqualTo(ListenableWorker.Result.success());
125         int totalUnassignedKeys = mKeyDao.getTotalUnassignedKeysForIrpc(SERVICE_NAME);
126         assertWithMessage("There should be unassigned keys in the database after provisioning")
127                 .that(totalUnassignedKeys)
128                 .isGreaterThan(0);
129 
130         ProvisionedKey attestationKey =
131                 mKeyDao.getKeyForClientAndIrpc(SERVICE_NAME, Process.SYSTEM_UID, Process.myUid());
132         assertThat(attestationKey).isNull();
133         // Assign a key to a new client.
134         attestationKey =
135                 mKeyDao.getOrAssignKey(
136                         SERVICE_NAME, Instant.now(), Process.SYSTEM_UID, Process.myUid());
137 
138         // Assert.
139         assertThat(attestationKey).isNotNull();
140         assertThat(attestationKey.irpcHal).isEqualTo(SERVICE_NAME);
141         assertWithMessage("One key should be assigned")
142                 .that(mKeyDao.getTotalUnassignedKeysForIrpc(SERVICE_NAME))
143                 .isEqualTo(totalUnassignedKeys - 1);
144 
145         // Parsing the certificate chain successfully indicates that the chain is well-formed,
146         // each certificate is signed by the next one, and the root certificate is self-signed.
147         X509Certificate[] certs = X509Utils.formatX509Certs(attestationKey.certificateChain);
148         assertThat(certs.length).isGreaterThan(1);
149         assertThat(certs[0].getSubjectX500Principal().getName()).contains("O=AVF");
150     }
151 }
152