Merge "pvmfw: Handle dependent nodes in VM DTBO" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ec9042c..5b0c000 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -62,6 +62,9 @@
// TODO(b/325610326): Add this target to presubmit once there is enough
// SLO data for it.
"name": "AvfRkpdAppIntegrationTests"
+ },
+ {
+ "name": "AvfRkpdVmAttestationTestApp"
}
],
"postsubmit": [
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index 01ab7c9..72cb5e1 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -20,6 +20,7 @@
"libnum_traits",
"librustutils",
"libvmclient",
+ "libplatformproperties_rust",
],
proc_macros: ["libnum_derive"],
apex_available: [
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 077a0ef..ffdd0ea 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -35,6 +35,7 @@
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
use glob::glob;
use log::{info, warn};
+use platformproperties::hypervisorproperties;
use rustutils::system_properties;
use std::fs::File;
use std::path::{Path, PathBuf};
@@ -232,7 +233,7 @@
fn want_protected_vm() -> Result<bool> {
let have_protected_vm =
- system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
+ hypervisorproperties::hypervisor_protected_vm_supported()?.unwrap_or(false);
if have_protected_vm {
info!("Starting protected VM");
return Ok(true);
@@ -243,8 +244,7 @@
bail!("Protected VM not supported, unable to start VM");
}
- let have_non_protected_vm =
- system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
+ let have_non_protected_vm = hypervisorproperties::hypervisor_vm_supported()?.unwrap_or(false);
if have_non_protected_vm {
warn!("Protected VM not supported, falling back to non-protected on debuggable build");
return Ok(false);
diff --git a/libs/hypervisor_props/Android.bp b/libs/hypervisor_props/Android.bp
index af08b01..af6d417 100644
--- a/libs/hypervisor_props/Android.bp
+++ b/libs/hypervisor_props/Android.bp
@@ -9,7 +9,7 @@
edition: "2021",
rustlibs: [
"libanyhow",
- "librustutils",
+ "libplatformproperties_rust",
],
apex_available: [
"com.android.compos",
diff --git a/libs/hypervisor_props/src/lib.rs b/libs/hypervisor_props/src/lib.rs
index 120a48c..14614fd 100644
--- a/libs/hypervisor_props/src/lib.rs
+++ b/libs/hypervisor_props/src/lib.rs
@@ -14,18 +14,17 @@
//! Access to hypervisor capabilities via system properties set by the bootloader.
-use anyhow::{Error, Result};
-use rustutils::system_properties;
+use anyhow::Result;
+use platformproperties::hypervisorproperties;
/// Returns whether there is a hypervisor present that supports non-protected VMs.
pub fn is_vm_supported() -> Result<bool> {
- system_properties::read_bool("ro.boot.hypervisor.vm.supported", false).map_err(Error::new)
+ Ok(hypervisorproperties::hypervisor_vm_supported()?.unwrap_or(false))
}
/// Returns whether there is a hypervisor present that supports protected VMs.
pub fn is_protected_vm_supported() -> Result<bool> {
- system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)
- .map_err(Error::new)
+ Ok(hypervisorproperties::hypervisor_protected_vm_supported()?.unwrap_or(false))
}
/// Returns whether there is a hypervisor present that supports any sort of VM, either protected
@@ -36,5 +35,5 @@
/// Returns the version of the hypervisor, if there is one.
pub fn version() -> Result<Option<String>> {
- system_properties::read("ro.boot.hypervisor.version").map_err(Error::new)
+ Ok(hypervisorproperties::hypervisor_version()?)
}
diff --git a/microdroid/init_debug_policy/src/init_debug_policy.rs b/microdroid/init_debug_policy/src/init_debug_policy.rs
index 90d04ac..c443088 100644
--- a/microdroid/init_debug_policy/src/init_debug_policy.rs
+++ b/microdroid/init_debug_policy/src/init_debug_policy.rs
@@ -15,7 +15,7 @@
//! Applies debug policies when booting microdroid
use rustutils::system_properties;
-use rustutils::system_properties::PropertyWatcherError;
+use rustutils::system_properties::error::PropertyWatcherError;
use std::fs::File;
use std::io::Read;
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
index 8f5fb41..de731f6 100644
--- a/service_vm/test_apk/Android.bp
+++ b/service_vm/test_apk/Android.bp
@@ -2,12 +2,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "VmAttestationTestApp",
+java_defaults {
+ name: "vm_attestation_testapp_defaults",
test_suites: [
"general-tests",
],
- srcs: ["src/java/**/*.java"],
static_libs: [
"MicrodroidDeviceTestHelper",
"androidx.test.runner",
@@ -19,7 +18,12 @@
jni_uses_platform_apis: true,
use_embedded_native_libs: true,
sdk_version: "test_current",
- compile_multilib: "first",
+}
+
+android_test {
+ name: "VmAttestationTestApp",
+ srcs: ["src/java/com/android/virt/vm_attestation/testapp/*.java"],
+ defaults: ["vm_attestation_testapp_defaults"],
}
rust_defaults {
@@ -41,4 +45,21 @@
rust_ffi {
name: "libvm_attestation_test_payload",
defaults: ["vm_attestation_test_payload_defaults"],
+ visibility: [":__subpackages__"],
+}
+
+android_test {
+ name: "AvfRkpdVmAttestationTestApp",
+ srcs: ["src/java/com/android/virt/rkpd/vm_attestation/testapp/*.java"],
+ defaults: ["vm_attestation_testapp_defaults"],
+ manifest: "AndroidManifest.rkpd.xml",
+ test_config: "AndroidTest.rkpd.xml",
+ static_libs: [
+ "RkpdAppTestUtil",
+ "androidx.work_work-testing",
+ ],
+ instrumentation_for: "rkpdapp",
+ // This app is a variation of rkpdapp, with additional permissions to run
+ // a VM. It is defined in packages/modules/RemoteKeyProvisioning.
+ data: [":avf-rkpdapp"],
}
diff --git a/service_vm/test_apk/AndroidManifest.rkpd.xml b/service_vm/test_apk/AndroidManifest.rkpd.xml
new file mode 100644
index 0000000..6ecc5a9
--- /dev/null
+++ b/service_vm/test_apk/AndroidManifest.rkpd.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.virt.rkpd.vm_attestation.testapp">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.rkpdapp"
+ android:label="AVF rkpd app integration tests" />
+</manifest>
diff --git a/service_vm/test_apk/AndroidTest.rkpd.xml b/service_vm/test_apk/AndroidTest.rkpd.xml
new file mode 100644
index 0000000..39eca32
--- /dev/null
+++ b/service_vm/test_apk/AndroidTest.rkpd.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="VM attestation integration tests with the rkpd app.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <!-- Need to disable SELinux policy to allow com.android.rkpdapp to run a VM. -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="AvfRkpdVmAttestationTestApp.apk" />
+ <option name="test-file-name" value="avf-rkpdapp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.virt.rkpd.vm_attestation.testapp" />
+ </test>
+
+ <!-- Only run if RKPD mainline module is installed -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="enable" value="true" />
+ <option name="mainline-module-package-name" value="com.android.rkpd" />
+ </object>
+</configuration>
diff --git a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
index 94a7b8d..34c8549 100644
--- a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
+++ b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
@@ -21,7 +21,31 @@
const int PORT = 5679;
/**
- * Requests attestation for testing.
+ * The result of signing a message with the attested key.
+ */
+ parcelable SigningResult {
+ /** The DER-encoded ECDSA signature of the message. */
+ byte[] signature;
+
+ /** The DER-encoded attestation X509 certificate chain. */
+ byte[] certificateChain;
+ }
+
+ /**
+ * Requests attestation with {@link AVmPayload_requestAttestation} API and signs the
+ * given message with the attested key.
+ *
+ * The remotely provisioned keys are retrieved from RKPD and are provisioned from the
+ * real RKP server.
+ *
+ * @param challenge the challenge to include in the attestation output.
+ * @param message the message to sign.
+ * @return the result of signing the message with the attested key.
+ */
+ SigningResult signWithAttestationKey(in byte[] challenge, in byte[] message);
+
+ /**
+ * Requests attestation for testing with {@link AVmPayload_requestAttestationForTesting} API.
*
* A fake key pair should be provisioned with the call to
* {@link VirtualMachine#enableTestAttestation()} before calling this method.
diff --git a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
new file mode 100644
index 0000000..e7061e1
--- /dev/null
+++ b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.rkpd.vm_attestation.testapp;
+
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import android.content.Context;
+import android.hardware.security.keymint.IRemotelyProvisionedComponent;
+import android.os.SystemProperties;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+
+import androidx.work.ListenableWorker;
+import androidx.work.testing.TestWorkerBuilder;
+
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.rkpdapp.database.ProvisionedKeyDao;
+import com.android.rkpdapp.database.RkpdDatabase;
+import com.android.rkpdapp.interfaces.ServerInterface;
+import com.android.rkpdapp.interfaces.ServiceManagerInterface;
+import com.android.rkpdapp.interfaces.SystemInterface;
+import com.android.rkpdapp.provisioner.PeriodicProvisioner;
+import com.android.rkpdapp.testutil.SystemInterfaceSelector;
+import com.android.rkpdapp.utils.Settings;
+import com.android.rkpdapp.utils.X509Utils;
+import com.android.virt.vm_attestation.testservice.IAttestationService;
+import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+
+/**
+ * End-to-end test for the pVM remote attestation.
+ *
+ * <p>The test checks the two major steps of the pVM remote attestation:
+ *
+ * <p>1. Key provisioning: The test provisions AVF keys from the RKP server and verifies that the
+ * keys are for AVF.
+ *
+ * <p>2. VM attestation: The test creates a VM with a payload binary that requests to attest the VM,
+ * and then signs a message with the attestation key.
+ *
+ * <p>To run this test, you need to:
+ *
+ * <p>- Have an arm64 device supporting protected VMs.
+ *
+ * <p>- Have a stable network connection on the device.
+ *
+ * <p>- Have the RKP server hostname configured in the device. If not, you can set it using: $ adb
+ * shell setprop remote_provisioning.hostname remoteprovisioning.googleapis.com
+ */
+@RunWith(Parameterized.class)
+public class RkpdVmAttestationTest extends MicrodroidDeviceTestBase {
+ private static final String TAG = "RkpdVmAttestationTest";
+ private static final String AVF_ATTESTATION_EXTENSION_OID = "1.3.6.1.4.1.11129.2.1.29.1";
+ private static final String SERVICE_NAME = IRemotelyProvisionedComponent.DESCRIPTOR + "/avf";
+ private static final String VM_PAYLOAD_PATH = "libvm_attestation_test_payload.so";
+ private static final String MESSAGE = "Hello RKP from AVF!";
+ private static final String TEST_APP_PACKAGE_NAME =
+ "com.android.virt.rkpd.vm_attestation.testapp";
+
+ private ProvisionedKeyDao mKeyDao;
+ private PeriodicProvisioner mProvisioner;
+
+ @Parameterized.Parameter(0)
+ public String mGki;
+
+ @Parameterized.Parameters(name = "gki={0}")
+ public static Collection<Object[]> params() {
+ List<Object[]> ret = new ArrayList<>();
+ ret.add(new Object[] {null /* use microdroid kernel */});
+ for (String gki : SUPPORTED_GKI_VERSIONS) {
+ ret.add(new Object[] {gki});
+ }
+ return ret;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assume().withMessage("The RKP server hostname is not configured -- assume RKP disabled.")
+ .that(SystemProperties.get("remote_provisioning.hostname"))
+ .isNotEmpty();
+ assume().withMessage("RKP Integration tests rely on network availability.")
+ .that(ServerInterface.isNetworkConnected(getContext()))
+ .isTrue();
+ // TODO(b/329652894): Assume that pVM remote attestation feature is supported.
+
+ prepareTestSetup(true /* protectedVm */, mGki);
+
+ Settings.clearPreferences(getContext());
+ mKeyDao = RkpdDatabase.getDatabase(getContext()).provisionedKeyDao();
+ mKeyDao.deleteAllKeys();
+
+ mProvisioner =
+ TestWorkerBuilder.from(
+ getContext(),
+ PeriodicProvisioner.class,
+ Executors.newSingleThreadExecutor())
+ .build();
+
+ SystemInterface systemInterface =
+ SystemInterfaceSelector.getSystemInterfaceForServiceName(SERVICE_NAME);
+ ServiceManagerInterface.setInstances(new SystemInterface[] {systemInterface});
+
+ setMaxPerformanceTaskProfile();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ServiceManagerInterface.setInstances(null);
+ if (mKeyDao != null) {
+ mKeyDao.deleteAllKeys();
+ }
+ Settings.clearPreferences(getContext());
+ }
+
+ @Test
+ public void usingProvisionedKeyForVmAttestationSucceeds() throws Exception {
+ // Provision keys.
+ assertThat(mProvisioner.doWork()).isEqualTo(ListenableWorker.Result.success());
+ assertThat(mKeyDao.getTotalUnassignedKeysForIrpc(SERVICE_NAME)).isGreaterThan(0);
+
+ // Arrange.
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(TEST_APP_PACKAGE_NAME, 0);
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setProtectedVm(true)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setPayloadBinaryName(VM_PAYLOAD_PATH)
+ .setVmOutputCaptured(true)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("attestation_with_rkpd_client", config);
+ byte[] challenge = new byte[32];
+ Arrays.fill(challenge, (byte) 0xab);
+
+ // Act.
+ CompletableFuture<Exception> exception = new CompletableFuture<>();
+ CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+ CompletableFuture<SigningResult> signingResultFuture = new CompletableFuture<>();
+ VmEventListener listener =
+ new VmEventListener() {
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ payloadReady.complete(true);
+ try {
+ IAttestationService service =
+ IAttestationService.Stub.asInterface(
+ vm.connectToVsockServer(IAttestationService.PORT));
+ signingResultFuture.complete(
+ service.signWithAttestationKey(challenge, MESSAGE.getBytes()));
+ } catch (Exception e) {
+ exception.complete(e);
+ } finally {
+ forceStop(vm);
+ }
+ }
+ };
+ listener.runToFinish(TAG, vm);
+
+ // Assert.
+ assertThat(payloadReady.getNow(false)).isTrue();
+ assertThat(exception.getNow(null)).isNull();
+ SigningResult signingResult = signingResultFuture.getNow(null);
+ assertThat(signingResult).isNotNull();
+
+ // Parsing the certificate chain successfully indicates that the certificate
+ // chain is valid, that each certificate is signed by the next one and the last
+ // one is self-signed.
+ X509Certificate[] certs = X509Utils.formatX509Certs(signingResult.certificateChain);
+ assertThat(certs.length).isGreaterThan(2);
+ assertWithMessage("The first certificate should be generated in the RKP VM")
+ .that(certs[0].getSubjectX500Principal().getName())
+ .isEqualTo("CN=Android Protected Virtual Machine Key");
+ checkAvfAttestationExtension(certs[0], challenge);
+ assertWithMessage("The second certificate should contain AVF in the subject")
+ .that(certs[1].getSubjectX500Principal().getName())
+ .contains("O=AVF");
+
+ // Verify the signature using the public key from the leaf certificate generated
+ // in the RKP VM.
+ Signature sig = Signature.getInstance("SHA256withECDSA");
+ sig.initVerify(certs[0].getPublicKey());
+ sig.update(MESSAGE.getBytes());
+ assertThat(sig.verify(signingResult.signature)).isTrue();
+ }
+
+ private void checkAvfAttestationExtension(X509Certificate cert, byte[] challenge) {
+ byte[] extension = cert.getExtensionValue(AVF_ATTESTATION_EXTENSION_OID);
+ assertThat(extension).isNotNull();
+ // TODO(b/325610326): Use bouncycastle to parse the extension and check other fields.
+ assertWithMessage("The extension should contain the challenge")
+ .that(containsSubarray(extension, challenge))
+ .isTrue();
+ }
+
+ private boolean containsSubarray(byte[] array, byte[] subarray) {
+ for (int i = 0; i < array.length - subarray.length + 1; i++) {
+ boolean found = true;
+ for (int j = 0; j < subarray.length; j++) {
+ if (array[i + j] != subarray[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
index 199b45c..a04fb1f 100644
--- a/service_vm/test_apk/src/native/main.rs
+++ b/service_vm/test_apk/src/native/main.rs
@@ -18,7 +18,7 @@
use avflog::LogResult;
use com_android_virt_vm_attestation_testservice::{
aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
- BnAttestationService, IAttestationService, PORT,
+ BnAttestationService, IAttestationService, SigningResult::SigningResult, PORT,
},
binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
};
@@ -34,7 +34,7 @@
AIBinder, AVmAttestationResult, AVmAttestationResult_free,
AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
AVmAttestationResult_getPrivateKey, AVmAttestationResult_sign, AVmAttestationStatus,
- AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady,
+ AVmAttestationStatus_toString, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
};
@@ -89,13 +89,8 @@
impl IAttestationService for AttestationService {
fn requestAttestationForTesting(&self) -> binder::Result<()> {
- // The data below is only a placeholder generated randomly with urandom
- let challenge = &[
- 0x6c, 0xad, 0x52, 0x50, 0x15, 0xe7, 0xf4, 0x1d, 0xa5, 0x60, 0x7e, 0xd2, 0x7d, 0xf1,
- 0x51, 0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d,
- 0xc8, 0x07, 0x11, 0x7b,
- ];
- let res = AttestationResult::request_attestation(challenge)
+ const CHALLENGE: &[u8] = &[0xaa; 32];
+ let res = AttestationResult::request_attestation_for_testing(CHALLENGE)
.map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
.with_log()
.or_service_specific_exception(-1)?;
@@ -103,6 +98,21 @@
Ok(())
}
+ fn signWithAttestationKey(
+ &self,
+ challenge: &[u8],
+ message: &[u8],
+ ) -> binder::Result<SigningResult> {
+ let res = AttestationResult::request_attestation(challenge)
+ .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ let certificate_chain =
+ res.certificate_chain().with_log().or_service_specific_exception(-1)?;
+ let signature = res.sign(message).with_log().or_service_specific_exception(-1)?;
+ Ok(SigningResult { certificateChain: certificate_chain, signature })
+ }
+
fn validateAttestationResult(&self) -> binder::Result<()> {
// TODO(b/191073073): Returns the attestation result to the host for validation.
self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
@@ -116,7 +126,9 @@
unsafe impl Send for AttestationResult {}
impl AttestationResult {
- fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
+ fn request_attestation_for_testing(
+ challenge: &[u8],
+ ) -> result::Result<Self, AVmAttestationStatus> {
let mut res: *mut AVmAttestationResult = ptr::null_mut();
// SAFETY: It is safe as we only read the challenge within its bounds and the
// function does not retain any reference to it.
@@ -136,11 +148,31 @@
}
}
- fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
+ fn request_attestation(challenge: &[u8]) -> result::Result<Self, AVmAttestationStatus> {
+ let mut res: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: It is safe as we only read the challenge within its bounds and the
+ // function does not retain any reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestation(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut res,
+ )
+ };
+ if status == AVmAttestationStatus::ATTESTATION_OK {
+ info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
+ let res = NonNull::new(res).expect("The attestation result is null");
+ Ok(Self(res))
+ } else {
+ Err(status)
+ }
+ }
+
+ fn certificate_chain(&self) -> Result<Vec<u8>> {
let num_certs = get_certificate_count(self.as_ref());
- let mut certs = Vec::with_capacity(num_certs);
+ let mut certs = Vec::new();
for i in 0..num_certs {
- certs.push(get_certificate_at(self.as_ref(), i)?);
+ certs.extend(get_certificate_at(self.as_ref(), i)?.iter());
}
Ok(certs)
}
@@ -149,7 +181,7 @@
get_private_key(self.as_ref())
}
- fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
+ fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
sign_with_attested_key(self.as_ref(), message)
}
@@ -231,7 +263,7 @@
Ok(private_key.into_boxed_slice())
}
-fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
+fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Vec<u8>> {
// SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
// before getting freed.
let size = unsafe {
@@ -258,7 +290,7 @@
};
ensure!(size <= signature.len());
signature.truncate(size);
- Ok(signature.into_boxed_slice())
+ Ok(signature)
}
fn status_to_cstr(status: AVmAttestationStatus) -> &'static CStr {
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 2a04103..732be94 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -17,6 +17,7 @@
name: "MicrodroidTestAppsDefaults",
test_suites: [
"cts",
+ "vts",
"general-tests",
],
static_libs: [
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 8a4c367..22cd0dc 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Runs Microdroid device-side tests.">
<option name="test-suite-tag" value="cts" />
+ <option name="test-suite-tag" value="vts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />