[test] Add e2e instrumentation test for VM attestation
This cl adds an e2e test to check the VM attestation with the
following steps:
- Provisioning a pair of mock keys in virtualizationservice.
- Requesting VM attestation from a running VM and tracing this
step.
- Validating the attestation result.
To facilitate the testing process, two new APIs have been added.
The first API allows for the provisioning of mock keys, while the
second API facilitates the request for attestation in testing mode.
The new test has been added to busytown config at cl/605577401.
Bug: 318333789
Test: atest VmAttestationTestApp
Change-Id: Icf9d39c704af316f7625cf9d057d255243eca445
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 92a5812..7962bc3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -24,6 +24,7 @@
interface IVirtualizationService {
const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+ const String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
/**
@@ -73,4 +74,10 @@
/** Returns whether given feature is enabled. */
boolean isFeatureEnabled(in String feature);
+
+ /**
+ * Provisions a key pair for the VM attestation testing, a fake certificate will be
+ * associated to the fake key pair when the VM requests attestation in testing mode.
+ */
+ void enableTestAttestation();
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index dd94526..abfc45a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -61,10 +61,20 @@
* attested is owned by this app.
* The uniqueness of the UID ensures that no two VMs owned by different apps
* are able to correlate keys.
+ * @param testMode Whether the request is for testing purposes.
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- Certificate[] requestAttestation(in byte[] csr, int requesterUid);
+ Certificate[] requestAttestation(in byte[] csr, int requesterUid, in boolean testMode);
+
+ /**
+ * Provisions a key pair for the VM attestation testing, a fake certificate will be
+ * associated to the fake key pair when the VM requests attestation in testing mode.
+ *
+ * The provisioned key pair will be used in the subsequent call to {@link #requestAttestation}
+ * with testMode set to true.
+ */
+ void enableTestAttestation();
/**
* Get a list of assignable devices.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index cf91302..6806a5c 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -51,10 +51,11 @@
* Requests a certificate chain for the provided certificate signing request (CSR).
*
* @param csr The certificate signing request.
+ * @param testMode Whether the request is for test purposes.
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- Certificate[] requestAttestation(in byte[] csr);
+ Certificate[] requestAttestation(in byte[] csr, in boolean testMode);
/**
* Request connection to Secretkeeper. This is used by pVM to store Anti-Rollback protected
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index a1a1fb9..d0c5d4a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,7 +16,8 @@
use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
-use crate::rkpvm::request_attestation;
+use crate::rkpvm::{request_attestation, generate_ecdsa_p256_key_pair};
+use crate::remote_provisioning;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
use android_system_virtualizationservice::{
@@ -38,6 +39,7 @@
use anyhow::{anyhow, ensure, Context, Result};
use avflog::LogResult;
use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
+use service_vm_comm::Response;
use lazy_static::lazy_static;
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
@@ -73,7 +75,73 @@
const CHUNK_RECV_MAX_LEN: usize = 1024;
+/// The fake certificate is used for testing only when a client VM requests attestation in test
+/// mode, it is a single certificate extracted on an unregistered device for testing.
+/// Here is the snapshot of the certificate:
+///
+/// ```
+/// Certificate:
+/// Data:
+/// Version: 3 (0x2)
+/// Serial Number:
+/// 59:ae:50:98:95:e1:34:25:f1:21:93:c0:4c:e5:24:66
+/// Signature Algorithm: ecdsa-with-SHA256
+/// Issuer: CN = Droid Unregistered Device CA, O = Google Test LLC
+/// Validity
+/// Not Before: Feb 5 14:39:39 2024 GMT
+/// Not After : Feb 14 14:39:39 2024 GMT
+/// Subject: CN = 59ae509895e13425f12193c04ce52466, O = TEE
+/// Subject Public Key Info:
+/// Public Key Algorithm: id-ecPublicKey
+/// Public-Key: (256 bit)
+/// pub:
+/// 04:30:32:cd:95:12:b0:71:8b:b7:14:44:26:58:d5:
+/// 82:8c:25:55:2c:6d:ef:98:e3:4f:88:d0:74:82:09:
+/// 3e:8d:6c:f0:f2:18:d5:83:0e:0d:f2:ce:c5:15:38:
+/// e5:6a:e6:4d:4d:95:15:b7:24:e7:cb:4b:63:42:21:
+/// bc:36:c6:0a:d8
+/// ASN1 OID: prime256v1
+/// NIST CURVE: P-256
+/// X509v3 extensions:
+/// ...
+/// ```
+const FAKE_CERTIFICATE_FOR_TESTING: &[u8] = &[
+ 0x30, 0x82, 0x01, 0xee, 0x30, 0x82, 0x01, 0x94, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x59,
+ 0xae, 0x50, 0x98, 0x95, 0xe1, 0x34, 0x25, 0xf1, 0x21, 0x93, 0xc0, 0x4c, 0xe5, 0x24, 0x66, 0x30,
+ 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x41, 0x31, 0x25, 0x30,
+ 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x55, 0x6e,
+ 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63,
+ 0x65, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4c, 0x4c, 0x43, 0x30, 0x1e,
+ 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x30, 0x35, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x17,
+ 0x0d, 0x32, 0x34, 0x30, 0x32, 0x31, 0x34, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x30, 0x39,
+ 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x35, 0x39, 0x61, 0x65, 0x35,
+ 0x30, 0x39, 0x38, 0x39, 0x35, 0x65, 0x31, 0x33, 0x34, 0x32, 0x35, 0x66, 0x31, 0x32, 0x31, 0x39,
+ 0x33, 0x63, 0x30, 0x34, 0x63, 0x65, 0x35, 0x32, 0x34, 0x36, 0x36, 0x31, 0x0c, 0x30, 0x0a, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x54, 0x45, 0x45, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
+ 0x03, 0x42, 0x00, 0x04, 0x30, 0x32, 0xcd, 0x95, 0x12, 0xb0, 0x71, 0x8b, 0xb7, 0x14, 0x44, 0x26,
+ 0x58, 0xd5, 0x82, 0x8c, 0x25, 0x55, 0x2c, 0x6d, 0xef, 0x98, 0xe3, 0x4f, 0x88, 0xd0, 0x74, 0x82,
+ 0x09, 0x3e, 0x8d, 0x6c, 0xf0, 0xf2, 0x18, 0xd5, 0x83, 0x0e, 0x0d, 0xf2, 0xce, 0xc5, 0x15, 0x38,
+ 0xe5, 0x6a, 0xe6, 0x4d, 0x4d, 0x95, 0x15, 0xb7, 0x24, 0xe7, 0xcb, 0x4b, 0x63, 0x42, 0x21, 0xbc,
+ 0x36, 0xc6, 0x0a, 0xd8, 0xa3, 0x76, 0x30, 0x74, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x39, 0x81, 0x41, 0x0a, 0xb9, 0xf3, 0xf4, 0x5b, 0x75, 0x97, 0x4a, 0x46, 0xd6,
+ 0x30, 0x9e, 0x1d, 0x7a, 0x3b, 0xec, 0xa8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x82, 0xbd, 0x00, 0xde, 0xcb, 0xc5, 0xe7, 0x72, 0x87, 0x3d, 0x1c, 0x0a,
+ 0x1e, 0x78, 0x4f, 0xf5, 0xd3, 0xc1, 0x3e, 0xb8, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x1e, 0x04, 0x03, 0xa1, 0x01, 0x08, 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00,
+ 0xae, 0xd8, 0x40, 0x9e, 0x37, 0x3e, 0x5c, 0x9c, 0xe2, 0x93, 0x3d, 0x8c, 0xf7, 0x05, 0x10, 0xe7,
+ 0xd1, 0x2b, 0x87, 0x8a, 0xee, 0xd6, 0x1e, 0x6c, 0x3b, 0xd2, 0x91, 0x3e, 0xa5, 0xdf, 0x91, 0x20,
+ 0x02, 0x20, 0x7f, 0x0f, 0x29, 0x54, 0x60, 0x80, 0x07, 0x50, 0x5f, 0x56, 0x6b, 0x9f, 0xe0, 0x94,
+ 0xb4, 0x3f, 0x3b, 0x0f, 0x61, 0xa0, 0x33, 0x40, 0xe6, 0x1a, 0x42, 0xda, 0x4b, 0xa4, 0xfd, 0x92,
+ 0xb9, 0x0f,
+];
+
lazy_static! {
+ static ref FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
static ref VFIO_SERVICE: Strong<dyn IVfioHandler> =
wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
.expect("Could not connect to VfioHandler");
@@ -169,10 +237,41 @@
Ok(cids)
}
+ fn enableTestAttestation(&self) -> binder::Result<()> {
+ check_manage_access()?;
+ check_use_custom_virtual_machine()?;
+ if !cfg!(remote_attestation) {
+ return Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some(
+ "enableTestAttestation is not supported with the remote_attestation \
+ feature disabled",
+ ),
+ ))
+ .with_log();
+ }
+ let res = generate_ecdsa_p256_key_pair()
+ .context("Failed to generate ECDSA P-256 key pair for testing")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ match res {
+ Response::GenerateEcdsaP256KeyPair(key_pair) => {
+ FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+ .lock()
+ .unwrap()
+ .replace(key_pair.key_blob.to_vec());
+ Ok(())
+ }
+ _ => Err(remote_provisioning::to_service_specific_error(res)),
+ }
+ .with_log()
+ }
+
fn requestAttestation(
&self,
csr: &[u8],
requester_uid: i32,
+ test_mode: bool,
) -> binder::Result<Vec<Certificate>> {
check_manage_access()?;
if !cfg!(remote_attestation) {
@@ -186,14 +285,31 @@
.with_log();
}
info!("Received csr. Requestting attestation...");
- let attestation_key = get_rkpd_attestation_key(
- REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
- requester_uid as u32,
- )
- .context("Failed to retrieve the remotely provisioned keys")
- .with_log()
- .or_service_specific_exception(-1)?;
- let mut certificate_chain = split_x509_certificate_chain(&attestation_key.encodedCertChain)
+ let (key_blob, certificate_chain) = if test_mode {
+ check_use_custom_virtual_machine()?;
+ info!("Using the fake key blob for testing...");
+ (
+ FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+ .lock()
+ .unwrap()
+ .clone()
+ .ok_or_else(|| anyhow!("No key blob for testing"))
+ .with_log()
+ .or_service_specific_exception(-1)?,
+ FAKE_CERTIFICATE_FOR_TESTING.to_vec(),
+ )
+ } else {
+ info!("Retrieving the remotely provisioned keys from RKPD...");
+ let attestation_key = get_rkpd_attestation_key(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ requester_uid as u32,
+ )
+ .context("Failed to retrieve the remotely provisioned keys")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ (attestation_key.keyBlob, attestation_key.encodedCertChain)
+ };
+ let mut certificate_chain = split_x509_certificate_chain(&certificate_chain)
.context("Failed to split the remotely provisioned certificate chain")
.with_log()
.or_service_specific_exception(-1)?;
@@ -206,7 +322,7 @@
}
let certificate = request_attestation(
csr.to_vec(),
- attestation_key.keyBlob,
+ key_blob,
certificate_chain[0].encodedCertificate.clone(),
)
.context("Failed to request attestation")
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 40f54db..c2c04df 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -145,7 +145,7 @@
}
}
-fn to_service_specific_error(response: Response) -> Status {
+pub(crate) fn to_service_specific_error(response: Response) -> Status {
match response {
Response::Err(e) => match e {
RequestProcessingError::InvalidMac => {