blob: 0f593508767d7bae19ddb0516d0c08d9d9d0f2b2 [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 ciborium::value::Value;
use client_vm_csr::generate_attestation_key_and_csr;
use log::info;
use service_vm_comm::{
ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
RequestProcessingError, Response, VmType,
};
use service_vm_manager::ServiceVm;
use std::fs::File;
use std::io;
use std::panic;
use std::path::PathBuf;
use vmclient::VmInstance;
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";
#[test]
fn process_requests_in_protected_vm() -> Result<()> {
check_processing_requests(VmType::ProtectedVm)
}
#[test]
fn process_requests_in_non_protected_vm() -> Result<()> {
check_processing_requests(VmType::NonProtectedVm)
}
fn check_processing_requests(vm_type: VmType) -> Result<()> {
let mut vm = start_service_vm(vm_type)?;
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.key_blob)?;
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, key_blob: &[u8]) -> 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 = diced_sample_inputs::make_sample_bcc_and_cdis()?;
let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
let params = ClientVmAttestationParams {
csr: attestation_data.csr.into_cbor_vec()?,
remotely_provisioned_key_blob: key_blob.to_vec(),
};
let request = Request::RequestClientVmAttestation(params);
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
// TODO(b/309441500): Check the certificate once it is implemented.
Response::Err(RequestProcessingError::OperationUnimplemented) => Ok(()),
_ => bail!("Incorrect response type: {response:?}"),
}
}
/// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
fn check_csr(csr: Vec<u8>) -> Result<()> {
let mut reader = io::Cursor::new(csr);
let csr: Value = ciborium::from_reader(&mut reader)?;
match csr {
Value::Array(arr) => {
assert_eq!(4, arr.len());
}
_ => bail!("Incorrect CSR format: {csr:?}"),
}
Ok(())
}
fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
android_logger::init_once(
android_logger::Config::default().with_tag("rialto").with_min_level(log::Level::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_type)
}
fn vm_instance(vm_type: VmType) -> Result<VmInstance> {
match vm_type {
VmType::ProtectedVm => {
service_vm_manager::protected_vm_instance(PathBuf::from(INSTANCE_IMG_PATH))
}
VmType::NonProtectedVm => nonprotected_vm_instance(),
}
}
fn nonprotected_vm_instance() -> Result<VmInstance> {
let rialto = File::open(UNSIGNED_RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
name: String::from("Non protected rialto"),
bootloader: Some(ParcelFileDescriptor::new(rialto)),
protectedVm: false,
memoryMib: 300,
platformVersion: "~1.0".to_string(),
..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")
}