Merge "[x509] Include Apk/Apex info in attestation certificate extension" into main
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 2755436..02a5a28 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -31,7 +31,9 @@
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
Request, RequestProcessingError, Response, VmType,
};
-use service_vm_fake_chain::client_vm::fake_client_vm_dice_artifacts;
+use service_vm_fake_chain::client_vm::{
+ fake_client_vm_dice_artifacts, fake_sub_components, SubComponent,
+};
use service_vm_manager::ServiceVm;
use std::fs;
use std::fs::File;
@@ -41,7 +43,7 @@
use vmclient::VmInstance;
use x509_parser::{
certificate::X509Certificate,
- der_parser::{der::parse_der, oid, oid::Oid},
+ der_parser::{ber::BerObject, der::parse_der, oid, oid::Oid},
prelude::FromDer,
x509::{AlgorithmIdentifier, SubjectPublicKeyInfo, X509Version},
};
@@ -175,6 +177,25 @@
}
}
+fn check_vm_components(vm_components: &[BerObject]) -> Result<()> {
+ let expected_components = fake_sub_components();
+ assert_eq!(expected_components.len(), vm_components.len());
+ for i in 0..expected_components.len() {
+ check_vm_component(&vm_components[i], &expected_components[i])?;
+ }
+ Ok(())
+}
+
+fn check_vm_component(vm_component: &BerObject, expected_component: &SubComponent) -> Result<()> {
+ let vm_component = vm_component.as_sequence()?;
+ assert_eq!(4, vm_component.len());
+ assert_eq!(expected_component.name, vm_component[0].as_str()?);
+ assert_eq!(expected_component.version, vm_component[1].as_u64()?);
+ assert_eq!(expected_component.code_hash, vm_component[2].as_slice()?);
+ assert_eq!(expected_component.authority_hash, vm_component[3].as_slice()?);
+ Ok(())
+}
+
fn check_certificate_for_client_vm(
certificate: &[u8],
maced_public_key: &[u8],
@@ -218,13 +239,15 @@
let (remaining, extension) = parse_der(extension.value)?;
assert!(remaining.is_empty());
let attestation_ext = extension.as_sequence()?;
- assert_eq!(2, attestation_ext.len());
+ assert_eq!(3, attestation_ext.len());
assert_eq!(csr_payload.challenge, attestation_ext[0].as_slice()?);
let is_vm_secure = attestation_ext[1].as_bool()?;
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[2].as_sequence()?;
+ check_vm_components(vm_components)?;
// Checks other fields on the certificate
assert_eq!(X509Version::V3, cert.version());
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 2baca2a..73828a7 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -14,9 +14,11 @@
//! Generation of certificates and attestation extensions.
+use crate::dice::SubComponent;
use alloc::vec;
+use alloc::vec::Vec;
use der::{
- asn1::{BitStringRef, ObjectIdentifier, UIntRef},
+ asn1::{BitStringRef, ObjectIdentifier, UIntRef, Utf8StringRef},
oid::AssociatedOid,
Decode, Sequence,
};
@@ -42,15 +44,17 @@
/// ```asn1
/// AttestationDescription ::= SEQUENCE {
/// attestationChallenge OCTET_STRING,
+/// isVmSecure BOOLEAN,
+/// vmComponents SEQUENCE OF VmComponent,
/// }
/// ```
-/// TODO(b/312448064): Add VM root of trust and payload information to the extension.
#[derive(Debug, Clone, Sequence)]
pub(crate) struct AttestationExtension<'a> {
#[asn1(type = "OCTET STRING")]
attestation_challenge: &'a [u8],
/// Indicates whether the VM is operating under a secure configuration.
is_vm_secure: bool,
+ vm_components: Vec<VmComponent<'a>>,
}
impl<'a> AssociatedOid for AttestationExtension<'a> {
@@ -58,8 +62,43 @@
}
impl<'a> AttestationExtension<'a> {
- pub(crate) fn new(attestation_challenge: &'a [u8], is_vm_secure: bool) -> Self {
- Self { attestation_challenge, is_vm_secure }
+ pub(crate) fn new(
+ attestation_challenge: &'a [u8],
+ is_vm_secure: bool,
+ vm_components: Vec<VmComponent<'a>>,
+ ) -> Self {
+ Self { attestation_challenge, is_vm_secure, vm_components }
+ }
+}
+
+/// VM component information
+///
+/// ```asn1
+/// VmComponent ::= SEQUENCE {
+/// name UTF8String,
+/// securityVersion INTEGER,
+/// codeHash OCTET STRING,
+/// authorityHash OCTET STRING,
+/// }
+/// ```
+#[derive(Debug, Clone, Sequence)]
+pub(crate) struct VmComponent<'a> {
+ name: Utf8StringRef<'a>,
+ version: u64,
+ #[asn1(type = "OCTET STRING")]
+ code_hash: &'a [u8],
+ #[asn1(type = "OCTET STRING")]
+ authority_hash: &'a [u8],
+}
+
+impl<'a> VmComponent<'a> {
+ pub(crate) fn new(sub_component: &'a SubComponent) -> der::Result<Self> {
+ Ok(Self {
+ name: Utf8StringRef::new(&sub_component.name)?,
+ version: sub_component.version,
+ code_hash: &sub_component.code_hash,
+ authority_hash: &sub_component.authority_hash,
+ })
}
}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 4e87136..cfdac2d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -76,9 +76,16 @@
rand_bytes(&mut serial_number)?;
let subject = Name::encode_from_string("CN=Android Protected Virtual Machine Key")?;
let rkp_cert = Certificate::from_der(¶ms.remotely_provisioned_cert)?;
+ let vm_components =
+ if let Some(components) = client_vm_dice_chain.microdroid_payload_components() {
+ components.iter().map(cert::VmComponent::new).collect::<der::Result<Vec<_>>>()?
+ } else {
+ Vec::new()
+ };
let attestation_ext = cert::AttestationExtension::new(
&csr_payload.challenge,
client_vm_dice_chain.all_entries_are_secure(),
+ vm_components,
)
.to_vec()?;
let tbs_cert = cert::build_tbs_certificate(
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index 0a5eac1..15cfbc9 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -14,6 +14,7 @@
//! This module contains functions related to DICE.
+use alloc::string::String;
use alloc::vec::Vec;
use ciborium::value::{Integer, Value};
use core::cell::OnceCell;
@@ -35,6 +36,17 @@
const MODE: i64 = -4670551;
const SUBJECT_PUBLIC_KEY: i64 = -4670552;
+const CONFIG_DESC_COMPONENT_NAME: i64 = -70002;
+const CONFIG_DESC_SUB_COMPONENTS: i64 = -71002;
+
+const SUB_COMPONENT_NAME: i64 = 1;
+const SUB_COMPONENT_VERSION: i64 = 2;
+const SUB_COMPONENT_CODE_HASH: i64 = 3;
+const SUB_COMPONENT_AUTHORITY_HASH: i64 = 4;
+
+const MICRODROID_KERNEL_COMPONENT_NAME: &str = "vm_entry";
+const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
+
/// Represents a partially decoded `DiceCertChain` from the client VM.
/// The whole chain is defined as following:
///
@@ -85,7 +97,43 @@
payloads.len() >= 3,
"The client VM DICE chain must contain at least three DiceChainEntryPayloads"
);
- Ok(Self { payloads })
+ let chain = Self { payloads };
+ chain.validate_microdroid_components_names()?;
+ Ok(chain)
+ }
+
+ fn validate_microdroid_components_names(&self) -> Result<()> {
+ let microdroid_kernel_name = &self.microdroid_kernel().config_descriptor.component_name;
+ if MICRODROID_KERNEL_COMPONENT_NAME != microdroid_kernel_name {
+ error!(
+ "The second to last entry in the client VM DICE chain must describe the \
+ Microdroid kernel. Got {}",
+ microdroid_kernel_name
+ );
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ let microdroid_payload_name = &self.microdroid_payload().config_descriptor.component_name;
+ if MICRODROID_PAYLOAD_COMPONENT_NAME != microdroid_payload_name {
+ error!(
+ "The last entry in the client VM DICE chain must describe the Microdroid \
+ payload. Got {}",
+ microdroid_payload_name
+ );
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ Ok(())
+ }
+
+ fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
+ &self.payloads[self.payloads.len() - 2]
+ }
+
+ fn microdroid_payload(&self) -> &DiceChainEntryPayload {
+ &self.payloads[self.payloads.len() - 1]
+ }
+
+ pub(crate) fn microdroid_payload_components(&self) -> Option<&Vec<SubComponent>> {
+ self.microdroid_payload().config_descriptor.sub_components.as_ref()
}
/// Returns true if all payloads in the DICE chain are in normal mode.
@@ -163,9 +211,7 @@
code_hash: [u8; HASH_SIZE],
#[allow(dead_code)]
authority_hash: [u8; HASH_SIZE],
- /// TODO(b/313815907): Parse the config descriptor and read Apk/Apexes info in it.
- #[allow(dead_code)]
- config_descriptor: Vec<u8>,
+ config_descriptor: ConfigDescriptor,
}
impl DiceChainEntryPayload {
@@ -209,6 +255,7 @@
}
CONFIG_DESC => {
let config_descriptor = value_to_bytes(value, "config_descriptor")?;
+ let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
builder.config_descriptor(config_descriptor)?;
}
_ => {}
@@ -217,10 +264,149 @@
builder.build()
}
+/// Represents a partially decoded `ConfigurationDescriptor`.
+///
+/// The whole `ConfigurationDescriptor` is defined in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
+#[derive(Debug, Clone)]
+pub(crate) struct ConfigDescriptor {
+ component_name: String,
+ sub_components: Option<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptor {
+ fn from_slice(data: &[u8]) -> Result<Self> {
+ let value = Value::from_slice(data)?;
+ let entries = value_to_map(value, "ConfigDescriptor")?;
+ let mut builder = ConfigDescriptorBuilder::default();
+ for (key, value) in entries.into_iter() {
+ let key: i64 = value_to_num(key, "ConfigDescriptor key")?;
+ match key {
+ CONFIG_DESC_COMPONENT_NAME => {
+ let name = value_to_text(value, "ConfigDescriptor component_name")?;
+ builder.component_name(name)?;
+ }
+ CONFIG_DESC_SUB_COMPONENTS => {
+ let sub_components = value_to_array(value, "ConfigDescriptor sub_components")?;
+ let sub_components = sub_components
+ .into_iter()
+ .map(SubComponent::try_from)
+ .collect::<Result<Vec<_>>>()?;
+ builder.sub_components(sub_components)?
+ }
+ _ => {}
+ }
+ }
+ builder.build()
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+struct ConfigDescriptorBuilder {
+ component_name: OnceCell<String>,
+ sub_components: OnceCell<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptorBuilder {
+ fn component_name(&mut self, component_name: String) -> Result<()> {
+ set_once(&self.component_name, component_name, "ConfigDescriptor component_name")
+ }
+
+ fn sub_components(&mut self, sub_components: Vec<SubComponent>) -> Result<()> {
+ set_once(&self.sub_components, sub_components, "ConfigDescriptor sub_components")
+ }
+
+ fn build(mut self) -> Result<ConfigDescriptor> {
+ let component_name =
+ take_value(&mut self.component_name, "ConfigDescriptor component_name")?;
+ let sub_components = self.sub_components.take();
+ Ok(ConfigDescriptor { component_name, sub_components })
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct SubComponent {
+ pub(crate) name: String,
+ pub(crate) version: u64,
+ pub(crate) code_hash: Vec<u8>,
+ pub(crate) authority_hash: Vec<u8>,
+}
+
+impl TryFrom<Value> for SubComponent {
+ type Error = RequestProcessingError;
+
+ fn try_from(value: Value) -> Result<Self> {
+ let entries = value_to_map(value, "SubComponent")?;
+ let mut builder = SubComponentBuilder::default();
+ for (key, value) in entries.into_iter() {
+ let key: i64 = value_to_num(key, "SubComponent key")?;
+ match key {
+ SUB_COMPONENT_NAME => {
+ builder.name(value_to_text(value, "SubComponent component_name")?)?
+ }
+ SUB_COMPONENT_VERSION => {
+ builder.version(value_to_num(value, "SubComponent version")?)?
+ }
+ SUB_COMPONENT_CODE_HASH => {
+ builder.code_hash(value_to_bytes(value, "SubComponent code_hash")?)?
+ }
+ SUB_COMPONENT_AUTHORITY_HASH => {
+ builder.authority_hash(value_to_bytes(value, "SubComponent authority_hash")?)?
+ }
+ k => {
+ error!("Unknown key in SubComponent: {}", k);
+ return Err(RequestProcessingError::InvalidDiceChain);
+ }
+ }
+ }
+ builder.build()
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+struct SubComponentBuilder {
+ name: OnceCell<String>,
+ version: OnceCell<u64>,
+ code_hash: OnceCell<Vec<u8>>,
+ authority_hash: OnceCell<Vec<u8>>,
+}
+
+impl SubComponentBuilder {
+ fn name(&mut self, name: String) -> Result<()> {
+ set_once(&self.name, name, "SubComponent name")
+ }
+
+ fn version(&mut self, version: u64) -> Result<()> {
+ set_once(&self.version, version, "SubComponent version")
+ }
+
+ fn code_hash(&mut self, code_hash: Vec<u8>) -> Result<()> {
+ set_once(&self.code_hash, code_hash, "SubComponent code_hash")
+ }
+
+ fn authority_hash(&mut self, authority_hash: Vec<u8>) -> Result<()> {
+ set_once(&self.authority_hash, authority_hash, "SubComponent authority_hash")
+ }
+
+ fn build(mut self) -> Result<SubComponent> {
+ let name = take_value(&mut self.name, "SubComponent name")?;
+ let version = take_value(&mut self.version, "SubComponent version")?;
+ let code_hash = take_value(&mut self.code_hash, "SubComponent code_hash")?;
+ let authority_hash = take_value(&mut self.authority_hash, "SubComponent authority_hash")?;
+ Ok(SubComponent { name, version, code_hash, authority_hash })
+ }
+}
+
fn value_to_array(v: Value, context: &'static str) -> coset::Result<Vec<Value>> {
v.into_array().map_err(|e| to_unexpected_item_error(&e, "array", context))
}
+fn value_to_text(v: Value, context: &'static str) -> coset::Result<String> {
+ v.into_text().map_err(|e| to_unexpected_item_error(&e, "tstr", context))
+}
+
fn value_to_map(v: Value, context: &'static str) -> coset::Result<Vec<(Value, Value)>> {
v.into_map().map_err(|e| to_unexpected_item_error(&e, "map", context))
}
@@ -273,7 +459,7 @@
mode: OnceCell<DiceMode>,
code_hash: OnceCell<[u8; HASH_SIZE]>,
authority_hash: OnceCell<[u8; HASH_SIZE]>,
- config_descriptor: OnceCell<Vec<u8>>,
+ config_descriptor: OnceCell<ConfigDescriptor>,
}
fn set_once<T>(field: &OnceCell<T>, value: T, field_name: &str) -> Result<()> {
@@ -307,7 +493,7 @@
set_once(&self.authority_hash, authority_hash, "authority_hash")
}
- fn config_descriptor(&mut self, config_descriptor: Vec<u8>) -> Result<()> {
+ fn config_descriptor(&mut self, config_descriptor: ConfigDescriptor) -> Result<()> {
set_once(&self.config_descriptor, config_descriptor, "config_descriptor")
}