Merge "libfdt: Add FdtNode::descendants()" into main
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 1c18d2d..4c5a622 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -48,10 +48,12 @@
     test_suites: ["general-tests"],
     rustlibs: [
         "libandroid_logger",
+        "libanyhow",
         "libapkverify",
         "libapkzip",
         "libbyteorder",
         "liblog_rust",
+        "libopenssl",
         "libzip",
     ],
     data: ["tests/data/*"],
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index 6af8122..1f3c74f 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -26,5 +26,5 @@
 mod v4;
 
 pub use algorithms::{HashAlgorithm, SignatureAlgorithmID};
-pub use v3::{get_public_key_der, verify};
+pub use v3::{extract_signed_data, verify, SignedData};
 pub use v4::{get_apk_digest, V4Signature};
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 8a8ad73..88644c7 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -44,13 +44,9 @@
     public_key: PKey<pkey::Public>,
 }
 
-impl Signer {
-    fn sdk_range(&self) -> RangeInclusive<u32> {
-        self.min_sdk..=self.max_sdk
-    }
-}
-
-struct SignedData {
+/// Contains the signed data part of an APK v3 signature.
+#[derive(Debug)]
+pub struct SignedData {
     digests: LengthPrefixed<Vec<LengthPrefixed<Digest>>>,
     certificates: LengthPrefixed<Vec<LengthPrefixed<X509Certificate>>>,
     min_sdk: u32,
@@ -59,20 +55,6 @@
     additional_attributes: LengthPrefixed<Vec<LengthPrefixed<AdditionalAttributes>>>,
 }
 
-impl SignedData {
-    fn sdk_range(&self) -> RangeInclusive<u32> {
-        self.min_sdk..=self.max_sdk
-    }
-
-    fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
-        Ok(self
-            .digests
-            .iter()
-            .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
-            .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
-    }
-}
-
 #[derive(Debug)]
 pub(crate) struct Signature {
     /// Option is used here to allow us to ignore unsupported algorithm.
@@ -80,6 +62,7 @@
     signature: LengthPrefixed<Bytes>,
 }
 
+#[derive(Debug)]
 struct Digest {
     signature_algorithm_id: Option<SignatureAlgorithmID>,
     digest: LengthPrefixed<Bytes>,
@@ -88,19 +71,19 @@
 type X509Certificate = Bytes;
 type AdditionalAttributes = Bytes;
 
-/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the public key
-/// associated with the signer in DER format.
-pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the SignedData from
+/// the signature.
+pub fn verify<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
     let apk = File::open(apk_path.as_ref())?;
     let (signer, mut sections) = extract_signer_and_apk_sections(apk, current_sdk)?;
     signer.verify(&mut sections)
 }
 
-/// Gets the public key (in DER format) that was used to sign the given APK/APEX file
-pub fn get_public_key_der<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<Box<[u8]>> {
+/// Extracts the SignedData from the signature of the given APK. (The signature is not verified.)
+pub fn extract_signed_data<P: AsRef<Path>>(apk_path: P, current_sdk: u32) -> Result<SignedData> {
     let apk = File::open(apk_path.as_ref())?;
     let (signer, _) = extract_signer_and_apk_sections(apk, current_sdk)?;
-    Ok(signer.public_key.public_key_to_der()?.into_boxed_slice())
+    signer.parse_signed_data()
 }
 
 pub(crate) fn extract_signer_and_apk_sections<R: Read + Seek>(
@@ -123,6 +106,10 @@
 }
 
 impl Signer {
+    fn sdk_range(&self) -> RangeInclusive<u32> {
+        self.min_sdk..=self.max_sdk
+    }
+
     /// Selects the signature that has the strongest supported `SignatureAlgorithmID`.
     /// The strongest signature is used in both v3 verification and v4 apk digest computation.
     pub(crate) fn strongest_signature(&self) -> Result<&Signature> {
@@ -143,27 +130,33 @@
         Ok(digest.digest.as_ref().to_vec().into_boxed_slice())
     }
 
-    /// Verifies the strongest signature from signatures against signed data using public key.
-    /// Returns the verified signed data.
-    fn verify_signature(&self, strongest: &Signature) -> Result<SignedData> {
-        let mut verifier = strongest
+    /// Verifies a signature over the signed data using the public key.
+    fn verify_signature(&self, signature: &Signature) -> Result<()> {
+        let mut verifier = signature
             .signature_algorithm_id
             .context("Unsupported algorithm")?
             .new_verifier(&self.public_key)?;
         verifier.update(&self.signed_data)?;
-        ensure!(verifier.verify(&strongest.signature)?, "Signature is invalid.");
-        // It is now safe to parse signed data.
+        ensure!(verifier.verify(&signature.signature)?, "Signature is invalid.");
+        Ok(())
+    }
+
+    /// Returns the signed data, converted from bytes.
+    fn parse_signed_data(&self) -> Result<SignedData> {
         self.signed_data.slice(..).read()
     }
 
     /// The steps in this method implements APK Signature Scheme v3 verification step 3.
-    fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<Box<[u8]>> {
+    fn verify<R: Read + Seek>(&self, sections: &mut ApkSections<R>) -> Result<SignedData> {
         // 1. Choose the strongest supported signature algorithm ID from signatures.
         let strongest = self.strongest_signature()?;
 
         // 2. Verify the corresponding signature from signatures against signed data using public
         // key.
-        let verified_signed_data = self.verify_signature(strongest)?;
+        self.verify_signature(strongest)?;
+
+        // It is now safe to parse signed data.
+        let verified_signed_data = self.parse_signed_data()?;
 
         // 3. Verify the min and max SDK versions in the signed data match those specified for the
         //    signer.
@@ -199,8 +192,7 @@
 
         // 7. Verify that public key of the first certificate of certificates is identical to public
         //    key.
-        let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
-        let cert = X509::from_der(cert.as_ref())?;
+        let cert = X509::from_der(verified_signed_data.first_certificate_der()?)?;
         ensure!(
             cert.public_key()?.public_eq(&self.public_key),
             "Public key mismatch between certificate and signature record"
@@ -209,7 +201,29 @@
         // TODO(b/245914104)
         // 8. If the proof-of-rotation attribute exists for the signer verify that the
         // struct is valid and this signer is the last certificate in the list.
-        Ok(self.public_key.public_key_to_der()?.into_boxed_slice())
+
+        Ok(verified_signed_data)
+    }
+}
+
+impl SignedData {
+    /// Returns the first X.509 certificate in the signed data, encoded in DER form. (All other
+    /// certificates are ignored for v3; this certificate describes the public key that was actually
+    /// used to sign the APK.)
+    pub fn first_certificate_der(&self) -> Result<&[u8]> {
+        Ok(self.certificates.first().context("No certificates listed")?)
+    }
+
+    fn sdk_range(&self) -> RangeInclusive<u32> {
+        self.min_sdk..=self.max_sdk
+    }
+
+    fn find_digest_by_algorithm(&self, algorithm_id: SignatureAlgorithmID) -> Result<&Digest> {
+        Ok(self
+            .digests
+            .iter()
+            .find(|&dig| dig.signature_algorithm_id == Some(algorithm_id))
+            .context(format!("Digest not found for algorithm: {:?}", algorithm_id))?)
     }
 }
 
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 0d8e020..441b708 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+use anyhow::Result;
 use apkverify::{
-    get_apk_digest, get_public_key_der, testing::assert_contains, verify, SignatureAlgorithmID,
+    extract_signed_data, get_apk_digest, testing::assert_contains, verify, SignatureAlgorithmID,
 };
 use apkzip::zip_sections;
 use byteorder::{LittleEndian, ReadBytesExt};
 use log::info;
+use openssl::x509::X509;
 use std::fmt::Write;
 use std::io::{Seek, SeekFrom};
 use std::{fs, matches, path::Path};
@@ -286,22 +288,30 @@
 /// * public key extracted from apk without verification
 /// * expected public key from the corresponding .der file
 fn validate_apk_public_key<P: AsRef<Path>>(apk_path: P) {
-    let public_key_from_verification = verify(&apk_path, SDK_INT);
-    let public_key_from_verification =
-        public_key_from_verification.expect("Error in verification result");
+    let signed_data_from_verification =
+        verify(&apk_path, SDK_INT).expect("Error in verification result");
+    let cert_from_verification = signed_data_from_verification.first_certificate_der().unwrap();
+    let public_key_from_verification = public_key_der_from_cert(cert_from_verification).unwrap();
 
     let expected_public_key_path = format!("{}.der", apk_path.as_ref().to_str().unwrap());
     assert_bytes_eq_to_data_in_file(&public_key_from_verification, expected_public_key_path);
 
-    let public_key_from_apk = get_public_key_der(&apk_path, SDK_INT);
-    let public_key_from_apk =
-        public_key_from_apk.expect("Error when extracting public key from apk");
+    let signed_data_from_apk = extract_signed_data(&apk_path, SDK_INT)
+        .expect("Error when extracting signed data from apk");
+    let cert_from_apk = signed_data_from_apk.first_certificate_der().unwrap();
+    // If the two certficiates are byte for byte identical (which they should be), then so are
+    // the public keys embedded in them.
     assert_eq!(
-        public_key_from_verification, public_key_from_apk,
-        "Public key extracted directly from apk does not match the public key from verification."
+        cert_from_verification, cert_from_apk,
+        "Certificate extracted directly from apk does not match the certificate from verification."
     );
 }
 
+fn public_key_der_from_cert(cert_der: &[u8]) -> Result<Vec<u8>> {
+    let cert = X509::from_der(cert_der)?;
+    Ok(cert.public_key()?.public_key_to_der()?)
+}
+
 /// Validates that the following apk_digest are equal:
 /// * apk_digest directly extracted from apk without computation
 /// * computed apk_digest
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index e6ddfb9..0cf7013 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -30,17 +30,17 @@
     payload_metadata: &PayloadMetadata,
 ) -> Result<OwnedDiceArtifacts> {
     let subcomponents = build_subcomponent_list(instance_data);
-    let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
+    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();
     code_hash_ctx.update(instance_data.apk_data.root_hash.as_ref());
-    authority_hash_ctx.update(instance_data.apk_data.pubkey.as_ref());
+    authority_hash_ctx.update(instance_data.apk_data.cert_hash.as_ref());
     for extra_apk in &instance_data.extra_apks_data {
         code_hash_ctx.update(extra_apk.root_hash.as_ref());
-        authority_hash_ctx.update(extra_apk.pubkey.as_ref());
+        authority_hash_ctx.update(extra_apk.cert_hash.as_ref());
     }
     for apex in &instance_data.apex_data {
         code_hash_ctx.update(apex.root_digest.as_ref());
@@ -57,42 +57,40 @@
     dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
 }
 
-struct Subcomponent<'a> {
+struct Subcomponent {
     name: String,
     version: u64,
-    code_hash: &'a [u8],
-    authority_hash: Box<[u8]>,
+    code_hash: Vec<u8>,
+    authority_hash: Vec<u8>,
 }
 
-impl<'a> Subcomponent<'a> {
-    fn to_value(&self) -> Result<Value> {
+impl Subcomponent {
+    fn into_value(self) -> Result<Value> {
         Ok(cbor!({
            1 => self.name,
            2 => self.version,
-           3 => self.code_hash,
-           4 => self.authority_hash
+           3 => Value::Bytes(self.code_hash),
+           4 => Value::Bytes(self.authority_hash),
         })?)
     }
 
-    fn for_apk(apk: &'a ApkData) -> Self {
+    fn for_apk(apk: &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)),
+            code_hash: apk.root_hash.clone(),
+            authority_hash: apk.cert_hash.clone(),
         }
     }
 
-    fn for_apex(apex: &'a ApexData) -> Self {
+    fn for_apex(apex: &ApexData) -> Self {
         // Note that this is only reachable if the dice_changes flag is on, in which case
         // the manifest data will always be present.
         Self {
             name: format!("apex:{}", apex.manifest_name.as_ref().unwrap()),
             version: apex.manifest_version.unwrap() as u64,
-            code_hash: &apex.root_digest,
-            authority_hash: Box::new(sha512(&apex.public_key)),
+            code_hash: apex.root_digest.clone(),
+            authority_hash: sha512(&apex.public_key).to_vec(),
         }
     }
 }
@@ -113,7 +111,7 @@
 // of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
-    subcomponents: &[Subcomponent],
+    subcomponents: Vec<Subcomponent>,
 ) -> Result<Vec<u8>> {
     let mut map = Vec::new();
     map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
@@ -129,7 +127,7 @@
 
     if !subcomponents.is_empty() {
         let values =
-            subcomponents.iter().map(Subcomponent::to_value).collect::<Result<Vec<_>>>()?;
+            subcomponents.into_iter().map(Subcomponent::into_value).collect::<Result<Vec<_>>>()?;
         map.push((cbor!(-71002)?, cbor!(values)?));
     }
 
@@ -141,7 +139,7 @@
     use super::*;
     use microdroid_metadata::PayloadConfig;
 
-    const NO_SUBCOMPONENTS: [Subcomponent; 0] = [];
+    const NO_SUBCOMPONENTS: Vec<Subcomponent> = Vec::new();
 
     fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
         assert_eq!(
@@ -157,7 +155,7 @@
     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, &NO_SUBCOMPONENTS)?;
+            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,
@@ -176,7 +174,7 @@
         };
         let payload_metadata = PayloadMetadata::Config(payload_config);
         let config_descriptor =
-            format_payload_config_descriptor(&payload_metadata, &NO_SUBCOMPONENTS)?;
+            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,
@@ -190,31 +188,30 @@
     #[test]
     fn payload_metadata_with_subcomponents_formats_correctly() -> Result<()> {
         let payload_metadata = PayloadMetadata::ConfigPath("/config_path".to_string());
-        let subcomponents = [
+        let subcomponents = vec![
             Subcomponent {
                 name: "apk1".to_string(),
                 version: 1,
-                code_hash: &[42u8],
-                authority_hash: Box::new([17u8]),
+                code_hash: vec![42, 43],
+                authority_hash: vec![17],
             },
             Subcomponent {
                 name: "apk2".to_string(),
                 version: 0x1000_0000_0001,
-                code_hash: &[43u8],
-                authority_hash: Box::new([19u8]),
+                code_hash: vec![43],
+                authority_hash: vec![19, 20],
             },
         ];
-        let config_descriptor =
-            format_payload_config_descriptor(&payload_metadata, &subcomponents)?;
+        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,
+            0x02, 0x01, 0x03, 0x42, 0x2a, 0x2b, 0x04, 0x41, 0x11, 0xa4, 0x01, 0x64, 0x61, 0x70,
+            0x6b, 0x32, 0x02, 0x1b, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x41,
+            0x2b, 0x04, 0x42, 0x13, 0x14,
         ];
         assert_eq_bytes(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
         Ok(())
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index b0fc03d..7a9f0e0 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -287,20 +287,18 @@
 
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct ApkData {
-    pub root_hash: Box<RootHash>,
-    pub pubkey: Box<[u8]>,
+    pub root_hash: Vec<u8>,
+    pub cert_hash: Vec<u8>,
     pub package_name: String,
     pub version_code: u64,
 }
 
 impl ApkData {
     pub fn root_hash_eq(&self, root_hash: &[u8]) -> bool {
-        self.root_hash.as_ref() == root_hash
+        self.root_hash == root_hash
     }
 }
 
-pub type RootHash = [u8];
-
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct ApexData {
     pub name: String,
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
index e63530b..445c1ae 100644
--- a/microdroid_manager/src/verify.rs
+++ b/microdroid_manager/src/verify.rs
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::instance::{ApexData, ApkData, MicrodroidData, RootHash};
+use crate::instance::{ApexData, ApkData, MicrodroidData};
 use crate::payload::{get_apex_data_from_payload, to_metadata};
 use crate::{is_strict_boot, is_verified_boot, MicrodroidError};
 use anyhow::{anyhow, ensure, Context, Result};
 use apkmanifest::get_manifest_info;
-use apkverify::{get_public_key_der, verify, V4Signature};
+use apkverify::{extract_signed_data, verify, V4Signature};
 use glob::glob;
 use itertools::sorted;
 use log::{info, warn};
 use microdroid_metadata::{write_metadata, Metadata};
+use openssl::sha::sha512;
 use rand::Fill;
 use rustutils::system_properties;
 use std::fs::OpenOptions;
@@ -190,10 +191,10 @@
 
 fn get_data_from_apk(
     apk_path: &str,
-    root_hash: Box<RootHash>,
+    root_hash: Box<[u8]>,
     root_hash_trustful: bool,
 ) -> Result<ApkData> {
-    let pubkey = get_public_key_from_apk(apk_path, root_hash_trustful)?;
+    let cert_hash = get_cert_hash_from_apk(apk_path, root_hash_trustful)?.to_vec();
     // 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
@@ -203,8 +204,8 @@
         .unwrap_or_default();
 
     Ok(ApkData {
-        root_hash,
-        pubkey,
+        root_hash: root_hash.into(),
+        cert_hash,
         package_name: manifest_info.package,
         version_code: manifest_info.version_code,
     })
@@ -233,21 +234,22 @@
     Ok(())
 }
 
-fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<[u8]>> {
     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]>> {
+fn get_cert_hash_from_apk(apk: &str, root_hash_trustful: bool) -> Result<[u8; 64]> {
     let current_sdk = get_current_sdk()?;
 
-    if !root_hash_trustful {
+    let signed_data = if !root_hash_trustful {
         verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
             "failed to verify {}",
             apk
         )))
     } else {
-        get_public_key_der(apk, current_sdk)
-    }
+        extract_signed_data(apk, current_sdk)
+    }?;
+    Ok(sha512(signed_data.first_certificate_der()?))
 }
 
 fn get_current_sdk() -> Result<u32> {
@@ -260,7 +262,7 @@
     apk: &'a str,
     idsig: &'a str,
     name: &'a str,
-    saved_root_hash: Option<&'a RootHash>,
+    saved_root_hash: Option<&'a [u8]>,
 }
 
 fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 7023b95..4957df2 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -288,6 +288,6 @@
 
     unsafe fn from_raw_range_mut(ptr: usize, range: NonEmptyRange) -> &'a mut [u8] {
         // SAFETY: The caller must ensure that the range is valid from ptr.
-        unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.end()) }
+        unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.len()) }
     }
 }
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 90008a9..bbb5e54 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -15,12 +15,12 @@
         "libciborium_nostd",
         "libcstr",
         "libdiced_open_dice_nostd",
-        "libdiced_sample_inputs_nostd",
         "libhyp",
         "libfdtpci",
         "liblibfdt",
         "liblog_rust_nostd",
         "libservice_vm_comm_nostd",
+        "libservice_vm_fake_chain_nostd",
         "libservice_vm_requests_nostd",
         "libtinyvec_nostd",
         "libvirtio_drivers",
@@ -113,12 +113,10 @@
         "libciborium",
         "libclient_vm_csr",
         "libcoset",
-        "libcstr",
-        "libdiced_open_dice",
-        "libdiced_sample_inputs",
         "liblibc",
         "liblog_rust",
         "libservice_vm_comm",
+        "libservice_vm_fake_chain",
         "libservice_vm_manager",
         "libvmclient",
         "libx509_parser",
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 0bdc927..1215021 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -38,6 +38,7 @@
 use libfdt::FdtError;
 use log::{debug, error, info};
 use service_vm_comm::{RequestProcessingError, Response, ServiceVmRequest, VmType};
+use service_vm_fake_chain::service_vm;
 use service_vm_requests::process_request;
 use virtio_drivers::{
     device::socket::{VsockAddr, VMADDR_CID_HOST},
@@ -163,9 +164,7 @@
         }
         // Currently, a sample DICE data is used for non-protected VMs, as these VMs only run
         // in tests at the moment.
-        // If we intend to run non-protected rialto in production, we should retrieve real
-        // DICE chain data instead.
-        VmType::NonProtectedVm => Box::new(diced_sample_inputs::make_sample_bcc_and_cdis()?),
+        VmType::NonProtectedVm => Box::new(service_vm::fake_service_vm_dice_artifacts()?),
     };
 
     let pci_info = PciInfo::from_fdt(fdt)?;
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 029895f..02a5a28 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -26,16 +26,14 @@
 use ciborium::value::Value;
 use client_vm_csr::generate_attestation_key_and_csr;
 use coset::{CborSerializable, CoseMac0, CoseSign};
-use cstr::cstr;
-use diced_open_dice::{
-    retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
-    DiceConfigValues, DiceMode, InputValues, OwnedDiceArtifacts, HASH_SIZE, HIDDEN_SIZE,
-};
 use log::info;
 use service_vm_comm::{
     ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
     Request, RequestProcessingError, Response, VmType,
 };
+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;
@@ -45,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},
 };
@@ -53,19 +51,6 @@
 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";
 const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
-/// The following data are generated randomly with urandom.
-const CODE_HASH_MICRODROID: [u8; HASH_SIZE] = [
-    0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
-    0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
-    0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
-    0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
-];
-const AUTHORITY_HASH_MICRODROID: [u8; HASH_SIZE] = [
-    0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
-    0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
-    0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
-    0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
-];
 
 #[test]
 fn process_requests_in_protected_vm() -> Result<()> {
@@ -148,8 +133,7 @@
         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 dice_artifacts = extend_dice_artifacts_with_microdroid_payload(&dice_artifacts)?;
+    let dice_artifacts = fake_client_vm_dice_artifacts()?;
     let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
     let cert_chain = fs::read(TEST_CERT_CHAIN_PATH)?;
     let (remaining, cert) = X509Certificate::from_der(&cert_chain)?;
@@ -193,30 +177,23 @@
     }
 }
 
-fn extend_dice_artifacts_with_microdroid_payload(
-    dice_artifacts: &dyn DiceArtifacts,
-) -> Result<OwnedDiceArtifacts> {
-    let config_values = DiceConfigValues {
-        component_name: Some(cstr!("Microdroid payload")),
-        component_version: Some(1),
-        resettable: true,
-        ..Default::default()
-    };
-    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
-    let input_values = InputValues::new(
-        CODE_HASH_MICRODROID,
-        Config::Descriptor(config_descriptor.as_slice()),
-        AUTHORITY_HASH_MICRODROID,
-        DiceMode::kDiceModeDebug,
-        [0u8; HIDDEN_SIZE], // hidden
-    );
-    retry_bcc_main_flow(
-        dice_artifacts.cdi_attest(),
-        dice_artifacts.cdi_seal(),
-        dice_artifacts.bcc().unwrap(),
-        &input_values,
-    )
-    .context("Failed to run BCC main flow for Microdroid")
+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(
@@ -262,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/comm/src/csr.rs b/service_vm/comm/src/csr.rs
index 2a27f90..f14787f 100644
--- a/service_vm/comm/src/csr.rs
+++ b/service_vm/comm/src/csr.rs
@@ -55,8 +55,8 @@
             return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
         }
         Ok(Self {
-            signed_csr_payload: try_as_bytes(arr.remove(1), "signed_csr_payload")?,
-            dice_cert_chain: try_as_bytes(arr.remove(0), "dice_cert_chain")?,
+            signed_csr_payload: value_to_bytes(arr.remove(1), "signed_csr_payload")?,
+            dice_cert_chain: value_to_bytes(arr.remove(0), "dice_cert_chain")?,
         })
     }
 }
@@ -94,21 +94,28 @@
             return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
         }
         Ok(Self {
-            challenge: try_as_bytes(arr.remove(1), "challenge")?,
-            public_key: try_as_bytes(arr.remove(0), "public_key")?,
+            challenge: value_to_bytes(arr.remove(1), "challenge")?,
+            public_key: value_to_bytes(arr.remove(0), "public_key")?,
         })
     }
 }
 
-/// Reads the provided value `v` as bytes array.
-pub fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
-    if let Value::Bytes(data) = v {
-        Ok(data)
-    } else {
-        let v_type = cbor_value_type(&v);
-        error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
-        Err(CoseError::UnexpectedItem(v_type, "bytes"))
-    }
+/// Converts the provided value `v` to bytes array.
+pub fn value_to_bytes(v: Value, context: &'static str) -> coset::Result<Vec<u8>> {
+    v.into_bytes().map_err(|e| to_unexpected_item_error(&e, "bstr", context))
+}
+
+/// Builds a `CoseError::UnexpectedItem` error when the provided value `v` is not of the expected
+/// type `expected_type` and logs the error message with the provided `context`.
+pub fn to_unexpected_item_error(
+    v: &Value,
+    expected_type: &'static str,
+    context: &'static str,
+) -> CoseError {
+    let v_type = cbor_value_type(v);
+    assert!(v_type != expected_type);
+    error!("The provided value type '{v_type}' is not of type '{expected_type}': {context}");
+    CoseError::UnexpectedItem(v_type, expected_type)
 }
 
 /// Reads the type of the provided value `v`.
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index c9de540..343d48e 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,7 +23,7 @@
 mod message;
 mod vsock;
 
-pub use csr::{cbor_value_type, try_as_bytes, Csr, CsrPayload};
+pub use csr::{cbor_value_type, to_unexpected_item_error, value_to_bytes, Csr, CsrPayload};
 pub use message::{
     ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
     RequestProcessingError, Response, ServiceVmRequest,
diff --git a/service_vm/fake_chain/Android.bp b/service_vm/fake_chain/Android.bp
new file mode 100644
index 0000000..ebc185d
--- /dev/null
+++ b/service_vm/fake_chain/Android.bp
@@ -0,0 +1,56 @@
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libservice_vm_fake_chain_defaults",
+    crate_name: "service_vm_fake_chain",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    visibility: [
+        "//packages/modules/Virtualization/rialto:__subpackages__",
+    ],
+    rustlibs: [
+        "libcstr",
+    ],
+}
+
+rust_library {
+    name: "libservice_vm_fake_chain",
+    defaults: ["libservice_vm_fake_chain_defaults"],
+    features: [
+        "std",
+    ],
+    rustlibs: [
+        "libciborium",
+        "libcoset",
+        "libdiced_open_dice",
+        "liblog_rust",
+    ],
+}
+
+rust_library_rlib {
+    name: "libservice_vm_fake_chain_nostd",
+    defaults: ["libservice_vm_fake_chain_defaults"],
+    rustlibs: [
+        "libciborium_nostd",
+        "libcoset_nostd",
+        "libdiced_open_dice_nostd",
+        "liblog_rust_nostd",
+    ],
+
+}
diff --git a/service_vm/fake_chain/src/client_vm.rs b/service_vm/fake_chain/src/client_vm.rs
new file mode 100644
index 0000000..eb8654b
--- /dev/null
+++ b/service_vm/fake_chain/src/client_vm.rs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Provides functions to build fake DICE artifacts for client VM in tests.
+
+use crate::service_vm;
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::{cbor, value::Value};
+use core::result;
+use coset::CborSerializable;
+use cstr::cstr;
+use diced_open_dice::{
+    retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+    DiceConfigValues, DiceError, DiceMode, InputValues, OwnedDiceArtifacts, Result, HASH_SIZE,
+    HIDDEN_SIZE,
+};
+use log::error;
+
+type CborResult<T> = result::Result<T, ciborium::value::Error>;
+
+/// All the following data are generated with urandom.
+const CODE_HASH_KERNEL: [u8; HASH_SIZE] = [
+    0xc8, 0x54, 0x6c, 0xad, 0x9d, 0xe7, 0x25, 0xc7, 0x2b, 0xed, 0x07, 0xe1, 0xe9, 0x1a, 0xb0, 0xd0,
+    0xa7, 0x7f, 0x43, 0xb9, 0xe4, 0x56, 0x79, 0x0d, 0x7d, 0xd8, 0xc5, 0xdd, 0xad, 0x0d, 0x31, 0x85,
+    0xaf, 0x94, 0x02, 0xd8, 0x9d, 0x70, 0xab, 0xba, 0xac, 0xc7, 0x12, 0x80, 0xec, 0x7b, 0x9b, 0x65,
+    0xec, 0x6b, 0xdd, 0x64, 0x94, 0xd0, 0x9a, 0x3a, 0x09, 0xf2, 0x49, 0xdb, 0x60, 0x3c, 0x50, 0x30,
+];
+const CODE_HASH_PAYLOAD: [u8; HASH_SIZE] = [
+    0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
+    0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
+    0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
+    0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
+];
+const AUTHORITY_HASH_PAYLOAD: [u8; HASH_SIZE] = [
+    0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
+    0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
+    0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
+    0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
+];
+const APK1_CODE_HASH: &[u8] = &[
+    0x41, 0x92, 0x0d, 0xd0, 0xf5, 0x60, 0xe3, 0x69, 0x26, 0x7f, 0xb8, 0xbc, 0x12, 0x3a, 0xd1, 0x95,
+    0x1d, 0xb8, 0x9a, 0x9c, 0x3a, 0x3f, 0x01, 0xbf, 0xa8, 0xd9, 0x6d, 0xe9, 0x90, 0x30, 0x1d, 0x0b,
+];
+const APK1_AUTHORITY_HASH: &[u8] = &[
+    0xe3, 0xd9, 0x1c, 0xf5, 0x6f, 0xee, 0x73, 0x40, 0x3d, 0x95, 0x59, 0x67, 0xea, 0x5d, 0x01, 0xfd,
+    0x25, 0x9d, 0x5c, 0x88, 0x94, 0x3a, 0xc6, 0xd7, 0xa9, 0xdc, 0x4c, 0x60, 0x81, 0xbe, 0x2b, 0x74,
+];
+const APEX1_CODE_HASH: &[u8] = &[
+    0x52, 0x93, 0x2b, 0xb0, 0x8d, 0xec, 0xdf, 0x54, 0x1f, 0x5c, 0x10, 0x9d, 0x17, 0xce, 0x7f, 0xac,
+    0xb0, 0x2b, 0xe2, 0x99, 0x05, 0x7d, 0xa3, 0x9b, 0xa6, 0x3e, 0xf9, 0x99, 0xa2, 0xea, 0xd4, 0xd9,
+];
+const APEX1_AUTHORITY_HASH: &[u8] = &[
+    0xd1, 0xfc, 0x3d, 0x5f, 0xa0, 0x5f, 0x02, 0xd0, 0x83, 0x9b, 0x0e, 0x32, 0xc2, 0x27, 0x09, 0x12,
+    0xcc, 0xfc, 0x42, 0xf6, 0x0d, 0xf4, 0x7d, 0xc8, 0x80, 0x1a, 0x64, 0x25, 0xa7, 0xfa, 0x4a, 0x37,
+];
+
+#[allow(missing_docs)]
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct SubComponent {
+    pub name: String,
+    pub version: u64,
+    pub code_hash: Vec<u8>,
+    pub authority_hash: Vec<u8>,
+}
+
+impl SubComponent {
+    fn to_value(&self) -> CborResult<Value> {
+        Ok(cbor!({
+           1 => self.name,
+           2 => self.version,
+           3 => Value::Bytes(self.code_hash.clone()),
+           4 => Value::Bytes(self.authority_hash.clone()),
+        })?)
+    }
+}
+
+/// Generates fake DICE artifacts for client VM with a DICE chain up to the certificate
+/// describing the Microdroid payload.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate -> Microdroid kernel certificate
+/// -> Microdroid payload certificate
+pub fn fake_client_vm_dice_artifacts() -> Result<OwnedDiceArtifacts> {
+    // Client VM DICE chain has the same prefix as the service VM DICE chain up to
+    // the pvmfw entry.
+    let (cdi_values, dice_chain) = service_vm::fake_dice_artifacts_up_to_pvmfw()?;
+
+    // Adds an entry describing the Microdroid kernel.
+    let config_values = DiceConfigValues {
+        component_name: Some(cstr!("vm_entry")),
+        component_version: Some(12),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    // The Microdroid kernel is signed with the same key as the one used for the service VM,
+    // so the authority hash is the same.
+    let authority_hash = service_vm::AUTHORITY_HASH_SERVICE_VM;
+    let input_values = InputValues::new(
+        CODE_HASH_KERNEL,
+        Config::Descriptor(config_descriptor.as_slice()),
+        authority_hash,
+        DiceMode::kDiceModeDebug,
+        [0; HIDDEN_SIZE], // No hidden.
+    );
+    let dice_artifacts = retry_bcc_main_flow(
+        &cdi_values.cdi_attest,
+        &cdi_values.cdi_seal,
+        &dice_chain,
+        &input_values,
+    )
+    .map_err(|e| {
+        error!("Failed to run the Microdroid kernel BCC main flow: {e}");
+        e
+    })?;
+
+    // Adds an entry describing the Microdroid payload.
+    let config_descriptor = fake_microdroid_payload_config_descriptor().map_err(|e| {
+        error!("Failed to generate config descriptor for Microdroid: {e}");
+        DiceError::InvalidInput
+    })?;
+    let input_values = InputValues::new(
+        CODE_HASH_PAYLOAD,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_PAYLOAD,
+        DiceMode::kDiceModeDebug,
+        [0u8; HIDDEN_SIZE], // hidden
+    );
+    retry_bcc_main_flow(
+        dice_artifacts.cdi_attest(),
+        dice_artifacts.cdi_seal(),
+        dice_artifacts.bcc().unwrap(),
+        &input_values,
+    )
+    .map_err(|e| {
+        error!("Failed to run the Microdroid payload BCC main flow: {e}");
+        e
+    })
+}
+
+fn fake_microdroid_payload_config_descriptor() -> CborResult<Vec<u8>> {
+    let mut map = Vec::new();
+    map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
+    map.push((cbor!(-71000)?, cbor!("/config_path")?));
+    let components =
+        fake_sub_components().iter().map(|c| c.to_value()).collect::<CborResult<_>>()?;
+    map.push((cbor!(-71002)?, Value::Array(components)));
+    Ok(Value::Map(map).to_vec().unwrap())
+}
+
+/// Generates a list of fake subcomponents as the Microdroid payload.
+pub fn fake_sub_components() -> Vec<SubComponent> {
+    vec![
+        SubComponent {
+            name: "apk:com.android.apk.apk1".to_string(),
+            version: 1,
+            code_hash: APK1_CODE_HASH.to_vec(),
+            authority_hash: APK1_AUTHORITY_HASH.to_vec(),
+        },
+        SubComponent {
+            name: "apex:com.android.apex.apex1".to_string(),
+            version: 1,
+            code_hash: APEX1_CODE_HASH.to_vec(),
+            authority_hash: APEX1_AUTHORITY_HASH.to_vec(),
+        },
+    ]
+}
diff --git a/service_vm/fake_chain/src/lib.rs b/service_vm/fake_chain/src/lib.rs
new file mode 100644
index 0000000..a5ab828
--- /dev/null
+++ b/service_vm/fake_chain/src/lib.rs
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Provides functions to build a test chain for non-protected rialto and tests.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+
+// `client_vm` builds DICE artifacts related to Microdroid, which is not relevant
+// to the nostd build used in rialto.
+#[cfg(feature = "std")]
+pub mod client_vm;
+pub mod service_vm;
diff --git a/service_vm/fake_chain/src/service_vm.rs b/service_vm/fake_chain/src/service_vm.rs
new file mode 100644
index 0000000..9bd831d
--- /dev/null
+++ b/service_vm/fake_chain/src/service_vm.rs
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Provides functions to build fake DICE artifacts for non-protected rialto used in
+//! end-to-end tests.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::value::Value;
+use coset::{
+    iana::{self, EnumI64},
+    Algorithm, AsCborValue, CborSerializable, CoseKey, KeyOperation, KeyType, Label,
+};
+use cstr::cstr;
+use diced_open_dice::{
+    derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor,
+    retry_bcc_main_flow, retry_dice_main_flow, CdiValues, Config, DiceConfigValues, DiceError,
+    DiceMode, InputValues, OwnedDiceArtifacts, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
+};
+use log::error;
+
+/// All the following data are generated with urandom.
+const UDS: [u8; CDI_SIZE] = [
+    0x1d, 0xa5, 0xea, 0x90, 0x47, 0xfc, 0xb5, 0xf6, 0x47, 0x12, 0xd3, 0x65, 0x9c, 0xf2, 0x00, 0xe0,
+    0x06, 0xf7, 0xe8, 0x9e, 0x2f, 0xd0, 0x94, 0x7f, 0xc9, 0x9a, 0x9d, 0x40, 0xf7, 0xce, 0x13, 0x21,
+];
+const CODE_HASH_PVMFW: [u8; HASH_SIZE] = [
+    0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26,
+    0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25,
+    0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb,
+    0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14,
+];
+const AUTHORITY_HASH_PVMFW: [u8; HASH_SIZE] = [
+    0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7,
+    0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf,
+    0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd,
+    0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d,
+];
+const HIDDEN_PVMFW: [u8; HIDDEN_SIZE] = [
+    0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, 0x5f, 0x1f,
+    0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, 0xc8, 0x07, 0x97, 0x4d,
+    0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e,
+    0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e,
+];
+const CODE_HASH_SERVICE_VM: [u8; HASH_SIZE] = [
+    0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d,
+    0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa,
+    0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f,
+    0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7,
+];
+pub(crate) const AUTHORITY_HASH_SERVICE_VM: [u8; HASH_SIZE] = [
+    0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa,
+    0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43,
+    0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab,
+    0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98,
+];
+const HIDDEN_SERVICE_VM: [u8; HIDDEN_SIZE] = [
+    0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, 0x21, 0x09,
+    0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, 0x7d, 0x7e, 0xf5, 0x8e,
+    0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a,
+    0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a,
+];
+
+fn ed25519_public_key_to_cbor_value(public_key: &[u8]) -> Result<Value> {
+    let key = CoseKey {
+        kty: KeyType::Assigned(iana::KeyType::OKP),
+        alg: Some(Algorithm::Assigned(iana::Algorithm::EdDSA)),
+        key_ops: vec![KeyOperation::Assigned(iana::KeyOperation::Verify)].into_iter().collect(),
+        params: vec![
+            (
+                Label::Int(iana::Ec2KeyParameter::Crv.to_i64()),
+                iana::EllipticCurve::Ed25519.to_i64().into(),
+            ),
+            (Label::Int(iana::Ec2KeyParameter::X.to_i64()), Value::Bytes(public_key.to_vec())),
+        ],
+        ..Default::default()
+    };
+    key.to_cbor_value().map_err(|e| {
+        error!("Failed to serialize the key to CBOR data: {e}");
+        DiceError::InvalidInput
+    })
+}
+
+/// Generates a fake DICE artifacts with a DICE chain up to the certificate describing pvmfw.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate
+pub(crate) fn fake_dice_artifacts_up_to_pvmfw() -> Result<(CdiValues, Vec<u8>)> {
+    let private_key_seed = derive_cdi_private_key_seed(&UDS).map_err(|e| {
+        error!("Failed to derive private key seed: {e}");
+        e
+    })?;
+
+    // Gets the root public key in DICE chain.
+    let (public_key, _) = keypair_from_seed(private_key_seed.as_array()).map_err(|e| {
+        error!("Failed to generate key pair: {e}");
+        e
+    })?;
+    let ed25519_public_key_value = ed25519_public_key_to_cbor_value(&public_key)?;
+
+    // Gets the pvmfw certificate to as the root certificate of DICE chain.
+    let config_values = DiceConfigValues {
+        component_name: Some(cstr!("Protected VM firmware")),
+        component_version: Some(1),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        CODE_HASH_PVMFW,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_PVMFW,
+        DiceMode::kDiceModeDebug,
+        HIDDEN_PVMFW,
+    );
+    let (cdi_values, cert) = retry_dice_main_flow(&UDS, &UDS, &input_values).map_err(|e| {
+        error!("Failed to run first main flow: {e}");
+        e
+    })?;
+    let dice_chain = Value::Array(vec![
+        ed25519_public_key_value,
+        Value::from_slice(&cert).map_err(|e| {
+            error!("Deserialize root DICE certificate failed: {e}");
+            DiceError::InvalidInput
+        })?,
+    ]);
+    let dice_chain = dice_chain.to_vec().map_err(|e| {
+        error!("Failed to serialize the DICE chain to CBOR data: {e}");
+        DiceError::InvalidInput
+    })?;
+    Ok((cdi_values, dice_chain))
+}
+
+/// Generates fake DICE artifacts for service VM with a DICE chain up to the certificate
+/// describing service VM.
+///
+/// The fake DICE chain has the following nodes:
+/// Root public key -> pvmfw certificate -> service VM certificate
+///
+/// The fake DICE chain is solely used in non-protected rialto for testing
+/// purposes.
+pub fn fake_service_vm_dice_artifacts() -> Result<OwnedDiceArtifacts> {
+    let (cdi_values, dice_chain) = fake_dice_artifacts_up_to_pvmfw()?;
+    let config_values = DiceConfigValues {
+        component_name: Some(cstr!("vm_entry")),
+        component_version: Some(12),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        CODE_HASH_SERVICE_VM,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_SERVICE_VM,
+        DiceMode::kDiceModeDebug,
+        HIDDEN_SERVICE_VM,
+    );
+    retry_bcc_main_flow(&cdi_values.cdi_attest, &cdi_values.cdi_seal, &dice_chain, &input_values)
+        .map_err(|e| {
+            error!("Failed to run the service VM BCC main flow: {e}");
+            e
+        })
+}
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(&params.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 c220af6..15cfbc9 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -14,8 +14,9 @@
 
 //! This module contains functions related to DICE.
 
+use alloc::string::String;
 use alloc::vec::Vec;
-use ciborium::value::Value;
+use ciborium::value::{Integer, Value};
 use core::cell::OnceCell;
 use core::result;
 use coset::{
@@ -23,7 +24,9 @@
 };
 use diced_open_dice::{DiceMode, HASH_SIZE};
 use log::error;
-use service_vm_comm::{cbor_value_type, try_as_bytes, RequestProcessingError};
+use service_vm_comm::{
+    cbor_value_type, to_unexpected_item_error, value_to_bytes, RequestProcessingError,
+};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
@@ -33,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:
 ///
@@ -83,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.
@@ -101,9 +151,9 @@
     service_vm_dice_chain: &[u8],
 ) -> Result<Vec<Value>> {
     let client_vm_dice_chain =
-        try_as_value_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
+        value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
     let service_vm_dice_chain =
-        try_as_value_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+        value_to_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
     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:
@@ -161,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 {
@@ -180,10 +228,7 @@
             error!("No payload found in the DICE chain entry");
             RequestProcessingError::InvalidDiceChain
         })?;
-        let payload = Value::from_slice(&payload)?;
-        let Value::Map(entries) = payload else {
-            return Err(CoseError::UnexpectedItem(cbor_value_type(&payload), "map").into());
-        };
+        let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
         build_payload(entries)
     }
 }
@@ -191,41 +236,191 @@
 fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
     let mut builder = PayloadBuilder::default();
     for (key, value) in entries.into_iter() {
-        let Some(Ok(key)) = key.as_integer().map(i64::try_from) else {
-            error!("Invalid key found in the DICE chain entry: {:?}", key);
-            return Err(RequestProcessingError::InvalidDiceChain);
-        };
+        let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
         match key {
             SUBJECT_PUBLIC_KEY => {
-                let subject_public_key = try_as_bytes(value, "subject_public_key")?;
+                let subject_public_key = value_to_bytes(value, "subject_public_key")?;
                 let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
                 builder.subject_public_key(subject_public_key)?;
             }
             MODE => builder.mode(to_mode(value)?)?,
-            CODE_HASH => builder.code_hash(try_as_byte_array(value, "code_hash")?)?,
-            AUTHORITY_HASH => {
-                builder.authority_hash(try_as_byte_array(value, "authority_hash")?)?
+            CODE_HASH => {
+                let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+                builder.code_hash(code_hash)?;
             }
-            CONFIG_DESC => builder.config_descriptor(try_as_bytes(value, "config_descriptor")?)?,
+            AUTHORITY_HASH => {
+                let authority_hash =
+                    value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+                builder.authority_hash(authority_hash)?;
+            }
+            CONFIG_DESC => {
+                let config_descriptor = value_to_bytes(value, "config_descriptor")?;
+                let config_descriptor = ConfigDescriptor::from_slice(&config_descriptor)?;
+                builder.config_descriptor(config_descriptor)?;
+            }
             _ => {}
         }
     }
     builder.build()
 }
 
-fn try_as_value_array(v: Value, context: &str) -> coset::Result<Vec<Value>> {
-    if let Value::Array(data) = v {
-        Ok(data)
-    } else {
-        let v_type = cbor_value_type(&v);
-        error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
-        Err(CoseError::UnexpectedItem(v_type, "array"))
+/// 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()
     }
 }
 
-fn try_as_byte_array<const N: usize>(v: Value, context: &str) -> Result<[u8; N]> {
-    let data = try_as_bytes(v, context)?;
-    data.try_into().map_err(|e| {
+#[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))
+}
+
+fn value_to_num<T: TryFrom<Integer>>(v: Value, context: &'static str) -> Result<T> {
+    let num = v.into_integer().map_err(|e| to_unexpected_item_error(&e, "int", context))?;
+    num.try_into().map_err(|_| {
+        error!("The provided value '{num:?}' is not a valid number: {context}");
+        RequestProcessingError::InvalidDiceChain
+    })
+}
+
+fn value_to_byte_array<const N: usize>(v: Value, context: &'static str) -> Result<[u8; N]> {
+    value_to_bytes(v, context)?.try_into().map_err(|e| {
         error!("The provided value '{context}' is not an array of length {N}: {e:?}");
         RequestProcessingError::InternalError
     })
@@ -264,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<()> {
@@ -298,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")
     }