Merge "Add APK details to the DICE chain" into main
diff --git a/libs/apkmanifest/src/apkmanifest.rs b/libs/apkmanifest/src/apkmanifest.rs
index 6766b21..b92aa74 100644
--- a/libs/apkmanifest/src/apkmanifest.rs
+++ b/libs/apkmanifest/src/apkmanifest.rs
@@ -28,7 +28,7 @@
use std::path::Path;
/// Information extracted from the Android manifest inside an APK.
-#[derive(Debug)]
+#[derive(Debug, Default, Eq, PartialEq)]
pub struct ApkManifestInfo {
/// The package name of the app.
pub package: String,
diff --git a/libs/dice/open_dice/src/error.rs b/libs/dice/open_dice/src/error.rs
index 53ffd2d..bef9a9c 100644
--- a/libs/dice/open_dice/src/error.rs
+++ b/libs/dice/open_dice/src/error.rs
@@ -38,11 +38,11 @@
impl fmt::Display for DiceError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- Self::InvalidInput => write!(f, "invalid input"),
+ Self::InvalidInput => write!(f, "Invalid input"),
Self::BufferTooSmall(buffer_required_size) => {
- write!(f, "buffer too small. Required {buffer_required_size} bytes.")
+ write!(f, "Buffer too small; need {buffer_required_size} bytes")
}
- Self::PlatformError => write!(f, "platform error"),
+ Self::PlatformError => write!(f, "Platform error"),
}
}
}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index db65193..8710e54 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -24,6 +24,7 @@
"libbyteorder",
"libcap_rust",
"libciborium",
+ "libcoset",
"libdiced_open_dice",
"libdiced_sample_inputs",
"libglob",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index a576416..6b0775a 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -13,12 +13,15 @@
// limitations under the License.
use crate::dice_driver::DiceDriver;
+use crate::instance::ApkData;
use crate::{is_debuggable, MicrodroidData};
use anyhow::{bail, Context, Result};
-use ciborium::{cbor, ser};
+use ciborium::{cbor, Value};
+use coset::CborSerializable;
use diced_open_dice::OwnedDiceArtifacts;
use microdroid_metadata::PayloadMetadata;
-use openssl::sha::Sha512;
+use openssl::sha::{sha512, Sha512};
+use std::iter::once;
/// Perform an open DICE derivation for the payload.
pub fn dice_derivation(
@@ -26,6 +29,11 @@
verified_data: &MicrodroidData,
payload_metadata: &PayloadMetadata,
) -> Result<OwnedDiceArtifacts> {
+ let subcomponents = build_subcomponent_list(verified_data);
+
+ let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
+ .context("Building config descriptor")?;
+
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
@@ -42,8 +50,6 @@
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 = is_debuggable()?;
@@ -52,35 +58,71 @@
dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
}
-/// Returns a configuration descriptor of the given payload following the BCC's specification:
-/// https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
-/// {
-/// -70002: "Microdroid payload",
-/// ? -71000: tstr ; payload_config_path
-/// ? -71001: PayloadConfig
-/// }
-/// PayloadConfig = {
-/// 1: tstr ; payload_binary_name
-/// }
-fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
- const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
+struct Subcomponent<'a> {
+ name: String,
+ version: u64,
+ code_hash: &'a [u8],
+ authority_hash: Box<[u8]>,
+}
- let config_descriptor_cbor_value = match payload {
- PayloadMetadata::ConfigPath(payload_config_path) => cbor!({
- -70002 => MICRODROID_PAYLOAD_COMPONENT_NAME,
- -71000 => payload_config_path
- }),
- PayloadMetadata::Config(payload_config) => cbor!({
- -70002 => MICRODROID_PAYLOAD_COMPONENT_NAME,
- -71001 => {1 => payload_config.payload_binary_name}
- }),
- _ => bail!("Failed to match the payload against a config type: {:?}", payload),
+impl<'a> Subcomponent<'a> {
+ fn to_value(&self) -> Result<Value> {
+ Ok(cbor!({
+ 1 => self.name,
+ 2 => self.version,
+ 3 => self.code_hash,
+ 4 => self.authority_hash
+ })?)
}
- .context("Failed to build a CBOR Value from payload metadata")?;
- let mut config_descriptor = Vec::new();
- ser::into_writer(&config_descriptor_cbor_value, &mut config_descriptor)?;
- Ok(config_descriptor)
+ fn for_apk(apk: &'a ApkData) -> Self {
+ Self {
+ name: format!("apk:{}", apk.package_name),
+ version: apk.version_code,
+ code_hash: &apk.root_hash,
+ authority_hash:
+ // TODO(b/305925597): Hash the certificate not the pubkey
+ Box::new(sha512(&apk.pubkey)),
+ }
+ }
+}
+
+fn build_subcomponent_list(verified_data: &MicrodroidData) -> Vec<Subcomponent> {
+ if !cfg!(dice_changes) {
+ return vec![];
+ }
+
+ once(&verified_data.apk_data)
+ .chain(&verified_data.extra_apks_data)
+ .map(Subcomponent::for_apk)
+ .collect()
+}
+
+// Returns a configuration descriptor of the given payload. See vm_config.cddl for a definition
+// of the format.
+fn format_payload_config_descriptor(
+ payload: &PayloadMetadata,
+ subcomponents: &[Subcomponent],
+) -> Result<Vec<u8>> {
+ let mut map = Vec::new();
+ map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
+ map.push(match payload {
+ PayloadMetadata::ConfigPath(payload_config_path) => {
+ (cbor!(-71000)?, cbor!(payload_config_path)?)
+ }
+ PayloadMetadata::Config(payload_config) => {
+ (cbor!(-71001)?, cbor!({1 => payload_config.payload_binary_name})?)
+ }
+ _ => bail!("Failed to match the payload against a config type: {:?}", payload),
+ });
+
+ if !subcomponents.is_empty() {
+ let values =
+ subcomponents.iter().map(Subcomponent::to_value).collect::<Result<Vec<_>>>()?;
+ map.push((cbor!(-71002)?, cbor!(values)?));
+ }
+
+ Ok(Value::Map(map).to_vec()?)
}
#[cfg(test)]
@@ -88,17 +130,30 @@
use super::*;
use microdroid_metadata::PayloadConfig;
+ const NO_SUBCOMPONENTS: [Subcomponent; 0] = [];
+
+ fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
+ assert_eq!(
+ expected,
+ actual,
+ "Expected {}, got {}",
+ hex::encode(expected),
+ hex::encode(actual)
+ )
+ }
+
#[test]
fn payload_metadata_with_path_formats_correctly() -> Result<()> {
let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
- let config_descriptor = format_payload_config_descriptor(&payload_metadata)?;
+ let config_descriptor =
+ format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
0x15, 0x57, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74,
0x68,
];
- assert_eq!(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
+ assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
Ok(())
}
@@ -109,14 +164,48 @@
..Default::default()
};
let payload_metadata = PayloadMetadata::Config(payload_config);
- let config_descriptor = format_payload_config_descriptor(&payload_metadata)?;
+ let config_descriptor =
+ format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
0x15, 0x58, 0xa1, 0x01, 0x6e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x62,
0x69, 0x6e, 0x61, 0x72, 0x79,
];
- assert_eq!(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
+ assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
+ Ok(())
+ }
+
+ #[test]
+ fn payload_metadata_with_subcomponents_formats_correctly() -> Result<()> {
+ let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
+ let subcomponents = [
+ Subcomponent {
+ name: "apk1".to_string(),
+ version: 1,
+ code_hash: &[42u8],
+ authority_hash: Box::new([17u8]),
+ },
+ Subcomponent {
+ name: "apk2".to_string(),
+ version: 0x1000_0000_0001,
+ code_hash: &[43u8],
+ authority_hash: Box::new([19u8]),
+ },
+ ];
+ let config_descriptor =
+ format_payload_config_descriptor(&payload_metadata, &subcomponents)?;
+ // Verified using cbor.me.
+ static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
+ 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
+ 0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
+ 0x15, 0x57, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74,
+ 0x68, 0x3a, 0x00, 0x01, 0x15, 0x59, 0x82, 0xa4, 0x01, 0x64, 0x61, 0x70, 0x6b, 0x31,
+ 0x02, 0x01, 0x03, 0x81, 0x18, 0x2a, 0x04, 0x81, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
+ 0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x81,
+ 0x18, 0x2b, 0x04, 0x81, 0x13,
+ ];
+ assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
Ok(())
}
}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 2ff04f1..6c9e245 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -289,6 +289,8 @@
pub struct ApkData {
pub root_hash: Box<RootHash>,
pub pubkey: Box<[u8]>,
+ pub package_name: String,
+ pub version_code: u64,
}
impl ApkData {
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
index 06b15f7..22f3414 100644
--- a/microdroid_manager/src/verify.rs
+++ b/microdroid_manager/src/verify.rs
@@ -145,19 +145,26 @@
// 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 main_apk_data =
+ get_data_from_apk(DM_MOUNTED_APK_PATH, root_hash_from_idsig, 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 })
+ get_data_from_apk(&mount_path, extra_root_hash, extra_root_hashes_trustful[i])
})
.collect::<Result<Vec<_>>>()?;
info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
+ // At this point, we can ensure that the root hashes from the idsig files are trusted, either
+ // because we have fully verified the APK signature (and apkdmverity checks all the data we
+ // verified is consistent with the root hash) or because we have the saved APK data which will
+ // be checked as identical to the data we have verified.
+
// 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()
@@ -170,16 +177,36 @@
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 },
+ apk_data: main_apk_data,
extra_apks_data,
apex_data: apex_data_from_payload,
})
}
+fn get_data_from_apk(
+ apk_path: &str,
+ root_hash: Box<RootHash>,
+ root_hash_trustful: bool,
+) -> Result<ApkData> {
+ let pubkey = get_public_key_from_apk(apk_path, root_hash_trustful)?;
+ // Read package name etc from the APK manifest. In the unlikely event that they aren't present
+ // we use the default values. We simply put these values in the DICE node for the payload, and
+ // users of that can decide how to handle blank information - there's no reason for us
+ // to fail starting a VM even with such a weird APK.
+ let manifest_info = get_manifest_info(apk_path)
+ .map_err(|e| warn!("Failed to read manifest info from APK: {e:?}"))
+ .unwrap_or_default();
+
+ Ok(ApkData {
+ root_hash,
+ pubkey,
+ package_name: manifest_info.package,
+ version_code: manifest_info.version_code,
+ })
+}
+
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)
}
@@ -187,24 +214,14 @@
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 {
+ 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)
+ get_public_key_der(apk, current_sdk)
+ }
}
fn get_current_sdk() -> Result<u32> {
diff --git a/microdroid_manager/src/vm_config.cddl b/microdroid_manager/src/vm_config.cddl
new file mode 100644
index 0000000..052262d
--- /dev/null
+++ b/microdroid_manager/src/vm_config.cddl
@@ -0,0 +1,31 @@
+; Configuration Descriptor used in the DICE node that describes the payload of a Microdroid virtual
+; machine.
+;
+; See the Open DICE specification
+; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md,
+; and the Android Profile for DICE
+; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md.
+;
+; CDDL for the normal Configuration Descriptor can be found at
+; https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl
+
+; The configuration descriptor node for a Microdroid VM, with extensions to describe the contents
+; of the VM payload.
+VmConfigDescriptor = {
+ -70002 : "Microdroid payload", ; Component name
+ (? -71000: tstr // ; Path to the payload config file
+ ? -71001: PayloadConfig),
+ ? -71002: [+ SubcomponentDescriptor],
+}
+
+PayloadConfig = {
+ 1: tstr ; Path to the binary file where payload execution starts
+}
+
+; Describes a unit of code (e.g. an APK or an APEX) present inside the VM.
+SubcomponentDescriptor = {
+ 1: tstr, ; Component name
+ 2: uint, ; Security version
+ ? 3: bstr, ; Code hash
+ 4: bstr, ; Authority hash
+}