Merge "[dice] Move open-dice Rust wrapper libraries to virt" into main
diff --git a/compos/Android.bp b/compos/Android.bp
index 19123dd..b840506 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -14,6 +14,7 @@
"libanyhow",
"libbinder_rs",
"libcompos_common",
+ "libhex",
"liblibc",
"liblog_rust",
"libminijail_rust",
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index 76da00a..bfd886e 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -53,7 +53,7 @@
let file = File::open(path).with_context(|| format!("Opening {}", path.display()))?;
let digest = fsverity::measure(file.as_fd())?;
- let digest = to_hex_string(&digest);
+ let digest = hex::encode(digest);
self.file_digests.push((target_path.to_owned(), digest));
Ok(())
@@ -82,7 +82,3 @@
Ok(())
}
}
-
-fn to_hex_string(buf: &[u8]) -> String {
- buf.iter().map(|b| format!("{:02x}", b)).collect()
-}
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 27905c9..a576416 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -1,10 +1,10 @@
-// Copyright 2022, The Android Open Source Project
+// Copyright 2023 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
+// 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,
@@ -12,142 +12,44 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Logic for handling the DICE values and boot operations.
-
-use anyhow::{anyhow, bail, Context, Error, Result};
-use byteorder::{NativeEndian, ReadBytesExt};
+use crate::dice_driver::DiceDriver;
+use crate::{is_debuggable, MicrodroidData};
+use anyhow::{bail, Context, Result};
use ciborium::{cbor, ser};
-use diced_open_dice::{
- bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
- Hidden, InputValues, OwnedDiceArtifacts,
-};
-use keystore2_crypto::ZVec;
-use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use diced_open_dice::OwnedDiceArtifacts;
use microdroid_metadata::PayloadMetadata;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
-use std::fs;
-use std::os::unix::io::AsRawFd;
-use std::path::{Path, PathBuf};
-use std::ptr::null_mut;
-use std::slice;
+use openssl::sha::Sha512;
-/// Artifacts that are mapped into the process address space from the driver.
-pub enum DiceDriver<'a> {
- Real {
- driver_path: PathBuf,
- mmap_addr: *mut c_void,
- mmap_size: usize,
- bcc_handover: BccHandover<'a>,
- },
- Fake(OwnedDiceArtifacts),
-}
-
-impl DiceDriver<'_> {
- fn dice_artifacts(&self) -> &dyn DiceArtifacts {
- match self {
- Self::Real { bcc_handover, .. } => bcc_handover,
- Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
- }
+/// Perform an open DICE derivation for the payload.
+pub fn dice_derivation(
+ dice: DiceDriver,
+ verified_data: &MicrodroidData,
+ payload_metadata: &PayloadMetadata,
+) -> Result<OwnedDiceArtifacts> {
+ // Calculate compound digests of code and authorities
+ let mut code_hash_ctx = Sha512::new();
+ let mut authority_hash_ctx = Sha512::new();
+ code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
+ authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
+ for extra_apk in &verified_data.extra_apks_data {
+ code_hash_ctx.update(extra_apk.root_hash.as_ref());
+ authority_hash_ctx.update(extra_apk.pubkey.as_ref());
}
-
- pub fn new(driver_path: &Path) -> Result<Self> {
- if driver_path.exists() {
- log::info!("Using DICE values from driver");
- } else if super::is_strict_boot() {
- bail!("Strict boot requires DICE value from driver but none were found");
- } else {
- log::warn!("Using sample DICE values");
- let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
- .expect("Failed to create sample dice artifacts.");
- return Ok(Self::Fake(dice_artifacts));
- };
-
- let mut file = fs::File::open(driver_path)
- .map_err(|error| Error::new(error).context("Opening driver"))?;
- let mmap_size =
- file.read_u64::<NativeEndian>()
- .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
- // SAFETY: It's safe to map the driver as the service will only create a single
- // mapping per process.
- let mmap_addr = unsafe {
- let fd = file.as_raw_fd();
- mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
- };
- if mmap_addr == MAP_FAILED {
- bail!("Failed to mmap {:?}", driver_path);
- }
- let mmap_buf =
- // SAFETY: The slice is created for the region of memory that was just
- // successfully mapped into the process address space so it will be
- // accessible and not referenced from anywhere else.
- unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
- let bcc_handover =
- bcc_handover_parse(mmap_buf).map_err(|_| anyhow!("Failed to parse Bcc Handover"))?;
- Ok(Self::Real {
- driver_path: driver_path.to_path_buf(),
- mmap_addr,
- mmap_size,
- bcc_handover,
- })
+ for apex in &verified_data.apex_data {
+ code_hash_ctx.update(apex.root_digest.as_ref());
+ authority_hash_ctx.update(apex.public_key.as_ref());
}
+ let code_hash = code_hash_ctx.finish();
+ let authority_hash = authority_hash_ctx.finish();
- /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
- pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
- // Deterministically derive a key to use for sealing data, rather than using the CDI
- // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
- // input key material is already cryptographically strong.
- let mut key = ZVec::new(key_length)?;
- let salt = &[];
- hkdf(&mut key, Md::sha256(), self.dice_artifacts().cdi_seal(), salt, identifier)?;
- Ok(key)
- }
+ let config_descriptor = format_payload_config_descriptor(payload_metadata)?;
- pub fn derive(
- self,
- code_hash: Hash,
- config_desc: &[u8],
- authority_hash: Hash,
- debug: bool,
- hidden: Hidden,
- ) -> Result<OwnedDiceArtifacts> {
- let input_values = InputValues::new(
- code_hash,
- Config::Descriptor(config_desc),
- authority_hash,
- if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
- hidden,
- );
- let current_dice_artifacts = self.dice_artifacts();
- let next_dice_artifacts = retry_bcc_main_flow(
- current_dice_artifacts.cdi_attest(),
- current_dice_artifacts.cdi_seal(),
- current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
- &input_values,
- )
- .context("DICE derive from driver")?;
- if let Self::Real { driver_path, .. } = &self {
- // Writing to the device wipes the artifacts. The string is ignored by the driver but
- // included for documentation.
- fs::write(driver_path, "wipe")
- .map_err(|err| Error::new(err).context("Wiping driver"))?;
- }
- Ok(next_dice_artifacts)
- }
-}
+ // Check debuggability, conservatively assuming it is debuggable
+ let debuggable = is_debuggable()?;
-impl Drop for DiceDriver<'_> {
- fn drop(&mut self) {
- if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
- // SAFETY: All references to the mapped region have the same lifetime as self. Since
- // self is being dropped, so are all the references to the mapped region meaning it's
- // safe to unmap.
- let ret = unsafe { munmap(mmap_addr, mmap_size) };
- if ret != 0 {
- log::warn!("Failed to munmap ({})", ret);
- }
- }
- }
+ // Send the details to diced
+ let hidden = verified_data.salt.clone().try_into().unwrap();
+ dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
}
/// Returns a configuration descriptor of the given payload following the BCC's specification:
@@ -160,7 +62,7 @@
/// PayloadConfig = {
/// 1: tstr ; payload_binary_name
/// }
-pub fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
+fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
let config_descriptor_cbor_value = match payload {
diff --git a/microdroid_manager/src/dice_driver.rs b/microdroid_manager/src/dice_driver.rs
new file mode 100644
index 0000000..229f3e0
--- /dev/null
+++ b/microdroid_manager/src/dice_driver.rs
@@ -0,0 +1,149 @@
+// 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.
+
+//! Logic for handling the DICE values and boot operations.
+
+use anyhow::{anyhow, bail, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use diced_open_dice::{
+ bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
+ Hidden, InputValues, OwnedDiceArtifacts,
+};
+use keystore2_crypto::ZVec;
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use std::fs;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
+
+/// Artifacts that are mapped into the process address space from the driver.
+pub enum DiceDriver<'a> {
+ Real {
+ driver_path: PathBuf,
+ mmap_addr: *mut c_void,
+ mmap_size: usize,
+ bcc_handover: BccHandover<'a>,
+ },
+ Fake(OwnedDiceArtifacts),
+}
+
+impl DiceDriver<'_> {
+ fn dice_artifacts(&self) -> &dyn DiceArtifacts {
+ match self {
+ Self::Real { bcc_handover, .. } => bcc_handover,
+ Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+ }
+ }
+
+ pub fn new(driver_path: &Path) -> Result<Self> {
+ if driver_path.exists() {
+ log::info!("Using DICE values from driver");
+ } else if super::is_strict_boot() {
+ bail!("Strict boot requires DICE value from driver but none were found");
+ } else {
+ log::warn!("Using sample DICE values");
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
+ .expect("Failed to create sample dice artifacts.");
+ return Ok(Self::Fake(dice_artifacts));
+ };
+
+ let mut file = fs::File::open(driver_path)
+ .map_err(|error| Error::new(error).context("Opening driver"))?;
+ let mmap_size =
+ file.read_u64::<NativeEndian>()
+ .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+ // SAFETY: It's safe to map the driver as the service will only create a single
+ // mapping per process.
+ let mmap_addr = unsafe {
+ let fd = file.as_raw_fd();
+ mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+ };
+ if mmap_addr == MAP_FAILED {
+ bail!("Failed to mmap {:?}", driver_path);
+ }
+ let mmap_buf =
+ // SAFETY: The slice is created for the region of memory that was just
+ // successfully mapped into the process address space so it will be
+ // accessible and not referenced from anywhere else.
+ unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+ let bcc_handover =
+ bcc_handover_parse(mmap_buf).map_err(|_| anyhow!("Failed to parse Bcc Handover"))?;
+ Ok(Self::Real {
+ driver_path: driver_path.to_path_buf(),
+ mmap_addr,
+ mmap_size,
+ bcc_handover,
+ })
+ }
+
+ /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
+ pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
+ // Deterministically derive a key to use for sealing data, rather than using the CDI
+ // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+ // input key material is already cryptographically strong.
+ let mut key = ZVec::new(key_length)?;
+ let salt = &[];
+ hkdf(&mut key, Md::sha256(), self.dice_artifacts().cdi_seal(), salt, identifier)?;
+ Ok(key)
+ }
+
+ pub fn derive(
+ self,
+ code_hash: Hash,
+ config_desc: &[u8],
+ authority_hash: Hash,
+ debug: bool,
+ hidden: Hidden,
+ ) -> Result<OwnedDiceArtifacts> {
+ let input_values = InputValues::new(
+ code_hash,
+ Config::Descriptor(config_desc),
+ authority_hash,
+ if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
+ hidden,
+ );
+ let current_dice_artifacts = self.dice_artifacts();
+ let next_dice_artifacts = retry_bcc_main_flow(
+ current_dice_artifacts.cdi_attest(),
+ current_dice_artifacts.cdi_seal(),
+ current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
+ &input_values,
+ )
+ .context("DICE derive from driver")?;
+ if let Self::Real { driver_path, .. } = &self {
+ // Writing to the device wipes the artifacts. The string is ignored by the driver but
+ // included for documentation.
+ fs::write(driver_path, "wipe")
+ .map_err(|err| Error::new(err).context("Wiping driver"))?;
+ }
+ Ok(next_dice_artifacts)
+ }
+}
+
+impl Drop for DiceDriver<'_> {
+ fn drop(&mut self) {
+ if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
+ // SAFETY: All references to the mapped region have the same lifetime as self. Since
+ // self is being dropped, so are all the references to the mapped region meaning it's
+ // safe to unmap.
+ let ret = unsafe { munmap(mmap_addr, mmap_size) };
+ if ret != 0 {
+ log::warn!("Failed to munmap ({})", ret);
+ }
+ }
+ }
+}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index b16a1e1..2ff04f1 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,7 +33,7 @@
//! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
//! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
-use crate::dice::DiceDriver;
+use crate::dice_driver::DiceDriver;
use crate::ioutil;
use anyhow::{anyhow, bail, Context, Result};
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 491d4de..8175753 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -15,16 +15,15 @@
//! Microdroid Manager
mod dice;
+mod dice_driver;
mod instance;
mod ioutil;
mod payload;
mod swap;
+mod verify;
mod vm_payload_service;
mod vm_secret;
-use crate::dice::{DiceDriver, format_payload_config_descriptor};
-use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
-use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
@@ -32,55 +31,46 @@
VM_PAYLOAD_SERVICE_SOCKET_NAME,
ENCRYPTEDSTORE_MOUNTPOINT,
};
+
+use crate::dice::dice_derivation;
+use crate::dice_driver::DiceDriver;
+use crate::instance::{ApexData, InstanceDisk, MicrodroidData};
+use crate::verify::verify_payload;
+use crate::vm_payload_service::register_vm_payload_service;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
-use apkverify::{get_public_key_der, verify, V4Signature};
-use apkmanifest::get_manifest_info;
use binder::Strong;
-use diced_open_dice::OwnedDiceArtifacts;
-use glob::glob;
-use itertools::sorted;
-use libc::VMADDR_CID_HOST;
-use log::{error, info, warn};
use keystore2_crypto::ZVec;
-use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
+use libc::VMADDR_CID_HOST;
+use log::{error, info};
+use microdroid_metadata::{write_metadata, PayloadMetadata};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::sys::signal::Signal;
-use openssl::sha::Sha512;
-use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
-use rand::Fill;
+use payload::{load_metadata, to_metadata};
use rpcbinder::RpcSession;
use rustutils::sockets::android_get_control_socket;
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
use std::borrow::Cow::{Borrowed, Owned};
-use std::convert::TryInto;
use std::env;
use std::ffi::CString;
-use std::fs::{self, create_dir, OpenOptions, File};
+use std::fs::{self, create_dir, File, OpenOptions};
use std::io::{Read, Write};
+use std::os::unix::io::{FromRawFd, OwnedFd};
use std::os::unix::process::CommandExt;
use std::os::unix::process::ExitStatusExt;
-use std::os::unix::io::{FromRawFd, OwnedFd};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::str;
-use std::time::{Duration, SystemTime};
+use std::time::Duration;
use vm_secret::VmSecret;
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
-const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
-const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
-const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
-const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
-const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
const AVF_DEBUG_POLICY_RAMDUMP: &str = "/sys/firmware/devicetree/base/avf/guest/common/ramdump";
const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
"/sys/firmware/devicetree/base/virtualization/guest/debug-microdroid,no-verified-boot";
-const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
@@ -164,7 +154,7 @@
fn main() -> Result<()> {
// If debuggable, print full backtrace to console log with stdio_to_kmsg
- if system_properties::read_bool(DEBUGGABLE_PROP, true)? {
+ if is_debuggable()? {
env::set_var("RUST_BACKTRACE", "full");
}
@@ -279,36 +269,6 @@
}
Ok(())
}
-fn dice_derivation(
- dice: DiceDriver,
- verified_data: &MicrodroidData,
- payload_metadata: &PayloadMetadata,
-) -> Result<OwnedDiceArtifacts> {
- // Calculate compound digests of code and authorities
- let mut code_hash_ctx = Sha512::new();
- let mut authority_hash_ctx = Sha512::new();
- code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
- authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
- for extra_apk in &verified_data.extra_apks_data {
- code_hash_ctx.update(extra_apk.root_hash.as_ref());
- authority_hash_ctx.update(extra_apk.pubkey.as_ref());
- }
- for apex in &verified_data.apex_data {
- code_hash_ctx.update(apex.root_digest.as_ref());
- authority_hash_ctx.update(apex.public_key.as_ref());
- }
- let code_hash = code_hash_ctx.finish();
- let authority_hash = authority_hash_ctx.finish();
-
- let config_descriptor = format_payload_config_descriptor(payload_metadata)?;
-
- // Check debuggability, conservatively assuming it is debuggable
- let debuggable = system_properties::read_bool(DEBUGGABLE_PROP, true)?;
-
- // Send the details to diced
- let hidden = verified_data.salt.clone().try_into().unwrap();
- dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
-}
fn is_strict_boot() -> bool {
Path::new(AVF_STRICT_BOOT).exists()
@@ -322,10 +282,14 @@
!Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
}
+fn is_debuggable() -> Result<bool> {
+ Ok(system_properties::read_bool(DEBUGGABLE_PROP, true)?)
+}
+
fn should_export_tombstones(config: &VmPayloadConfig) -> bool {
match config.export_tombstones {
Some(b) => b,
- None => system_properties::read_bool(DEBUGGABLE_PROP, true).unwrap_or(false),
+ None => is_debuggable().unwrap_or(false),
}
}
@@ -425,7 +389,7 @@
zipfuse.mount(
MountForExec::Allowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
- Path::new(DM_MOUNTED_APK_PATH),
+ Path::new(verify::DM_MOUNTED_APK_PATH),
Path::new(VM_APK_CONTENTS_PATH),
"microdroid_manager.apk.mounted".to_owned(),
)?;
@@ -491,28 +455,6 @@
exec_task(task, service).context("Failed to run payload")
}
-struct ApkDmverityArgument<'a> {
- apk: &'a str,
- idsig: &'a str,
- name: &'a str,
- saved_root_hash: Option<&'a RootHash>,
-}
-
-fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
- let mut cmd = Command::new(APKDMVERITY_BIN);
-
- for argument in args {
- cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
- if let Some(root_hash) = argument.saved_root_hash {
- cmd.arg(&to_hex_string(root_hash));
- } else {
- cmd.arg("none");
- }
- }
-
- cmd.spawn().context("Spawn apkdmverity")
-}
-
enum MountForExec {
Allowed,
Disallowed,
@@ -583,147 +525,6 @@
Ok(())
}
-// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
-// when the root_hash values from the idsig file and the instance disk are different. This function
-// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
-// saved to the instance disk.
-fn verify_payload(
- metadata: &Metadata,
- saved_data: Option<&MicrodroidData>,
-) -> Result<MicrodroidData> {
- let start_time = SystemTime::now();
-
- // Verify main APK
- let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
- let root_hash_trustful =
- saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
-
- // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
- // instead of the value read from the idsig file.
- let main_apk_argument = {
- ApkDmverityArgument {
- apk: MAIN_APK_PATH,
- idsig: MAIN_APK_IDSIG_PATH,
- name: MAIN_APK_DEVICE_NAME,
- saved_root_hash: if root_hash_trustful {
- Some(root_hash_from_idsig.as_ref())
- } else {
- None
- },
- }
- };
- let mut apkdmverity_arguments = vec![main_apk_argument];
-
- // Verify extra APKs
- // For now, we can't read the payload config, so glob APKs and idsigs.
- // Later, we'll see if it matches with the payload config.
-
- // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
- // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
- let extra_apks =
- sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
- let extra_idsigs =
- sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
- ensure!(
- extra_apks.len() == extra_idsigs.len(),
- "Extra apks/idsigs mismatch: {} apks but {} idsigs",
- extra_apks.len(),
- extra_idsigs.len()
- );
-
- let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
- .iter()
- .map(|idsig| {
- get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
- })
- .collect();
-
- let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
- extra_root_hashes_from_idsig
- .iter()
- .enumerate()
- .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
- .collect()
- } else {
- vec![false; extra_root_hashes_from_idsig.len()]
- };
- let extra_apk_names: Vec<_> =
- (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
-
- for (i, extra_apk) in extra_apks.iter().enumerate() {
- apkdmverity_arguments.push({
- ApkDmverityArgument {
- apk: extra_apk.to_str().unwrap(),
- idsig: extra_idsigs[i].to_str().unwrap(),
- name: &extra_apk_names[i],
- saved_root_hash: if extra_root_hashes_trustful[i] {
- Some(&extra_root_hashes_from_idsig[i])
- } else {
- None
- },
- }
- });
- }
-
- // Start apkdmverity and wait for the dm-verify block
- let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
-
- // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
- // APEX payload.
- let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
-
- // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
- // Skip writing it if the debug policy ignoring identity is on
- if is_verified_boot() {
- write_apex_payload_data(saved_data, &apex_data_from_payload)?;
- }
-
- // Start apexd to activate APEXes
- system_properties::write("ctl.start", "apexd-vm")?;
-
- // TODO(inseob): add timeout
- apkdmverity_child.wait()?;
-
- // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
- // the APK file and therefore can be very slow if the APK is large. Note that this step is
- // taken only when the root_hash is un-trustful which can be either when this is the first boot
- // of the VM or APK was updated in the host.
- // TODO(jooyung): consider multithreading to make this faster
- let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
- let extra_apks_data = extra_root_hashes_from_idsig
- .into_iter()
- .enumerate()
- .map(|(i, extra_root_hash)| {
- let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
- let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
- Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
- })
- .collect::<Result<Vec<_>>>()?;
-
- info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
-
- // Use the salt from a verified instance, or generate a salt for a new instance.
- let salt = if let Some(saved_data) = saved_data {
- saved_data.salt.clone()
- } else if is_strict_boot() {
- // No need to add more entropy as a previous stage must have used a new, random salt.
- vec![0u8; 64]
- } else {
- let mut salt = vec![0u8; 64];
- salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
- salt
- };
-
- // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
- // fully verifying the APK or by comparing it with the saved root_hash.
- Ok(MicrodroidData {
- salt,
- apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
- extra_apks_data,
- apex_data: apex_data_from_payload,
- })
-}
-
fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
// For now, only the number of apks is important, as the mount point and dm-verity name is fixed
for i in 0..config.extra_apks.len() {
@@ -771,39 +572,6 @@
Ok(())
}
-fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
- Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
-}
-
-fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
- let current_sdk = get_current_sdk()?;
-
- let public_key_der = if !root_hash_trustful {
- verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
- "failed to verify {}",
- apk
- )))?
- } else {
- get_public_key_der(apk, current_sdk)?
- };
-
- match get_manifest_info(apk) {
- Ok(manifest_info) => {
- // TODO (b/299591171): Do something with this info
- info!("Manifest info is {manifest_info:?}")
- }
- Err(e) => warn!("Failed to read manifest info from APK: {e:?}"),
- };
-
- Ok(public_key_der)
-}
-
-fn get_current_sdk() -> Result<u32> {
- let current_sdk = system_properties::read("ro.build.version.sdk")?;
- let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
- current_sdk.parse().context("Malformed SDK version")
-}
-
fn load_config(payload_metadata: PayloadMetadata) -> Result<VmPayloadConfig> {
match payload_metadata {
PayloadMetadata::ConfigPath(path) => {
@@ -843,7 +611,7 @@
return Ok(());
}
- let debuggable = system_properties::read_bool(DEBUGGABLE_PROP, true)?;
+ let debuggable = is_debuggable()?;
let ramdump = get_debug_policy_bool(AVF_DEBUG_POLICY_RAMDUMP)?.unwrap_or_default();
let requested = debuggable | ramdump;
@@ -920,10 +688,6 @@
Ok(path)
}
-fn to_hex_string(buf: &[u8]) -> String {
- buf.iter().map(|b| format!("{:02X}", b)).collect()
-}
-
fn prepare_encryptedstore(vm_secret: &VmSecret) -> Result<Child> {
let mut key = ZVec::new(ENCRYPTEDSTORE_KEYSIZE)?;
vm_secret.derive_encryptedstore_key(&mut key)?;
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
new file mode 100644
index 0000000..06b15f7
--- /dev/null
+++ b/microdroid_manager/src/verify.rs
@@ -0,0 +1,236 @@
+// Copyright 2023 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.
+
+use crate::instance::{ApkData, MicrodroidData, RootHash};
+use crate::payload::get_apex_data_from_payload;
+use crate::{is_strict_boot, is_verified_boot, write_apex_payload_data, MicrodroidError};
+use anyhow::{anyhow, ensure, Context, Result};
+use apkmanifest::get_manifest_info;
+use apkverify::{get_public_key_der, verify, V4Signature};
+use glob::glob;
+use itertools::sorted;
+use log::{info, warn};
+use microdroid_metadata::Metadata;
+use rand::Fill;
+use rustutils::system_properties;
+use std::path::Path;
+use std::process::{Child, Command};
+use std::str;
+use std::time::SystemTime;
+
+pub const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
+
+const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
+const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
+const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
+const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
+const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
+
+const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
+
+/// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
+/// when the root_hash values from the idsig file and the instance disk are different. This function
+/// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
+/// saved to the instance disk.
+pub fn verify_payload(
+ metadata: &Metadata,
+ saved_data: Option<&MicrodroidData>,
+) -> Result<MicrodroidData> {
+ let start_time = SystemTime::now();
+
+ // Verify main APK
+ let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
+ let root_hash_trustful =
+ saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
+
+ // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
+ // instead of the value read from the idsig file.
+ let main_apk_argument = {
+ ApkDmverityArgument {
+ apk: MAIN_APK_PATH,
+ idsig: MAIN_APK_IDSIG_PATH,
+ name: MAIN_APK_DEVICE_NAME,
+ saved_root_hash: if root_hash_trustful {
+ Some(root_hash_from_idsig.as_ref())
+ } else {
+ None
+ },
+ }
+ };
+ let mut apkdmverity_arguments = vec![main_apk_argument];
+
+ // Verify extra APKs
+ // For now, we can't read the payload config, so glob APKs and idsigs.
+ // Later, we'll see if it matches with the payload config.
+
+ // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
+ // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
+ let extra_apks =
+ sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+ let extra_idsigs =
+ sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+ ensure!(
+ extra_apks.len() == extra_idsigs.len(),
+ "Extra apks/idsigs mismatch: {} apks but {} idsigs",
+ extra_apks.len(),
+ extra_idsigs.len()
+ );
+
+ let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
+ .iter()
+ .map(|idsig| {
+ get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
+ })
+ .collect();
+
+ let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
+ extra_root_hashes_from_idsig
+ .iter()
+ .enumerate()
+ .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
+ .collect()
+ } else {
+ vec![false; extra_root_hashes_from_idsig.len()]
+ };
+ let extra_apk_names: Vec<_> =
+ (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
+
+ for (i, extra_apk) in extra_apks.iter().enumerate() {
+ apkdmverity_arguments.push({
+ ApkDmverityArgument {
+ apk: extra_apk.to_str().unwrap(),
+ idsig: extra_idsigs[i].to_str().unwrap(),
+ name: &extra_apk_names[i],
+ saved_root_hash: if extra_root_hashes_trustful[i] {
+ Some(&extra_root_hashes_from_idsig[i])
+ } else {
+ None
+ },
+ }
+ });
+ }
+
+ // Start apkdmverity and wait for the dm-verify block
+ let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
+
+ // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
+ // APEX payload.
+ let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
+
+ // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
+ // Skip writing it if the debug policy ignoring identity is on
+ if is_verified_boot() {
+ write_apex_payload_data(saved_data, &apex_data_from_payload)?;
+ }
+
+ // Start apexd to activate APEXes
+ system_properties::write("ctl.start", "apexd-vm")?;
+
+ // TODO(inseob): add timeout
+ apkdmverity_child.wait()?;
+
+ // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
+ // the APK file and therefore can be very slow if the APK is large. Note that this step is
+ // taken only when the root_hash is un-trustful which can be either when this is the first boot
+ // of the VM or APK was updated in the host.
+ // TODO(jooyung): consider multithreading to make this faster
+ let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
+ let extra_apks_data = extra_root_hashes_from_idsig
+ .into_iter()
+ .enumerate()
+ .map(|(i, extra_root_hash)| {
+ let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
+ let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
+ Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
+ })
+ .collect::<Result<Vec<_>>>()?;
+
+ info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
+
+ // Use the salt from a verified instance, or generate a salt for a new instance.
+ let salt = if let Some(saved_data) = saved_data {
+ saved_data.salt.clone()
+ } else if is_strict_boot() {
+ // No need to add more entropy as a previous stage must have used a new, random salt.
+ vec![0u8; 64]
+ } else {
+ let mut salt = vec![0u8; 64];
+ salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
+ salt
+ };
+
+ // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
+ // fully verifying the APK or by comparing it with the saved root_hash.
+ Ok(MicrodroidData {
+ salt,
+ apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
+ extra_apks_data,
+ apex_data: apex_data_from_payload,
+ })
+}
+
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+ Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
+}
+
+fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
+ let current_sdk = get_current_sdk()?;
+
+ let public_key_der = if !root_hash_trustful {
+ verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
+ "failed to verify {}",
+ apk
+ )))?
+ } else {
+ get_public_key_der(apk, current_sdk)?
+ };
+
+ match get_manifest_info(apk) {
+ Ok(manifest_info) => {
+ // TODO (b/299591171): Do something with this info
+ info!("Manifest info is {manifest_info:?}")
+ }
+ Err(e) => warn!("Failed to read manifest info from APK: {e:?}"),
+ };
+
+ Ok(public_key_der)
+}
+
+fn get_current_sdk() -> Result<u32> {
+ let current_sdk = system_properties::read("ro.build.version.sdk")?;
+ let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
+ current_sdk.parse().context("Malformed SDK version")
+}
+
+struct ApkDmverityArgument<'a> {
+ apk: &'a str,
+ idsig: &'a str,
+ name: &'a str,
+ saved_root_hash: Option<&'a RootHash>,
+}
+
+fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
+ let mut cmd = Command::new(APKDMVERITY_BIN);
+
+ for argument in args {
+ cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
+ if let Some(root_hash) = argument.saved_root_hash {
+ cmd.arg(&hex::encode(root_hash));
+ } else {
+ cmd.arg("none");
+ }
+ }
+
+ cmd.spawn().context("Spawn apkdmverity")
+}