blob: 582b69e34b9723c3fbffe7f1e85647a6368a4b47 [file] [log] [blame]
// Copyright 2022, 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.
//! Integration test for Rialto.
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, ProcessState},
};
use anyhow::{bail, Context, Result};
use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
use client_vm_csr::generate_attestation_key_and_csr;
use coset::{CborSerializable, CoseMac0, CoseSign};
use hwtrust::{rkp, session::Session};
use log::{info, warn};
use service_vm_comm::{
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
Request, RequestProcessingError, Response, VmType,
};
use service_vm_fake_chain::client_vm::{
fake_client_vm_dice_artifacts, fake_sub_components, SubComponent,
};
use service_vm_manager::{ServiceVm, VM_MEMORY_MB};
use std::fs;
use std::fs::File;
use std::panic;
use std::path::PathBuf;
use std::str::FromStr;
use vmclient::VmInstance;
use x509_cert::{
certificate::{Certificate, Version},
der::{self, asn1, Decode, Encode},
name::Name,
spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo},
};
const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
#[cfg(dice_changes)]
#[test]
fn process_requests_in_protected_vm() -> Result<()> {
if hypervisor_props::is_protected_vm_supported()? {
// The test is skipped if the feature flag |dice_changes| is not enabled, because when
// the flag is off, the DICE chain is truncated in the pvmfw, and the service VM cannot
// verify the chain due to the missing entries in the chain.
check_processing_requests(VmType::ProtectedVm, None)
} else {
warn!("pVMs are not supported on device, skipping test");
Ok(())
}
}
#[test]
fn process_requests_in_non_protected_vm() -> Result<()> {
const MEMORY_MB: i32 = 300;
check_processing_requests(VmType::NonProtectedVm, Some(MEMORY_MB))?;
check_processing_requests(VmType::NonProtectedVm, None)
}
fn check_processing_requests(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<()> {
let mut vm = start_service_vm(vm_type, vm_memory_mb)?;
check_processing_reverse_request(&mut vm)?;
let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
check_processing_generating_certificate_request(&mut vm, &key_pair.maced_public_key)?;
check_attestation_request(&mut vm, &key_pair, vm_type)?;
Ok(())
}
fn check_processing_reverse_request(vm: &mut ServiceVm) -> Result<()> {
let message = "abc".repeat(500);
let request = Request::Reverse(message.as_bytes().to_vec());
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
let expected_response: Vec<u8> = message.as_bytes().iter().rev().cloned().collect();
assert_eq!(Response::Reverse(expected_response), response);
Ok(())
}
fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<EcdsaP256KeyPair> {
let request = Request::GenerateEcdsaP256KeyPair;
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
Response::GenerateEcdsaP256KeyPair(key_pair) => {
assert_array_has_nonzero(&key_pair.maced_public_key);
assert_array_has_nonzero(&key_pair.key_blob);
Ok(key_pair)
}
_ => bail!("Incorrect response type: {response:?}"),
}
}
fn assert_array_has_nonzero(v: &[u8]) {
assert!(v.iter().any(|&x| x != 0))
}
fn check_processing_generating_certificate_request(
vm: &mut ServiceVm,
maced_public_key: &[u8],
) -> Result<()> {
let params = GenerateCertificateRequestParams {
keys_to_sign: vec![maced_public_key.to_vec()],
challenge: vec![],
};
let request = Request::GenerateCertificateRequest(params);
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
Response::GenerateCertificateRequest(csr) => check_csr(csr),
_ => bail!("Incorrect response type: {response:?}"),
}
}
fn check_attestation_request(
vm: &mut ServiceVm,
remotely_provisioned_key_pair: &EcdsaP256KeyPair,
vm_type: VmType,
) -> Result<()> {
/// The following data was generated randomly with urandom.
const CHALLENGE: [u8; 16] = [
0x7d, 0x86, 0x58, 0x79, 0x3a, 0x09, 0xdf, 0x1c, 0xa5, 0x80, 0x80, 0x15, 0x2b, 0x13, 0x17,
0x5c,
];
let dice_artifacts = fake_client_vm_dice_artifacts()?;
let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
let cert_chain = fs::read(TEST_CERT_CHAIN_PATH)?;
// The certificate chain contains several certificates, but we only need the first one.
// Parsing the data with trailing data always fails with a `TrailingData` error.
let cert_len: usize = match Certificate::from_der(&cert_chain).unwrap_err().kind() {
der::ErrorKind::TrailingData { decoded, .. } => decoded.try_into().unwrap(),
e => bail!("Unexpected error: {e}"),
};
// Builds the mock parameters for the client VM attestation.
// The `csr` and `remotely_provisioned_key_blob` parameters are extracted from the same
// libraries as in production.
// The `remotely_provisioned_cert` parameter is an RKP certificate extracted from a test
// certificate chain retrieved from RKPD.
let params = ClientVmAttestationParams {
csr: attestation_data.csr.clone().into_cbor_vec()?,
remotely_provisioned_key_blob: remotely_provisioned_key_pair.key_blob.to_vec(),
remotely_provisioned_cert: cert_chain[..cert_len].to_vec(),
};
let request = Request::RequestClientVmAttestation(params);
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
Response::RequestClientVmAttestation(certificate) => {
// The end-to-end test for non-protected VM attestation works because both the service
// VM and the client VM use the same fake DICE chain.
assert_eq!(vm_type, VmType::NonProtectedVm);
check_certificate_for_client_vm(
&certificate,
&remotely_provisioned_key_pair.maced_public_key,
&attestation_data.csr,
&Certificate::from_der(&cert_chain[..cert_len]).unwrap(),
)?;
Ok(())
}
Response::Err(RequestProcessingError::InvalidDiceChain) => {
// The end-to-end test for protected VM attestation doesn't work because the service VM
// compares the fake DICE chain in the CSR with the real DICE chain.
// We cannot generate a valid DICE chain with the same payloads up to pvmfw.
assert_eq!(vm_type, VmType::ProtectedVm);
Ok(())
}
_ => bail!("Incorrect response type: {response:?}"),
}
}
fn check_vm_components(vm_components: &asn1::SequenceOf<asn1::Any, 4>) -> Result<()> {
let expected_components = fake_sub_components();
assert_eq!(expected_components.len(), vm_components.len());
for (i, expected_component) in expected_components.iter().enumerate() {
check_vm_component(vm_components.get(i).unwrap(), expected_component)?;
}
Ok(())
}
fn check_vm_component(vm_component: &asn1::Any, expected_component: &SubComponent) -> Result<()> {
let vm_component = vm_component.decode_as::<asn1::SequenceOf<asn1::Any, 4>>().unwrap();
assert_eq!(4, vm_component.len());
let name = vm_component.get(0).unwrap().decode_as::<asn1::Utf8StringRef>().unwrap();
assert_eq!(expected_component.name, name.as_ref());
let version = vm_component.get(1).unwrap().decode_as::<u64>().unwrap();
assert_eq!(expected_component.version, version);
let code_hash = vm_component.get(2).unwrap().decode_as::<asn1::OctetString>().unwrap();
assert_eq!(expected_component.code_hash, code_hash.as_bytes());
let authority_hash = vm_component.get(3).unwrap().decode_as::<asn1::OctetString>().unwrap();
assert_eq!(expected_component.authority_hash, authority_hash.as_bytes());
Ok(())
}
fn check_certificate_for_client_vm(
certificate: &[u8],
maced_public_key: &[u8],
csr: &Csr,
parent_certificate: &Certificate,
) -> Result<()> {
let cose_mac = CoseMac0::from_slice(maced_public_key)?;
let authority_public_key =
EcKey::from_cose_public_key_slice(&cose_mac.payload.unwrap()).unwrap();
let cert = Certificate::from_der(certificate).unwrap();
// Checks the certificate signature against the authority public key.
const ECDSA_WITH_SHA_256: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
let expected_algorithm = AlgorithmIdentifier { oid: ECDSA_WITH_SHA_256, parameters: None };
assert_eq!(expected_algorithm, cert.signature_algorithm);
let tbs_cert = cert.tbs_certificate;
let digest = sha256(&tbs_cert.to_der().unwrap()).unwrap();
authority_public_key
.ecdsa_verify_der(cert.signature.raw_bytes(), &digest)
.expect("Failed to verify the certificate signature with the authority public key");
// Checks that the certificate's subject public key is equal to the key in the CSR.
let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
let csr_payload =
cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
let subject_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key).unwrap();
let expected_spki_data =
PKey::try_from(subject_public_key).unwrap().subject_public_key_info().unwrap();
let expected_spki = SubjectPublicKeyInfo::from_der(&expected_spki_data).unwrap();
assert_eq!(expected_spki, tbs_cert.subject_public_key_info);
// Checks the certificate extension.
const ATTESTATION_EXTENSION_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.29.1");
let extensions = tbs_cert.extensions.unwrap();
assert_eq!(1, extensions.len());
let extension = &extensions[0];
assert_eq!(ATTESTATION_EXTENSION_OID, extension.extn_id);
assert!(!extension.critical);
let attestation_ext =
asn1::SequenceOf::<asn1::Any, 3>::from_der(extension.extn_value.as_bytes()).unwrap();
assert_eq!(3, attestation_ext.len());
let challenge = attestation_ext.get(0).unwrap().decode_as::<asn1::OctetString>().unwrap();
assert_eq!(csr_payload.challenge, challenge.as_bytes());
let is_vm_secure = attestation_ext.get(1).unwrap().decode_as::<bool>().unwrap();
assert!(
!is_vm_secure,
"The VM shouldn't be secure as the last payload added in the test is in Debug mode"
);
let vm_components =
attestation_ext.get(2).unwrap().decode_as::<asn1::SequenceOf<asn1::Any, 4>>().unwrap();
check_vm_components(&vm_components)?;
// Checks other fields on the certificate
assert_eq!(Version::V3, tbs_cert.version);
assert_eq!(parent_certificate.tbs_certificate.validity, tbs_cert.validity);
assert_eq!(
Name::from_str("CN=Android Protected Virtual Machine Key").unwrap(),
tbs_cert.subject
);
assert_eq!(parent_certificate.tbs_certificate.subject, tbs_cert.issuer);
Ok(())
}
fn check_csr(csr: Vec<u8>) -> Result<()> {
let mut session = Session::default();
session.set_allow_any_mode(true);
let _csr = rkp::Csr::from_cbor(&session, &csr[..]).context("Failed to parse CSR")?;
Ok(())
}
fn start_service_vm(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<ServiceVm> {
android_logger::init_once(
android_logger::Config::default()
.with_tag("rialto")
.with_max_level(log::LevelFilter::Debug),
);
// Redirect panic messages to logcat.
panic::set_hook(Box::new(|panic_info| {
log::error!("{}", panic_info);
}));
// We need to start the thread pool for Binder to work properly, especially link_to_death.
ProcessState::start_thread_pool();
ServiceVm::start_vm(vm_instance(vm_type, vm_memory_mb)?, vm_type)
}
fn vm_instance(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<VmInstance> {
match vm_type {
VmType::ProtectedVm => {
assert!(vm_memory_mb.is_none());
service_vm_manager::protected_vm_instance(PathBuf::from(INSTANCE_IMG_PATH))
}
VmType::NonProtectedVm => nonprotected_vm_instance(vm_memory_mb.unwrap_or(VM_MEMORY_MB)),
}
}
fn nonprotected_vm_instance(memory_mib: i32) -> Result<VmInstance> {
let rialto = File::open(UNSIGNED_RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
// Do not use `#allocateInstanceId` to generate the instance ID because the method
// also adds an instance ID to the database it manages.
// This is not necessary for this test.
let mut instance_id = [0u8; 64];
rand_bytes(&mut instance_id).unwrap();
let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
name: format!("Non protected rialto ({memory_mib}MiB)"),
kernel: Some(ParcelFileDescriptor::new(rialto)),
protectedVm: false,
memoryMib: memory_mib,
platformVersion: "~1.0".to_string(),
instanceId: instance_id,
..Default::default()
});
let console = Some(service_vm_manager::android_log_fd()?);
let log = Some(service_vm_manager::android_log_fd()?);
let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
info!("Connected to VirtMgr for service VM");
VmInstance::create(service.as_ref(), &config, console, /* consoleIn */ None, log, None)
.context("Failed to create VM")
}