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