[attestation] Validate vendor module loaded by client VM in RKP VM

This cl added the following tasks to the RKP VM:

- Parses a client VM DICE chain containing an additional vendor
module entry.
- Validates the code hash in the vendor module DICE entry against
the code hash read from the device tree.

The cl also adds a CTS test that triggers the VM attestation from
a VM with vendor module.

Bug: 330678211
Test: atest MicrodroidTests
Change-Id: Id56c6edd8baa32bae6a8ad7b5bca7b18ce167022
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
index 9f83b78..c9aa711 100644
--- a/service_vm/comm/src/message.rs
+++ b/service_vm/comm/src/message.rs
@@ -158,6 +158,12 @@
 
     /// The DICE chain from the client VM is invalid.
     InvalidDiceChain,
+
+    /// Cannot find the vendor hash tree root digest in the device tree.
+    NoVendorHashTreeRootDigestInDT,
+
+    /// The vendor partition loaded by the client VM is invalid.
+    InvalidVendorPartition,
 }
 
 impl fmt::Display for RequestProcessingError {
@@ -186,6 +192,12 @@
             Self::InvalidDiceChain => {
                 write!(f, "The DICE chain from the client VM is invalid")
             }
+            Self::NoVendorHashTreeRootDigestInDT => {
+                write!(f, "Cannot find the vendor hash tree root digest in the device tree")
+            }
+            Self::InvalidVendorPartition => {
+                write!(f, "The vendor partition loaded by the client VM is invalid")
+            }
         }
     }
 }
diff --git a/service_vm/requests/src/api.rs b/service_vm/requests/src/api.rs
index 315d2af..9eca20f 100644
--- a/service_vm/requests/src/api.rs
+++ b/service_vm/requests/src/api.rs
@@ -21,22 +21,38 @@
 use service_vm_comm::{Request, Response};
 
 /// Processes a request and returns the corresponding response.
-/// This function serves as the entry point for the request processing
-/// module.
-pub fn process_request(request: Request, dice_artifacts: &dyn DiceArtifacts) -> Response {
+/// This function serves as the entry point for the request processing module.
+pub fn process_request(request: Request, context: &RequestContext) -> Response {
     match request {
         Request::Reverse(v) => Response::Reverse(reverse(v)),
-        Request::GenerateEcdsaP256KeyPair => rkp::generate_ecdsa_p256_key_pair(dice_artifacts)
-            .map_or_else(Response::Err, Response::GenerateEcdsaP256KeyPair),
+        Request::GenerateEcdsaP256KeyPair => {
+            rkp::generate_ecdsa_p256_key_pair(context.dice_artifacts)
+                .map_or_else(Response::Err, Response::GenerateEcdsaP256KeyPair)
+        }
         Request::GenerateCertificateRequest(p) => {
-            rkp::generate_certificate_request(p, dice_artifacts)
+            rkp::generate_certificate_request(p, context.dice_artifacts)
                 .map_or_else(Response::Err, Response::GenerateCertificateRequest)
         }
-        Request::RequestClientVmAttestation(p) => client_vm::request_attestation(p, dice_artifacts)
-            .map_or_else(Response::Err, Response::RequestClientVmAttestation),
+        Request::RequestClientVmAttestation(p) => client_vm::request_attestation(
+            p,
+            context.dice_artifacts,
+            context.vendor_hashtree_root_digest,
+        )
+        .map_or_else(Response::Err, Response::RequestClientVmAttestation),
     }
 }
 
+/// The context for the request processing.
+///
+/// This struct contains the reference data used during the request processing.
+pub struct RequestContext<'a> {
+    /// The reference DICE artifacts.
+    pub dice_artifacts: &'a dyn DiceArtifacts,
+
+    /// The reference hash tree root digest of the vendor partition if exists.
+    pub vendor_hashtree_root_digest: Option<&'a [u8]>,
+}
+
 fn reverse(payload: Vec<u8>) -> Vec<u8> {
     payload.into_iter().rev().collect()
 }
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 15a3bd0..d2e674b 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -16,9 +16,7 @@
 //! client VM.
 
 use crate::cert;
-use crate::dice::{
-    validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain, DiceChainEntryPayload,
-};
+use crate::dice::{ClientVmDiceChain, DiceChainEntryPayload};
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
 use bssl_avf::{rand_bytes, sha256, Digester, EcKey, PKey};
@@ -28,7 +26,7 @@
 use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
 use der::{Decode, Encode};
 use diced_open_dice::{DiceArtifacts, HASH_SIZE};
-use log::{error, info};
+use log::{debug, error, info};
 use microdroid_kernel_hashes::{HASH_SIZE as KERNEL_HASH_SIZE, OS_HASHES};
 use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
 use x509_cert::{certificate::Certificate, name::Name};
@@ -41,6 +39,7 @@
 pub(super) fn request_attestation(
     params: ClientVmAttestationParams,
     dice_artifacts: &dyn DiceArtifacts,
+    vendor_hashtree_root_digest_from_dt: Option<&[u8]>,
 ) -> Result<Vec<u8>> {
     let csr = Csr::from_cbor_slice(&params.csr)?;
     let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
@@ -50,22 +49,11 @@
     })?;
     let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
 
-    // Validates the prefix of the Client VM DICE chain in the CSR.
-    let service_vm_dice_chain =
-        dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
-    let service_vm_dice_chain = parse_value_array(service_vm_dice_chain, "service_vm_dice_chain")?;
-    let client_vm_dice_chain = parse_value_array(&csr.dice_cert_chain, "client_vm_dice_chain")?;
-    validate_client_vm_dice_chain_prefix_match(&client_vm_dice_chain, &service_vm_dice_chain)?;
-    // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
-    // DiceChainEntryPayloads.
-    let client_vm_dice_chain =
-        ClientVmDiceChain::validate_signatures_and_parse_dice_chain(client_vm_dice_chain)?;
-
-    // The last entry in the service VM DICE chain describes the service VM, which should
-    // be signed with the same key as the kernel image.
-    let service_vm_entry = service_vm_dice_chain.last().unwrap();
-    validate_kernel_authority_hash(client_vm_dice_chain.microdroid_kernel(), service_vm_entry)?;
-    validate_kernel_code_hash(&client_vm_dice_chain)?;
+    let client_vm_dice_chain = validate_client_vm_dice_chain(
+        &csr.dice_cert_chain,
+        dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?,
+        vendor_hashtree_root_digest_from_dt,
+    )?;
 
     // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
     let aad = &[];
@@ -140,6 +128,83 @@
     key.ecdsa_sign(&digest)
 }
 
+fn validate_service_vm_dice_chain_length(service_vm_dice_chain: &[Value]) -> Result<()> {
+    if service_vm_dice_chain.len() < 3 {
+        // The service VM's DICE chain must contain the root key and at least two other entries
+        // that describe:
+        //   - pvmfw
+        //   - Service VM kernel
+        error!(
+            "The service VM DICE chain must contain at least three entries. Got '{}' entries",
+            service_vm_dice_chain.len()
+        );
+        return Err(RequestProcessingError::InternalError);
+    }
+    Ok(())
+}
+
+/// Validates the client VM DICE chain against the reference service VM DICE chain and
+/// the reference `vendor_hashtree_root_digest`.
+///
+/// Returns the valid `ClientVmDiceChain` if the validation succeeds.
+fn validate_client_vm_dice_chain(
+    client_vm_dice_chain: &[u8],
+    service_vm_dice_chain: &[u8],
+    vendor_hashtree_root_digest: Option<&[u8]>,
+) -> Result<ClientVmDiceChain> {
+    let service_vm_dice_chain = parse_value_array(service_vm_dice_chain, "service_vm_dice_chain")?;
+    validate_service_vm_dice_chain_length(&service_vm_dice_chain)?;
+
+    let client_vm_dice_chain = parse_value_array(client_vm_dice_chain, "client_vm_dice_chain")?;
+    validate_client_vm_dice_chain_prefix_match(&client_vm_dice_chain, &service_vm_dice_chain)?;
+
+    // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
+    // DiceChainEntryPayloads.
+    let client_vm_dice_chain = ClientVmDiceChain::validate_signatures_and_parse_dice_chain(
+        client_vm_dice_chain,
+        service_vm_dice_chain.len(),
+    )?;
+    validate_vendor_partition_code_hash_if_exists(
+        &client_vm_dice_chain,
+        vendor_hashtree_root_digest,
+    )?;
+
+    // The last entry in the service VM DICE chain describes the service VM, which should
+    // be signed with the same key as the kernel image.
+    let service_vm_entry = service_vm_dice_chain.last().unwrap();
+    validate_kernel_authority_hash(client_vm_dice_chain.microdroid_kernel(), service_vm_entry)?;
+    validate_kernel_code_hash(&client_vm_dice_chain)?;
+
+    info!("The client VM DICE chain validation succeeded");
+    Ok(client_vm_dice_chain)
+}
+
+fn validate_vendor_partition_code_hash_if_exists(
+    client_vm_dice_chain: &ClientVmDiceChain,
+    vendor_hashtree_root_digest: Option<&[u8]>,
+) -> Result<()> {
+    let Some(vendor_partition) = client_vm_dice_chain.vendor_partition() else {
+        debug!("The vendor partition is not present in the Client VM DICE chain");
+        return Ok(());
+    };
+    let Some(expected_root_digest) = vendor_hashtree_root_digest else {
+        error!(
+            "The vendor partition is present in the DICE chain, \
+             but the vendor_hashtree_root_digest is not provided in the DT"
+        );
+        return Err(RequestProcessingError::NoVendorHashTreeRootDigestInDT);
+    };
+    if Digester::sha512().digest(expected_root_digest)? == vendor_partition.code_hash {
+        Ok(())
+    } else {
+        error!(
+            "The vendor partition code hash in the Client VM DICE chain does \
+             not match the expected value from the DT"
+        );
+        Err(RequestProcessingError::InvalidVendorPartition)
+    }
+}
+
 /// Validates that the authority hash of the Microdroid kernel in the Client VM DICE chain
 /// matches the authority hash of the service VM entry in the service VM DICE chain, because
 /// the Microdroid kernel is signed with the same key as the one used for the service VM.
@@ -198,3 +263,20 @@
     let service_vm = DiceChainEntryPayload::from_slice(&payload)?;
     Ok(service_vm.authority_hash)
 }
+
+fn validate_client_vm_dice_chain_prefix_match(
+    client_vm_dice_chain: &[Value],
+    service_vm_dice_chain: &[Value],
+) -> Result<()> {
+    // Ignores the last entry that describes service VM
+    let entries_up_to_pvmfw = &service_vm_dice_chain[0..(service_vm_dice_chain.len() - 1)];
+    if client_vm_dice_chain.get(0..entries_up_to_pvmfw.len()) == Some(entries_up_to_pvmfw) {
+        Ok(())
+    } else {
+        error!(
+            "The client VM's DICE chain does not match service VM's DICE chain up to \
+             the pvmfw entry"
+        );
+        Err(RequestProcessingError::InvalidDiceChain)
+    }
+}
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index 657e482..df29676 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -31,7 +31,7 @@
     Label,
 };
 use diced_open_dice::{DiceMode, HASH_SIZE};
-use log::error;
+use log::{debug, error, info};
 use service_vm_comm::RequestProcessingError;
 
 type Result<T> = result::Result<T, RequestProcessingError>;
@@ -50,7 +50,8 @@
 const SUB_COMPONENT_CODE_HASH: i64 = 3;
 const SUB_COMPONENT_AUTHORITY_HASH: i64 = 4;
 
-const MICRODROID_KERNEL_COMPONENT_NAME: &str = "vm_entry";
+const KERNEL_COMPONENT_NAME: &str = "vm_entry";
+const VENDOR_PARTITION_COMPONENT_NAME: &str = "Microdroid vendor";
 const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
 
 /// Represents a partially decoded `DiceCertChain` from the client VM.
@@ -63,6 +64,10 @@
 #[derive(Debug, Clone)]
 pub(crate) struct ClientVmDiceChain {
     payloads: Vec<DiceChainEntryPayload>,
+    /// The index of the vendor partition entry in the DICE chain if it exists.
+    vendor_partition_index: Option<usize>,
+    /// The index of the kernel entry in the DICE chain.
+    kernel_index: usize,
 }
 
 impl ClientVmDiceChain {
@@ -75,7 +80,11 @@
     /// Returns a partially decoded client VM's DICE chain if the verification succeeds.
     pub(crate) fn validate_signatures_and_parse_dice_chain(
         mut client_vm_dice_chain: Vec<Value>,
+        service_vm_dice_chain_len: usize,
     ) -> Result<Self> {
+        let has_vendor_partition =
+            vendor_partition_exists(client_vm_dice_chain.len(), service_vm_dice_chain_len)?;
+
         let root_public_key =
             CoseKey::from_cbor_value(client_vm_dice_chain.remove(0))?.try_into()?;
 
@@ -93,45 +102,62 @@
             payloads.push(payload);
             previous_public_key = &payloads.last().unwrap().subject_public_key;
         }
-        // After successfully calling `validate_client_vm_dice_chain_prefix_match`, we can be
-        // certain that the client VM's DICE chain must contain at least three entries that
-        // describe:
-        // - pvmfw
-        // - Microdroid kernel
-        // - Apk/Apexes
-        assert!(
-            payloads.len() >= 3,
-            "The client VM DICE chain must contain at least three DiceChainEntryPayloads"
-        );
-        let chain = Self { payloads };
-        chain.validate_microdroid_components_names()?;
-        Ok(chain)
+
+        Self::build(payloads, has_vendor_partition)
     }
 
-    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;
+    fn build(
+        dice_entry_payloads: Vec<DiceChainEntryPayload>,
+        has_vendor_partition: bool,
+    ) -> Result<Self> {
+        let microdroid_payload_name =
+            &dice_entry_payloads[dice_entry_payloads.len() - 1].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 {}",
+                 payload. Got '{}'",
                 microdroid_payload_name
             );
             return Err(RequestProcessingError::InvalidDiceChain);
         }
-        Ok(())
+
+        let (vendor_partition_index, kernel_index) = if has_vendor_partition {
+            let index = dice_entry_payloads.len() - 2;
+            let vendor_partition_name =
+                &dice_entry_payloads[index].config_descriptor.component_name;
+            if VENDOR_PARTITION_COMPONENT_NAME != vendor_partition_name {
+                error!(
+                    "The vendor partition entry in the client VM DICE chain must describe the \
+                        vendor partition. Got '{}'",
+                    vendor_partition_name,
+                );
+                return Err(RequestProcessingError::InvalidDiceChain);
+            }
+            (Some(index), index - 1)
+        } else {
+            (None, dice_entry_payloads.len() - 2)
+        };
+
+        let kernel_name = &dice_entry_payloads[kernel_index].config_descriptor.component_name;
+        if KERNEL_COMPONENT_NAME != kernel_name {
+            error!(
+                "The microdroid kernel entry in the client VM DICE chain must describe the \
+                 Microdroid kernel. Got '{}'",
+                kernel_name,
+            );
+            return Err(RequestProcessingError::InvalidDiceChain);
+        }
+
+        debug!("All entries in the client VM DICE chain have correct component names");
+        Ok(Self { payloads: dice_entry_payloads, vendor_partition_index, kernel_index })
     }
 
     pub(crate) fn microdroid_kernel(&self) -> &DiceChainEntryPayload {
-        &self.payloads[self.payloads.len() - 2]
+        &self.payloads[self.kernel_index]
+    }
+
+    pub(crate) fn vendor_partition(&self) -> Option<&DiceChainEntryPayload> {
+        self.vendor_partition_index.map(|i| &self.payloads[i])
     }
 
     pub(crate) fn microdroid_payload(&self) -> &DiceChainEntryPayload {
@@ -148,39 +174,33 @@
     }
 }
 
-/// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
-/// entry.
-///
-/// Returns `Ok(())` if the verification succeeds.
-pub(crate) fn validate_client_vm_dice_chain_prefix_match(
-    client_vm_dice_chain: &[Value],
-    service_vm_dice_chain: &[Value],
-) -> Result<()> {
-    if service_vm_dice_chain.len() < 3 {
-        // The service VM's DICE chain must contain the root key and at least two other entries
-        // that describe:
-        //   - pvmfw
-        //   - Service VM kernel
-        error!("The service VM DICE chain must contain at least three entries");
-        return Err(RequestProcessingError::InternalError);
+fn vendor_partition_exists(
+    client_vm_dice_chain_len: usize,
+    service_vm_dice_chain_len: usize,
+) -> Result<bool> {
+    let entries_up_to_pvmfw_len = service_vm_dice_chain_len - 1;
+    // Client VM DICE chain = entries_up_to_pvmfw
+    //    + Vendor module entry (exists only when the vendor partition is present)
+    //    + Microdroid kernel entry (added in pvmfw)
+    //    + Apk/Apexes entry (added in microdroid)
+    match client_vm_dice_chain_len.checked_sub(entries_up_to_pvmfw_len) {
+        Some(2) => {
+            debug!("The vendor partition entry is not present in the client VM's DICE chain");
+            Ok(false)
+        }
+        Some(3) => {
+            info!("The vendor partition entry is present in the client VM's DICE chain");
+            Ok(true)
+        }
+        _ => {
+            error!(
+                "The client VM's DICE chain must contain two or three extra entries. \
+            Service VM DICE chain: {} entries, client VM DICE chain: {} entries",
+                service_vm_dice_chain_len, client_vm_dice_chain_len
+            );
+            Err(RequestProcessingError::InvalidDiceChain)
+        }
     }
-    // Ignores the last entry that describes service VM
-    let entries_up_to_pvmfw = &service_vm_dice_chain[0..(service_vm_dice_chain.len() - 1)];
-    if entries_up_to_pvmfw.len() + 2 != client_vm_dice_chain.len() {
-        // Client VM DICE chain = entries_up_to_pvmfw
-        //    + Microdroid kernel entry (added in pvmfw)
-        //    + Apk/Apexes entry (added in microdroid)
-        error!("The client VM's DICE chain must contain exactly two extra entries");
-        return Err(RequestProcessingError::InvalidDiceChain);
-    }
-    if entries_up_to_pvmfw != &client_vm_dice_chain[0..entries_up_to_pvmfw.len()] {
-        error!(
-            "The client VM's DICE chain does not match service VM's DICE chain up to \
-             the pvmfw entry"
-        );
-        return Err(RequestProcessingError::InvalidDiceChain);
-    }
-    Ok(())
 }
 
 #[derive(Debug, Clone)]
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index 0dfac09..36b006f 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -26,4 +26,4 @@
 mod pub_key;
 mod rkp;
 
-pub use api::process_request;
+pub use api::{process_request, RequestContext};