Merge "Update Android source for Rust 1.74.1" into main
diff --git a/Android.bp b/Android.bp
index 9c17c7f..54919d4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -61,5 +61,5 @@
 genrule_defaults {
     name: "dts_to_dtb",
     tools: ["dtc"],
-    cmd: "$(location dtc) -I dts -O dtb $(in) -o $(out)",
+    cmd: "FILES=($(in)) && $(location dtc) -I dts -O dtb $${FILES[-1]} -o $(out)",
 }
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index c4c90cd..0cb8ca1 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -15,6 +15,7 @@
         "libbitflags",
         "libclap",
         "libdm_rust",
+        "libhex",
         "libitertools",
         "liblibc",
         "libnix",
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index d9e9e2b..0ecb0ea 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -46,7 +46,7 @@
 
     for (apk, idsig, name, roothash) in apks.tuples() {
         let roothash = if roothash != "none" {
-            Some(util::parse_hexstring(roothash).expect("failed to parse roothash"))
+            Some(hex::decode(roothash).expect("failed to parse roothash"))
         } else {
             None
         };
@@ -108,8 +108,10 @@
             bail!("The size of {:?} is not multiple of {}.", &apk, BLOCK_SIZE)
         }
         (
-            loopdevice::attach(&apk, 0, apk_size, /*direct_io*/ true, /*writable*/ false)
-                .context("Failed to attach APK to a loop device")?,
+            loopdevice::attach(
+                &apk, 0, apk_size, /* direct_io */ true, /* writable */ false,
+            )
+            .context("Failed to attach APK to a loop device")?,
             apk_size,
         )
     };
@@ -123,9 +125,10 @@
     // Due to unknown reason(b/191344832), we can't enable "direct IO" for the IDSIG file (backing
     // the hash). For now we don't use "direct IO" but it seems OK since the IDSIG file is very
     // small and the benefit of direct-IO would be negliable.
-    let hash_device =
-        loopdevice::attach(&idsig, offset, size, /*direct_io*/ false, /*writable*/ false)
-            .context("Failed to attach idsig to a loop device")?;
+    let hash_device = loopdevice::attach(
+        &idsig, offset, size, /* direct_io */ false, /* writable */ false,
+    )
+    .context("Failed to attach idsig to a loop device")?;
 
     // Build a dm-verity target spec from the information from the idsig file. The apk and the
     // idsig files are used as the data device and the hash device, respectively.
@@ -338,7 +341,7 @@
         // of the data device is done in the scopeguard for the return value of `enable_verity`
         // below. Only the idsig_loop_device needs detatching.
         let apk_loop_device = loopdevice::attach(
-            &apk_path, 0, apk_size, /*direct_io*/ true, /*writable*/ false,
+            &apk_path, 0, apk_size, /* direct_io */ true, /* writable */ false,
         )
         .unwrap();
         let idsig_loop_device = scopeguard::guard(
@@ -346,8 +349,8 @@
                 &idsig_path,
                 0,
                 idsig_size,
-                /*direct_io*/ false,
-                /*writable*/ false,
+                /* direct_io */ false,
+                /* writable */ false,
             )
             .unwrap(),
             |dev| loopdevice::detach(dev).unwrap(),
diff --git a/authfs/Android.bp b/authfs/Android.bp
index a4151c2..8ac600d 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -19,6 +19,7 @@
         "libclap",
         "libfsverity_digests_proto_rust",
         "libfuse_rust",
+        "libhex",
         "liblibc",
         "liblog_rust",
         "libnix",
diff --git a/authfs/src/fsverity/builder.rs b/authfs/src/fsverity/builder.rs
index 8585fdf..6d724ca 100644
--- a/authfs/src/fsverity/builder.rs
+++ b/authfs/src/fsverity/builder.rs
@@ -159,7 +159,7 @@
     #[test]
     fn merkle_tree_empty_file() -> Result<()> {
         assert_eq!(
-            to_u8_vec("3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95"),
+            hex::decode("3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95")?,
             generate_fsverity_digest_sequentially(&Vec::new())?
         );
         Ok(())
@@ -169,7 +169,7 @@
     fn merkle_tree_file_size_less_than_or_equal_to_4k() -> Result<()> {
         // Test a file that contains 4096 '\01's.
         assert_eq!(
-            to_u8_vec("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af"),
+            hex::decode("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af")?,
             generate_fsverity_digest_sequentially(&vec![1; 4096])?
         );
         Ok(())
@@ -180,24 +180,24 @@
         // Test files that contains >4096 '\01's.
 
         assert_eq!(
-            to_u8_vec("2901b849fda2d91e3929524561c4a47e77bb64734319759507b2029f18b9cc52"),
+            hex::decode("2901b849fda2d91e3929524561c4a47e77bb64734319759507b2029f18b9cc52")?,
             generate_fsverity_digest_sequentially(&vec![1; 4097])?
         );
 
         assert_eq!(
-            to_u8_vec("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d"),
+            hex::decode("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d")?,
             generate_fsverity_digest_sequentially(&vec![1; 8192])?
         );
 
         // Test with max size that still fits in 2 levels.
         assert_eq!(
-            to_u8_vec("26b7c190a34e19f420808ee7ec233b09fa6c34543b5a9d2950530114c205d14f"),
+            hex::decode("26b7c190a34e19f420808ee7ec233b09fa6c34543b5a9d2950530114c205d14f")?,
             generate_fsverity_digest_sequentially(&vec![1; 524288])?
         );
 
         // Test with data that requires 3 levels.
         assert_eq!(
-            to_u8_vec("316835d9be1c95b5cd55d07ae7965d651689efad186e26cbf680e40b683a3262"),
+            hex::decode("316835d9be1c95b5cd55d07ae7965d651689efad186e26cbf680e40b683a3262")?,
             generate_fsverity_digest_sequentially(&vec![1; 524289])?
         );
         Ok(())
@@ -215,7 +215,7 @@
         tree.update_hash(2, &hash, CHUNK_SIZE * 3);
 
         assert_eq!(
-            to_u8_vec("7d3c0d2e1dc54230b20ed875f5f3a4bd3f9873df601936b3ca8127d4db3548f3"),
+            hex::decode("7d3c0d2e1dc54230b20ed875f5f3a4bd3f9873df601936b3ca8127d4db3548f3")?,
             tree.calculate_fsverity_digest()?
         );
         Ok(())
@@ -268,12 +268,4 @@
         }
         Ok(tree.calculate_fsverity_digest()?)
     }
-
-    fn to_u8_vec(hex_str: &str) -> Vec<u8> {
-        assert!(hex_str.len() % 2 == 0);
-        (0..hex_str.len())
-            .step_by(2)
-            .map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16).unwrap())
-            .collect()
-    }
 }
diff --git a/authfs/src/fsverity/editor.rs b/authfs/src/fsverity/editor.rs
index 4af6e80..c84500b 100644
--- a/authfs/src/fsverity/editor.rs
+++ b/authfs/src/fsverity/editor.rs
@@ -373,7 +373,7 @@
         let file = VerifiedFileEditor::new(InMemoryEditor::new());
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95")
+            hex::decode("3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95")?
                 .as_slice()
         );
         Ok(())
@@ -386,7 +386,7 @@
         assert_eq!(file.write_at(&[1; 4096], 0)?, 4096);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af")
+            hex::decode("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af")?
                 .as_slice()
         );
 
@@ -395,7 +395,7 @@
         assert_eq!(file.write_at(&[1; 4097], 0)?, 4097);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("2901b849fda2d91e3929524561c4a47e77bb64734319759507b2029f18b9cc52")
+            hex::decode("2901b849fda2d91e3929524561c4a47e77bb64734319759507b2029f18b9cc52")?
                 .as_slice()
         );
 
@@ -404,7 +404,7 @@
         assert_eq!(file.write_at(&[1; 10000], 0)?, 10000);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("7545409b556071554d18973a29b96409588c7cda4edd00d5586b27a11e1a523b")
+            hex::decode("7545409b556071554d18973a29b96409588c7cda4edd00d5586b27a11e1a523b")?
                 .as_slice()
         );
         Ok(())
@@ -417,7 +417,7 @@
         assert_eq!(file.write_at(&[1; 5], 3)?, 5);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("a23fc5130d3d7b3323fc4b4a5e79d5d3e9ddf3a3f5872639e867713512c6702f")
+            hex::decode("a23fc5130d3d7b3323fc4b4a5e79d5d3e9ddf3a3f5872639e867713512c6702f")?
                 .as_slice()
         );
 
@@ -426,7 +426,7 @@
         assert_eq!(file.write_at(&[1; 6000], 4000)?, 6000);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("d16d4c1c186d757e646f76208b21254f50d7f07ea07b1505ff48b2a6f603f989")
+            hex::decode("d16d4c1c186d757e646f76208b21254f50d7f07ea07b1505ff48b2a6f603f989")?
                 .as_slice()
         );
         Ok(())
@@ -439,7 +439,7 @@
         assert_eq!(file.write_at(&[1; 4096], 4096)?, 4096);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("4df2aefd8c2a9101d1d8770dca3ede418232eabce766bb8e020395eae2e97103")
+            hex::decode("4df2aefd8c2a9101d1d8770dca3ede418232eabce766bb8e020395eae2e97103")?
                 .as_slice()
         );
 
@@ -448,7 +448,7 @@
         assert_eq!(file.write_at(&[1; 5000], 6000)?, 5000);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("47d5da26f6934484e260630a69eb2eebb21b48f69bc8fbf8486d1694b7dba94f")
+            hex::decode("47d5da26f6934484e260630a69eb2eebb21b48f69bc8fbf8486d1694b7dba94f")?
                 .as_slice()
         );
 
@@ -457,7 +457,7 @@
         assert_eq!(file.write_at(&[1; 5], 16381)?, 5);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("8bd118821fb4aff26bb4b51d485cc481a093c68131b7f4f112e9546198449752")
+            hex::decode("8bd118821fb4aff26bb4b51d485cc481a093c68131b7f4f112e9546198449752")?
                 .as_slice()
         );
         Ok(())
@@ -470,34 +470,34 @@
         assert_eq!(file.write_at(&[1; 2048], 4096 + 2048)?, 2048);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("4c433d8640c888b629dc673d318cbb8d93b1eebcc784d9353e07f09f0dcfe707")
+            hex::decode("4c433d8640c888b629dc673d318cbb8d93b1eebcc784d9353e07f09f0dcfe707")?
                 .as_slice()
         );
         assert_eq!(file.write_at(&[1; 2048], 2048)?, 2048);
         assert_eq!(file.write_at(&[1; 2048], 4096)?, 2048);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d")
+            hex::decode("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d")?
                 .as_slice()
         );
         assert_eq!(file.write_at(&[0; 2048], 2048)?, 2048);
         assert_eq!(file.write_at(&[0; 2048], 4096)?, 2048);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("4c433d8640c888b629dc673d318cbb8d93b1eebcc784d9353e07f09f0dcfe707")
+            hex::decode("4c433d8640c888b629dc673d318cbb8d93b1eebcc784d9353e07f09f0dcfe707")?
                 .as_slice()
         );
         assert_eq!(file.write_at(&[1; 4096], 2048)?, 4096);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d")
+            hex::decode("2a476d58eb80394052a3a783111e1458ac3ecf68a7878183fed86ca0ff47ec0d")?
                 .as_slice()
         );
         assert_eq!(file.write_at(&[1; 2048], 8192)?, 2048);
         assert_eq!(file.write_at(&[1; 2048], 8192 + 2048)?, 2048);
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("23cbac08371e6ee838ebcc7ae6512b939d2226e802337be7b383c3e046047d24")
+            hex::decode("23cbac08371e6ee838ebcc7ae6512b939d2226e802337be7b383c3e046047d24")?
                 .as_slice()
         );
         Ok(())
@@ -555,7 +555,7 @@
 
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("fef1b4f19bb7a2cd944d7cdee44d1accb12726389ca5b0f61ac0f548ae40876f")
+            hex::decode("fef1b4f19bb7a2cd944d7cdee44d1accb12726389ca5b0f61ac0f548ae40876f")?
                 .as_slice()
         );
         Ok(())
@@ -572,7 +572,7 @@
 
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("9e0e2745c21e4e74065240936d2047340d96a466680c3c9d177b82433e7a0bb1")
+            hex::decode("9e0e2745c21e4e74065240936d2047340d96a466680c3c9d177b82433e7a0bb1")?
                 .as_slice()
         );
         Ok(())
@@ -589,7 +589,7 @@
 
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("fef1b4f19bb7a2cd944d7cdee44d1accb12726389ca5b0f61ac0f548ae40876f")
+            hex::decode("fef1b4f19bb7a2cd944d7cdee44d1accb12726389ca5b0f61ac0f548ae40876f")?
                 .as_slice()
         );
         Ok(())
@@ -621,17 +621,9 @@
 
         assert_eq!(
             file.calculate_fsverity_digest()?,
-            to_u8_vec("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af")
+            hex::decode("cd0875ca59c7d37e962c5e8f5acd3770750ac80225e2df652ce5672fd34500af")?
                 .as_slice()
         );
         Ok(())
     }
-
-    fn to_u8_vec(hex_str: &str) -> Vec<u8> {
-        assert!(hex_str.len() % 2 == 0);
-        (0..hex_str.len())
-            .step_by(2)
-            .map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16).unwrap())
-            .collect()
-    }
 }
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 9ff0ae3..e14b771 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -169,21 +169,6 @@
     })
 }
 
-fn from_hex_string(s: &str) -> Result<Vec<u8>> {
-    if s.len() % 2 == 1 {
-        bail!("Incomplete hex string: {}", s);
-    } else {
-        let results = (0..s.len())
-            .step_by(2)
-            .map(|i| {
-                u8::from_str_radix(&s[i..i + 2], 16)
-                    .map_err(|e| anyhow!("Cannot parse hex {}: {}", &s[i..i + 2], e))
-            })
-            .collect::<Result<Vec<_>>>();
-        Ok(results?)
-    }
-}
-
 fn new_remote_verified_file_entry(
     service: file::VirtFdService,
     remote_fd: i32,
@@ -193,7 +178,7 @@
         reader: LazyVerifiedReadonlyFile::prepare_by_fd(
             service,
             remote_fd,
-            from_hex_string(expected_digest)?,
+            hex::decode(expected_digest)?,
         ),
     })
 }
@@ -332,18 +317,3 @@
         std::process::exit(1);
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn parse_hex_string() {
-        assert_eq!(from_hex_string("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
-        assert_eq!(from_hex_string("DEADBEEF").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
-        assert_eq!(from_hex_string("").unwrap(), Vec::<u8>::new());
-
-        assert!(from_hex_string("deadbee").is_err());
-        assert!(from_hex_string("X").is_err());
-    }
-}
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/libs/bssl/Android.bp b/libs/bssl/Android.bp
index e1f4ffd..bed3dfb 100644
--- a/libs/bssl/Android.bp
+++ b/libs/bssl/Android.bp
@@ -23,6 +23,7 @@
     rustlibs: [
         "libbssl_avf_error_nostd",
         "libbssl_ffi_nostd",
+        "libcbor_util_nostd",
         "libciborium_nostd",
         "libcoset_nostd",
         "liblog_rust_nostd",
diff --git a/libs/bssl/error/Android.bp b/libs/bssl/error/Android.bp
index dc2902e..000e385 100644
--- a/libs/bssl/error/Android.bp
+++ b/libs/bssl/error/Android.bp
@@ -21,6 +21,8 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
+        "libcoset_nostd",
+        "liblog_rust_nostd",
         "libserde_nostd",
     ],
 }
@@ -32,6 +34,8 @@
         "std",
     ],
     rustlibs: [
+        "libcoset",
+        "liblog_rust",
         "libserde",
     ],
 }
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index c0dca2e..7f01c6c 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -38,6 +38,9 @@
     /// Failed to decode the COSE_Key.
     CoseKeyDecodingFailed,
 
+    /// An error occurred when interacting with the coset crate.
+    CosetError,
+
     /// Unimplemented operation.
     Unimplemented,
 }
@@ -50,11 +53,21 @@
             }
             Self::InternalError => write!(f, "An unexpected internal error occurred"),
             Self::CoseKeyDecodingFailed => write!(f, "Failed to decode the COSE_Key"),
+            Self::CosetError => {
+                write!(f, "An error occurred when interacting with the coset crate")
+            }
             Self::Unimplemented => write!(f, "Unimplemented operation"),
         }
     }
 }
 
+impl From<coset::CoseError> for Error {
+    fn from(e: coset::CoseError) -> Self {
+        log::error!("Coset error: {e}");
+        Self::CosetError
+    }
+}
+
 /// BoringSSL API names.
 #[allow(missing_docs)]
 #[allow(non_camel_case_types)]
@@ -81,9 +94,14 @@
     EVP_AEAD_CTX_new,
     EVP_AEAD_CTX_open,
     EVP_AEAD_CTX_seal,
+    EVP_Digest,
+    EVP_MD_CTX_new,
     EVP_PKEY_new,
+    EVP_PKEY_new_raw_public_key,
     EVP_PKEY_set1_EC_KEY,
     EVP_marshal_public_key,
+    EVP_DigestVerify,
+    EVP_DigestVerifyInit,
     HKDF,
     HMAC,
     RAND_bytes,
diff --git a/libs/bssl/src/digest.rs b/libs/bssl/src/digest.rs
index 49e66e6..e986a38 100644
--- a/libs/bssl/src/digest.rs
+++ b/libs/bssl/src/digest.rs
@@ -14,7 +14,18 @@
 
 //! Wrappers of the digest functions in BoringSSL digest.h.
 
-use bssl_ffi::{EVP_MD_size, EVP_sha256, EVP_sha512, EVP_MD};
+use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+    EVP_Digest, EVP_MD_CTX_free, EVP_MD_CTX_new, EVP_MD_size, EVP_sha256, EVP_sha384, EVP_sha512,
+    EVP_MAX_MD_SIZE, EVP_MD, EVP_MD_CTX,
+};
+use core::ptr::{self, NonNull};
+use log::error;
+
+const MAX_DIGEST_SIZE: usize = EVP_MAX_MD_SIZE as usize;
 
 /// Message digester wrapping `EVP_MD`.
 #[derive(Clone, Debug)]
@@ -28,7 +39,17 @@
         let p = unsafe { EVP_sha256() };
         // SAFETY: The returned pointer should always be valid and points to a static
         // `EVP_MD`.
-        Self(unsafe { &*p })
+        Self(unsafe { p.as_ref().unwrap() })
+    }
+
+    /// Returns a `Digester` implementing `SHA-384` algorithm.
+    pub fn sha384() -> Self {
+        // SAFETY: This function does not access any Rust variables and simply returns
+        // a pointer to the static variable in BoringSSL.
+        let p = unsafe { EVP_sha384() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_MD`.
+        Self(unsafe { p.as_ref().unwrap() })
     }
 
     /// Returns a `Digester` implementing `SHA-512` algorithm.
@@ -38,7 +59,7 @@
         let p = unsafe { EVP_sha512() };
         // SAFETY: The returned pointer should always be valid and points to a static
         // `EVP_MD`.
-        Self(unsafe { &*p })
+        Self(unsafe { p.as_ref().unwrap() })
     }
 
     /// Returns the digest size in bytes.
@@ -46,4 +67,64 @@
         // SAFETY: The inner pointer is fetched from EVP_* hash functions in BoringSSL digest.h
         unsafe { EVP_MD_size(self.0) }
     }
+
+    /// Computes the digest of the provided `data`.
+    pub fn digest(&self, data: &[u8]) -> Result<Vec<u8>> {
+        let mut out = vec![0u8; MAX_DIGEST_SIZE];
+        let mut out_size = 0;
+        let engine = ptr::null_mut(); // Use the default engine.
+        let ret =
+            // SAFETY: This function reads `data` and writes to `out` within its bounds.
+            // `out` has `MAX_DIGEST_SIZE` bytes of space for write as required in the
+            // BoringSSL spec.
+            // The digester is a valid pointer to a static `EVP_MD` as it is returned by
+            // BoringSSL API during the construction of this struct.
+            unsafe {
+                EVP_Digest(
+                    data.as_ptr() as *const _,
+                    data.len(),
+                    out.as_mut_ptr(),
+                    &mut out_size,
+                    self.0,
+                    engine,
+                )
+            };
+        check_int_result(ret, ApiName::EVP_Digest)?;
+        let out_size = usize::try_from(out_size).map_err(|e| {
+            error!("Failed to convert digest size to usize: {:?}", e);
+            Error::InternalError
+        })?;
+        if self.size() != out_size {
+            return Err(to_call_failed_error(ApiName::EVP_Digest));
+        }
+        out.truncate(out_size);
+        Ok(out)
+    }
+}
+
+/// Message digester context wrapping `EVP_MD_CTX`.
+#[derive(Clone, Debug)]
+pub struct DigesterContext(NonNull<EVP_MD_CTX>);
+
+impl Drop for DigesterContext {
+    fn drop(&mut self) {
+        // SAFETY: This function frees any resources owned by `EVP_MD_CTX` and resets it to a
+        // freshly initialised state and then frees the context.
+        // It is safe because `EVP_MD_CTX` has been allocated by BoringSSL and isn't used after
+        // this.
+        unsafe { EVP_MD_CTX_free(self.0.as_ptr()) }
+    }
+}
+
+impl DigesterContext {
+    /// Creates a new `DigesterContext` wrapping a freshly allocated and initialised `EVP_MD_CTX`.
+    pub fn new() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let ctx = unsafe { EVP_MD_CTX_new() };
+        NonNull::new(ctx).map(Self).ok_or(to_call_failed_error(ApiName::EVP_MD_CTX_new))
+    }
+
+    pub(crate) fn as_mut_ptr(&mut self) -> *mut EVP_MD_CTX {
+        self.0.as_ptr()
+    }
 }
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 511b133..894934d 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -29,11 +29,12 @@
     EC_KEY_set_public_key_affine_coordinates, EC_POINT_get_affine_coordinates,
     NID_X9_62_prime256v1, NID_secp384r1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
 };
+use cbor_util::{get_label_value, get_label_value_as_bytes};
 use ciborium::Value;
 use core::ptr::{self, NonNull};
 use coset::{
     iana::{self, EnumI64},
-    CborSerializable, CoseKey, CoseKeyBuilder, Label,
+    CborSerializable, CoseKey, CoseKeyBuilder, KeyType, Label,
 };
 use log::error;
 use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
@@ -79,13 +80,27 @@
     }
 
     /// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
-    pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+    pub fn from_cose_public_key_slice(cose_key: &[u8]) -> Result<Self> {
         let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
             error!("Failed to deserialize COSE_Key: {e:?}");
             Error::CoseKeyDecodingFailed
         })?;
+        Self::from_cose_public_key(&cose_key)
+    }
+
+    /// Constructs an `EcKey` instance from the provided `COSE_Key`.
+    ///
+    /// The lifetime of the returned `EcKey` is not tied to the lifetime of the `cose_key`,
+    /// because the affine coordinates stored in the `cose_key` are copied into the `EcKey`.
+    ///
+    /// Currently, only the EC P-256 and P-384 curves are supported.
+    pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+        if cose_key.kty != KeyType::Assigned(iana::KeyType::EC2) {
+            error!("Only EC2 keys are supported. Key type in the COSE Key: {:?}", cose_key.kty);
+            return Err(Error::Unimplemented);
+        }
         let ec_key =
-            match get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))? {
+            match get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))? {
                 crv if crv == &Value::from(P256_CURVE.to_i64()) => EcKey::new_p256()?,
                 crv if crv == &Value::from(P384_CURVE.to_i64()) => EcKey::new_p384()?,
                 crv => {
@@ -96,8 +111,8 @@
                     return Err(Error::Unimplemented);
                 }
             };
-        let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
-        let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+        let x = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+        let y = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
 
         let group = ec_key.ec_group()?;
         group.check_affine_coordinate_size(x)?;
@@ -309,17 +324,6 @@
     }
 }
 
-fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
-    Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
-        error!("Value not a bstr.");
-        Error::CoseKeyDecodingFailed
-    })?)
-}
-
-fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
-    Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
-}
-
 /// Wrapper of an `EC_GROUP` reference.
 struct EcGroup<'a>(&'a EC_GROUP);
 
diff --git a/libs/bssl/src/evp.rs b/libs/bssl/src/evp.rs
index 5362925..fe3d88e 100644
--- a/libs/bssl/src/evp.rs
+++ b/libs/bssl/src/evp.rs
@@ -15,22 +15,31 @@
 //! Wrappers of the EVP functions in BoringSSL evp.h.
 
 use crate::cbb::CbbFixed;
+use crate::digest::{Digester, DigesterContext};
 use crate::ec_key::EcKey;
 use crate::util::{check_int_result, to_call_failed_error};
 use alloc::vec::Vec;
-use bssl_avf_error::{ApiName, Result};
+use bssl_avf_error::{ApiName, Error, Result};
 use bssl_ffi::{
-    CBB_flush, CBB_len, EVP_PKEY_free, EVP_PKEY_new, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key,
-    EVP_PKEY,
+    CBB_flush, CBB_len, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_free, EVP_PKEY_new,
+    EVP_PKEY_new_raw_public_key, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key, EVP_PKEY,
+    EVP_PKEY_ED25519, EVP_PKEY_X25519,
 };
-use core::ptr::NonNull;
+use cbor_util::{get_label_value, get_label_value_as_bytes};
+use ciborium::Value;
+use core::ptr::{self, NonNull};
+use coset::{
+    iana::{self, EnumI64},
+    CoseKey, KeyType, Label,
+};
+use log::error;
 
 /// Wrapper of an `EVP_PKEY` object, representing a public or private key.
 pub struct PKey {
     pkey: NonNull<EVP_PKEY>,
-    /// Since this struct owns the inner key, the inner key remains valid as
+    /// If this struct owns the inner EC key, the inner EC key should remain valid as
     /// long as the pointer to `EVP_PKEY` is valid.
-    _inner_key: EcKey,
+    _inner_ec_key: Option<EcKey>,
 }
 
 impl Drop for PKey {
@@ -53,14 +62,14 @@
 
     fn try_from(key: EcKey) -> Result<Self> {
         let pkey = new_pkey()?;
-        // SAFETY: The function only sets the inner key of the initialized and
+        // SAFETY: The function only sets the inner EC key of the initialized and
         // non-null `EVP_PKEY` to point to the given `EC_KEY`. It only reads from
         // and writes to the initialized `EVP_PKEY`.
         // Since this struct owns the inner key, the inner key remains valid as
         // long as `EVP_PKEY` is valid.
         let ret = unsafe { EVP_PKEY_set1_EC_KEY(pkey.as_ptr(), key.0.as_ptr()) };
         check_int_result(ret, ApiName::EVP_PKEY_set1_EC_KEY)?;
-        Ok(Self { pkey, _inner_key: key })
+        Ok(Self { pkey, _inner_ec_key: Some(key) })
     }
 }
 
@@ -87,4 +96,126 @@
         let len = unsafe { CBB_len(cbb.as_ref()) };
         Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec())
     }
+
+    /// This function takes a raw public key data slice and creates a `PKey` instance wrapping
+    /// a freshly allocated `EVP_PKEY` object from it.
+    ///
+    /// The lifetime of the returned instance is not tied to the lifetime of the raw public
+    /// key slice because the raw data is copied into the `EVP_PKEY` object.
+    ///
+    /// Currently the only supported raw formats are X25519 and Ed25519, where the formats
+    /// are specified in RFC 7748 and RFC 8032 respectively.
+    pub fn new_raw_public_key(raw_public_key: &[u8], type_: PKeyType) -> Result<Self> {
+        let engine = ptr::null_mut(); // Engine is not used.
+        let pkey =
+            // SAFETY: The function only reads from the given raw public key within its bounds.
+            // The returned pointer is checked below.
+            unsafe {
+                EVP_PKEY_new_raw_public_key(
+                    type_.0,
+                    engine,
+                    raw_public_key.as_ptr(),
+                    raw_public_key.len(),
+                )
+            };
+        let pkey =
+            NonNull::new(pkey).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new_raw_public_key))?;
+        Ok(Self { pkey, _inner_ec_key: None })
+    }
+
+    /// Creates a `PKey` from the given `cose_key`.
+    ///
+    /// The lifetime of the returned instance is not tied to the lifetime of the `cose_key` as the
+    /// data of `cose_key` is copied into the `EVP_PKEY` or `EC_KEY` object.
+    pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+        match &cose_key.kty {
+            KeyType::Assigned(iana::KeyType::EC2) => {
+                EcKey::from_cose_public_key(cose_key)?.try_into()
+            }
+            KeyType::Assigned(iana::KeyType::OKP) => {
+                let curve_type =
+                    get_label_value(cose_key, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+                let curve_type = match curve_type {
+                    crv if crv == &Value::from(iana::EllipticCurve::Ed25519.to_i64()) => {
+                        PKeyType::ED25519
+                    }
+                    crv if crv == &Value::from(iana::EllipticCurve::X25519.to_i64()) => {
+                        PKeyType::X25519
+                    }
+                    crv => {
+                        error!("Unsupported curve type in OKP COSE key: {:?}", crv);
+                        return Err(Error::Unimplemented);
+                    }
+                };
+                let x = get_label_value_as_bytes(
+                    cose_key,
+                    Label::Int(iana::OkpKeyParameter::X.to_i64()),
+                )?;
+                Self::new_raw_public_key(x, curve_type)
+            }
+            kty => {
+                error!("Unsupported key type in COSE key: {:?}", kty);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    /// Verifies the given `signature` of the `message` using the current public key.
+    ///
+    /// The `message` will be hashed using the given `digester` before verification.
+    ///
+    /// For algorithms like Ed25519 that do not use pre-hashed inputs, the `digester` should
+    /// be `None`.
+    pub fn verify(
+        &self,
+        signature: &[u8],
+        message: &[u8],
+        digester: Option<Digester>,
+    ) -> Result<()> {
+        let mut digester_context = DigesterContext::new()?;
+        // The `EVP_PKEY_CTX` is set to null as this function does not collect the context
+        // during the verification.
+        let pkey_context = ptr::null_mut();
+        let engine = ptr::null_mut(); // Use the default engine.
+        let ret =
+            // SAFETY: All the non-null parameters passed to this function have been properly
+            // initialized as required in the BoringSSL spec.
+            unsafe {
+                EVP_DigestVerifyInit(
+                    digester_context.as_mut_ptr(),
+                    pkey_context,
+                    digester.map_or(ptr::null(), |d| d.0),
+                    engine,
+                    self.pkey.as_ptr(),
+                )
+            };
+        check_int_result(ret, ApiName::EVP_DigestVerifyInit)?;
+
+        // SAFETY: The function only reads from the given slices within their bounds.
+        // The `EVP_MD_CTX` is successfully initialized before this call.
+        let ret = unsafe {
+            EVP_DigestVerify(
+                digester_context.as_mut_ptr(),
+                signature.as_ptr(),
+                signature.len(),
+                message.as_ptr(),
+                message.len(),
+            )
+        };
+        check_int_result(ret, ApiName::EVP_DigestVerify)
+    }
+}
+
+/// Type of the keys supported by `PKey`.
+///
+/// It is a wrapper of the `EVP_PKEY_*` macros defined BoringSSL evp.h, which are the
+/// NID values of the corresponding keys.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct PKeyType(i32);
+
+impl PKeyType {
+    /// EVP_PKEY_X25519 / NID_X25519
+    pub const X25519: PKeyType = PKeyType(EVP_PKEY_X25519);
+    /// EVP_PKEY_ED25519 / NID_ED25519
+    pub const ED25519: PKeyType = PKeyType(EVP_PKEY_ED25519);
 }
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 25b0163..a420168 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -38,7 +38,7 @@
 pub use cbs::Cbs;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
-pub use evp::PKey;
+pub use evp::{PKey, PKeyType};
 pub use hkdf::hkdf;
 pub use hmac::hmac_sha256;
 pub use rand::rand_bytes;
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 4f0697c..9c7eb4f 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, PKey, Result};
+use bssl_avf::{sha256, ApiName, Digester, EcKey, EcdsaError, Error, PKey, Result};
 use coset::CborSerializable;
 use spki::{
     der::{AnyRef, Decode},
@@ -72,7 +72,7 @@
     ec_key.generate_key()?;
     let cose_key = ec_key.cose_public_key()?;
     let cose_key_data = cose_key.clone().to_vec().unwrap();
-    let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+    let deserialized_ec_key = EcKey::from_cose_public_key_slice(&cose_key_data)?;
 
     assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
     Ok(())
@@ -82,10 +82,29 @@
 fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
     let mut ec_key = EcKey::new_p256()?;
     ec_key.generate_key()?;
-    let digest = sha256(MESSAGE1)?;
+    let digester = Digester::sha256();
+    let digest = digester.digest(MESSAGE1)?;
+    assert_eq!(digest, sha256(MESSAGE1)?);
 
     let signature = ec_key.ecdsa_sign(&digest)?;
-    ec_key.ecdsa_verify(&signature, &digest)
+    ec_key.ecdsa_verify(&signature, &digest)?;
+    // Building a `PKey` from a temporary `CoseKey` should work as the lifetime
+    // of the `PKey` is not tied to the lifetime of the `CoseKey`.
+    let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+    pkey.verify(&signature, MESSAGE1, Some(digester))
+}
+
+#[test]
+fn ecdsa_p384_signing_and_verification_succeed() -> Result<()> {
+    let mut ec_key = EcKey::new_p384()?;
+    ec_key.generate_key()?;
+    let digester = Digester::sha384();
+    let digest = digester.digest(MESSAGE1)?;
+
+    let signature = ec_key.ecdsa_sign(&digest)?;
+    ec_key.ecdsa_verify(&signature, &digest)?;
+    let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+    pkey.verify(&signature, MESSAGE1, Some(digester))
 }
 
 #[test]
@@ -100,6 +119,12 @@
     let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
     let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
     assert_eq!(expected_err, err);
+
+    let pkey: PKey = ec_key2.try_into()?;
+    let err = pkey.verify(&signature, MESSAGE1, Some(Digester::sha256())).unwrap_err();
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_DigestVerify, EcdsaError::BadSignature.into());
+    assert_eq!(expected_err, err);
     Ok(())
 }
 
diff --git a/libs/cborutil/Android.bp b/libs/cborutil/Android.bp
index 4758c4b..96dbf09 100644
--- a/libs/cborutil/Android.bp
+++ b/libs/cborutil/Android.bp
@@ -24,6 +24,7 @@
     rustlibs: [
         "libciborium_nostd",
         "libcoset_nostd",
+        "liblog_rust_nostd",
         "libserde_nostd",
     ],
 }
@@ -37,6 +38,7 @@
     rustlibs: [
         "libciborium",
         "libcoset",
+        "liblog_rust",
         "libserde",
     ],
 }
diff --git a/libs/cborutil/src/lib.rs b/libs/cborutil/src/lib.rs
index 2ec5af4..6e834f1 100644
--- a/libs/cborutil/src/lib.rs
+++ b/libs/cborutil/src/lib.rs
@@ -18,8 +18,11 @@
 
 extern crate alloc;
 
+use alloc::string::String;
 use alloc::vec::Vec;
-use coset::{CoseError, Result};
+use ciborium::value::{Integer, Value};
+use coset::{CoseError, CoseKey, Label, Result};
+use log::error;
 use serde::{de::DeserializeOwned, Serialize};
 
 /// Serializes the given data to a CBOR-encoded byte vector.
@@ -39,3 +42,88 @@
         Err(CoseError::ExtraneousData)
     }
 }
+
+/// Converts the provided value `v` to a value array.
+pub fn value_to_array(v: Value, context: &'static str) -> Result<Vec<Value>> {
+    v.into_array().map_err(|e| to_unexpected_item_error(&e, "array", context))
+}
+
+/// Converts the provided value `v` to a text string.
+pub fn value_to_text(v: Value, context: &'static str) -> Result<String> {
+    v.into_text().map_err(|e| to_unexpected_item_error(&e, "tstr", context))
+}
+
+/// Converts the provided value `v` to a map.
+pub fn value_to_map(v: Value, context: &'static str) -> Result<Vec<(Value, Value)>> {
+    v.into_map().map_err(|e| to_unexpected_item_error(&e, "map", context))
+}
+
+/// Converts the provided value `v` to a number.
+pub 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}");
+        CoseError::OutOfRangeIntegerValue
+    })
+}
+
+/// Converts the provided value `v` to a byte array of length `N`.
+pub fn value_to_byte_array<const N: usize>(v: Value, context: &'static str) -> Result<[u8; N]> {
+    let arr = value_to_bytes(v, context)?;
+    arr.try_into().map_err(|e| {
+        error!("The provided value '{context}' is not an array of length {N}: {e:?}");
+        CoseError::UnexpectedItem("bstr", "array of length {N}")
+    })
+}
+
+/// Converts the provided value `v` to bytes array.
+pub fn value_to_bytes(v: Value, context: &'static str) -> 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`.
+pub fn cbor_value_type(v: &Value) -> &'static str {
+    match v {
+        Value::Integer(_) => "int",
+        Value::Bytes(_) => "bstr",
+        Value::Float(_) => "float",
+        Value::Text(_) => "tstr",
+        Value::Bool(_) => "bool",
+        Value::Null => "nul",
+        Value::Tag(_, _) => "tag",
+        Value::Array(_) => "array",
+        Value::Map(_) => "map",
+        _ => "other",
+    }
+}
+
+/// Returns the value of the given label in the given COSE key as bytes.
+pub fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+    let v = get_label_value(key, label)?;
+    Ok(v.as_bytes().ok_or_else(|| {
+        to_unexpected_item_error(v, "bstr", "Get label value in CoseKey as bytes")
+    })?)
+}
+
+/// Returns the value of the given label in the given COSE key.
+pub fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+    Ok(&key
+        .params
+        .iter()
+        .find(|(k, _)| k == &label)
+        .ok_or(CoseError::UnexpectedItem("", "Label not found in CoseKey"))?
+        .1)
+}
diff --git a/libs/devicemapper/src/util.rs b/libs/devicemapper/src/util.rs
index e8df424..cc071e4 100644
--- a/libs/devicemapper/src/util.rs
+++ b/libs/devicemapper/src/util.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{anyhow, bail, Result};
+use anyhow::{bail, Result};
 use nix::sys::stat::FileStat;
 use std::fs::File;
 use std::os::unix::fs::FileTypeExt;
@@ -52,24 +52,6 @@
     Ok(())
 }
 
-/// Returns hexadecimal reprentation of a given byte array.
-pub fn hexstring_from(s: &[u8]) -> String {
-    s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or_default()
-}
-
-/// Parses a hexadecimal string into a byte array
-pub fn parse_hexstring(s: &str) -> Result<Vec<u8>> {
-    let len = s.len();
-    if len % 2 != 0 {
-        bail!("length {} is not even", len)
-    } else {
-        (0..len)
-            .step_by(2)
-            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| anyhow!(e)))
-            .collect()
-    }
-}
-
 /// fstat that accepts a path rather than FD
 pub fn fstat(p: &Path) -> Result<FileStat> {
     let f = File::open(p)?;
diff --git a/libs/devicemapper/src/verity.rs b/libs/devicemapper/src/verity.rs
index 24584f8..bbd9d38 100644
--- a/libs/devicemapper/src/verity.rs
+++ b/libs/devicemapper/src/verity.rs
@@ -151,7 +151,7 @@
         };
 
         let root_digest = if let Some(root_digest) = self.root_digest {
-            hexstring_from(root_digest)
+            hex::encode(root_digest)
         } else {
             bail!("root digest is not set")
         };
@@ -159,7 +159,7 @@
         let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
             "-".to_string() // Note. It's not an empty string!
         } else {
-            hexstring_from(self.salt.unwrap())
+            hex::encode(self.salt.unwrap())
         };
 
         // Step2: serialize the information according to the spec, which is ...
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index 000f723..a524655 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -323,6 +323,29 @@
     }
 }
 
+/// Iterator over descendants
+#[derive(Debug)]
+pub struct DescendantsIterator<'a> {
+    node: Option<(FdtNode<'a>, usize)>,
+}
+
+impl<'a> DescendantsIterator<'a> {
+    pub(crate) fn new(node: &'a FdtNode) -> Self {
+        Self { node: Some((*node, 0)) }
+    }
+}
+
+impl<'a> Iterator for DescendantsIterator<'a> {
+    type Item = (FdtNode<'a>, usize);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let (node, depth) = self.node?;
+        self.node = node.next_node(depth).ok().flatten().filter(|(_, depth)| *depth > 0);
+
+        self.node
+    }
+}
+
 /// Iterator over properties
 #[derive(Debug)]
 pub struct PropertyIterator<'a> {
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index b513649..aae75f7 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -20,8 +20,8 @@
 mod iterators;
 
 pub use iterators::{
-    AddressRange, CellIterator, CompatibleIterator, MemRegIterator, PropertyIterator,
-    RangesIterator, Reg, RegIterator, SubnodeIterator,
+    AddressRange, CellIterator, CompatibleIterator, DescendantsIterator, MemRegIterator,
+    PropertyIterator, RangesIterator, Reg, RegIterator, SubnodeIterator,
 };
 
 use core::cmp::max;
@@ -486,6 +486,23 @@
         Ok(fdt_err_or_option(ret)?.map(|offset| FdtNode { fdt: self.fdt, offset }))
     }
 
+    /// Returns an iterator of descendants
+    pub fn descendants(&'a self) -> DescendantsIterator<'a> {
+        DescendantsIterator::new(self)
+    }
+
+    fn next_node(&self, depth: usize) -> Result<Option<(Self, usize)>> {
+        let mut next_depth: c_int = depth.try_into().unwrap();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe {
+            libfdt_bindgen::fdt_next_node(self.fdt.as_ptr(), self.offset, &mut next_depth)
+        };
+        let Ok(next_depth) = usize::try_from(next_depth) else {
+            return Ok(None);
+        };
+        Ok(fdt_err_or_option(ret)?.map(|offset| (FdtNode { fdt: self.fdt, offset }, next_depth)))
+    }
+
     /// Returns an iterator of properties
     pub fn properties(&'a self) -> Result<PropertyIterator<'a>> {
         PropertyIterator::new(self)
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index 63cbdee..d5d6ece 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -308,3 +308,23 @@
     // Just check whether borrow checker doesn't complain this.
     memory.setprop_inplace(cstr!("device_type"), b"MEMORY\0").unwrap();
 }
+
+#[test]
+fn node_descendants() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    let node_z = fdt.node(cstr!("/node_z")).unwrap().unwrap();
+    let descendants: Vec<_> =
+        node_z.descendants().map(|(node, depth)| (node.name().unwrap(), depth)).collect();
+
+    assert_eq!(
+        descendants,
+        vec![
+            (cstr!("node_za"), 1),
+            (cstr!("node_zb"), 1),
+            (cstr!("node_zz"), 1),
+            (cstr!("node_zzz"), 2)
+        ]
+    );
+}
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/Android.bp b/pvmfw/Android.bp
index c6befb4..d267e2e 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -81,10 +81,10 @@
         ":test_pvmfw_devices_vm_dtbo",
         ":test_pvmfw_devices_vm_dtbo_without_symbols",
         ":test_pvmfw_devices_with_rng",
-        ":test_pvmfw_devices_with_rng_iommu",
         ":test_pvmfw_devices_with_multiple_devices_iommus",
         ":test_pvmfw_devices_with_iommu_sharing",
         ":test_pvmfw_devices_with_iommu_id_conflict",
+        ":test_pvmfw_devices_without_iommus",
     ],
     // To use libpvmfw_fdt_template for testing
     enabled: false,
@@ -121,37 +121,43 @@
     out: ["test_pvmfw_devices_vm_dtbo_without_symbols.dtbo"],
 }
 
+genrule_defaults {
+    name: "test_device_assignment_dts_to_dtb",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_crosvm_dt_base.dtsi"],
+}
+
 genrule {
     name: "test_pvmfw_devices_with_rng",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_rng.dts"],
     out: ["test_pvmfw_devices_with_rng.dtb"],
 }
 
 genrule {
-    name: "test_pvmfw_devices_with_rng_iommu",
-    defaults: ["dts_to_dtb"],
-    srcs: ["testdata/test_pvmfw_devices_with_rng_iommu.dts"],
-    out: ["test_pvmfw_devices_with_rng_iommu.dtb"],
+    name: "test_pvmfw_devices_without_iommus",
+    defaults: ["test_device_assignment_dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_without_iommus.dts"],
+    out: ["test_pvmfw_devices_without_iommus.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_multiple_devices_iommus",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts"],
     out: ["test_pvmfw_devices_with_multiple_devices_iommus.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_iommu_sharing",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_iommu_sharing.dts"],
     out: ["test_pvmfw_devices_with_iommu_sharing.dtb"],
 }
 
 genrule {
     name: "test_pvmfw_devices_with_iommu_id_conflict",
-    defaults: ["dts_to_dtb"],
+    defaults: ["test_device_assignment_dts_to_dtb"],
     srcs: ["testdata/test_pvmfw_devices_with_iommu_id_conflict.dts"],
     out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
 }
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 4a269c3..9abc123 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -265,60 +265,60 @@
 	pviommu_0: pviommu0 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_1: pviommu1 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_2: pviommu2 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_3: pviommu3 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_4: pviommu4 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_5: pviommu5 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_6: pviommu6 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_7: pviommu7 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_8: pviommu8 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 
 	pviommu_9: pviommu9 {
 		compatible = "pkvm,pviommu";
 		id = <PLACEHOLDER>;
-		#iommu-cells = <0>;
+		#iommu-cells = <1>;
 	};
 };
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/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index 60bf21c..14f1fe5 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -452,8 +452,8 @@
     const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
     const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
         "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
+    const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
     const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
-    const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
     const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
         "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
     const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
@@ -464,7 +464,7 @@
         path: CString,
         reg: Vec<u8>,
         interrupts: Vec<u8>,
-        iommus: Vec<u32>, // pvIOMMU ids
+        iommus: Vec<u32>, // pvIOMMU id and vSID
     }
 
     impl AssignedDeviceNode {
@@ -536,6 +536,26 @@
     }
 
     #[test]
+    fn device_info_assigned_info_without_iommus() {
+        let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+
+        let expected = [AssignedDeviceInfo {
+            node_path: CString::new("/backlight").unwrap(),
+            dtbo_node_path: cstr!("/fragment@backlight/__overlay__/backlight").into(),
+            reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+            interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+            iommus: vec![],
+        }];
+
+        assert_eq!(device_info.assigned_devices, expected);
+    }
+
+    #[test]
     fn device_info_assigned_info() {
         let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
@@ -549,7 +569,7 @@
             dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
             reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-            iommus: vec![],
+            iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
         }];
 
         assert_eq!(device_info.assigned_devices, expected);
@@ -585,13 +605,19 @@
         let light = vm_dtbo.node(cstr!("/fragment@rng/__overlay__/light")).unwrap();
         assert_eq!(light, None);
 
+        let led = vm_dtbo.node(cstr!("/fragment@led/__overlay__/led")).unwrap();
+        assert_eq!(led, None);
+
+        let backlight = vm_dtbo.node(cstr!("/fragment@backlight/__overlay__/backlight")).unwrap();
+        assert_eq!(backlight, None);
+
         let symbols_node = vm_dtbo.symbols().unwrap();
         assert_eq!(symbols_node, None);
     }
 
     #[test]
     fn device_info_patch() {
-        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
+        let mut fdt_data = fs::read(FDT_WITHOUT_IOMMUS_FILE_PATH).unwrap();
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
         let mut data = vec![0_u8; fdt_data.len() + vm_dtbo_data.len()];
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
@@ -610,14 +636,14 @@
         // Note: Intentionally not using AssignedDeviceNode for matching all props.
         type FdtResult<T> = libfdt::Result<T>;
         let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
-            (Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
-            (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
+            (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
+            (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
             (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
             (Ok(cstr!("iommus")), Ok(Vec::new())),
             (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
         ];
 
-        let rng_node = platform_dt.node(cstr!("/rng")).unwrap().unwrap();
+        let rng_node = platform_dt.node(cstr!("/backlight")).unwrap().unwrap();
         let mut properties: Vec<_> = rng_node
             .properties()
             .unwrap()
@@ -634,7 +660,7 @@
 
     #[test]
     fn device_info_overlay_iommu() {
-        let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
+        let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
         let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
@@ -691,13 +717,13 @@
                 path: CString::new("/rng").unwrap(),
                 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-                iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
+                iommus: vec![0x4, 0xFF0],
             },
             AssignedDeviceNode {
                 path: CString::new("/light").unwrap(),
                 reg: into_fdt_prop(vec![0x100, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
-                iommus: vec![0x40, 0xFFA, 0x50, 0xFFB, 0x60, 0xFFC],
+                iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
             },
         ];
 
@@ -706,7 +732,7 @@
             assert_eq!(node, Ok(expected));
         }
         let pviommus = collect_pviommus(platform_dt);
-        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
+        assert_eq!(pviommus, Ok(vec![0x4, 0x40, 0x50]));
     }
 
     #[test]
@@ -734,13 +760,13 @@
                 path: CString::new("/rng").unwrap(),
                 reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-                iommus: vec![0x4, 0xFF0, 0x9, 0xFF1],
+                iommus: vec![0x4, 0xFF0],
             },
             AssignedDeviceNode {
-                path: CString::new("/light").unwrap(),
+                path: CString::new("/led").unwrap(),
                 reg: into_fdt_prop(vec![0x100, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
-                iommus: vec![0x9, 0xFF1, 0x40, 0xFFA],
+                iommus: vec![0x4, 0xFF0],
             },
         ];
 
@@ -750,7 +776,7 @@
         }
 
         let pviommus = collect_pviommus(platform_dt);
-        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
+        assert_eq!(pviommus, Ok(vec![0x4]));
     }
 
     #[test]
diff --git a/pvmfw/testdata/test_crosvm_dt_base.dtsi b/pvmfw/testdata/test_crosvm_dt_base.dtsi
new file mode 100644
index 0000000..0c1a311
--- /dev/null
+++ b/pvmfw/testdata/test_crosvm_dt_base.dtsi
@@ -0,0 +1,152 @@
+/dts-v1/;
+/plugin/;
+
+// This is generated manually by removing unassigned pvIOMMU nodes
+// from patched platform.dts.
+
+/ {
+	interrupt-parent = <0x01>;
+	compatible = "linux,dummy-virt";
+	#address-cells = <0x02>;
+	#size-cells = <0x02>;
+
+	chosen {
+		bootargs = "panic=-1 crashkernel=31M";
+		linux,initrd-end = <0x811d6cb8>;
+		linux,initrd-start = <0x81000000>;
+		stdout-path = "/uart@3f8";
+		1,pci-probe-only = <0x01>;
+		kaslr-seed = <0x00 0x00>;
+		avf,strict-boot;
+		avf,new-instance;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0x00 0x10000000>;
+	};
+
+	reserved-memory {
+		#address-cells = <0x02>;
+		#size-cells = <0x02>;
+		ranges;
+
+		restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			size = <0x00 0xe00000>;
+			alignment = <0x00 0x1000>;
+			phandle = <0x02>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0x00 0x7fe25000 0x00 0x1000>;
+		};
+	};
+
+	cpus {
+		#address-cells = <0x01>;
+		#size-cells = <0x00>;
+
+		cpu@0 {
+			device_type = "cpu";
+			compatible = "arm,arm-v8";
+			enable-method = "psci";
+			reg = <0x00>;
+		};
+	};
+
+	intc {
+		compatible = "arm,gic-v3";
+		#address-cells = <0x02>;
+		#size-cells = <0x02>;
+		#interrupt-cells = <0x03>;
+		interrupt-controller;
+		reg = <0x00 0x3fff0000 0x00 0x10000 0x00 0x3ffd0000 0x00 0x20000>;
+		phandle = <0x01>;
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		always-on;
+		interrupts = <0x01 0x0d 0x108 0x01 0x0e 0x108 0x01 0x0b 0x108 0x01 0x0a 0x108>;
+	};
+
+	uart@2e8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x2e8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x02 0x01>;
+	};
+
+	uart@2f8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x2f8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x02 0x01>;
+	};
+
+	uart@3e8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x3e8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x00 0x01>;
+	};
+
+	uart@3f8 {
+		compatible = "ns16550a";
+		reg = <0x00 0x3f8 0x00 0x08>;
+		clock-frequency = <0x1c2000>;
+		interrupts = <0x00 0x00 0x01>;
+	};
+
+	psci {
+		compatible = "arm,psci-1.0";
+		method = "hvc";
+	};
+
+	pci {
+		compatible = "pci-host-cam-generic";
+		device_type = "pci";
+		#address-cells = <0x03>;
+		#size-cells = <0x02>;
+		#interrupt-cells = <0x01>;
+		dma-coherent;
+		memory-region = <0x02>;
+		ranges = <0x3000000 0x00 0x2000000 0x00 0x2000000 0x00 0x2000000 0x3000000 0x00 0x90800000 0x00 0x90800000 0xff 0x6f800000>;
+		bus-range = <0x00 0x00>;
+		reg = <0x00 0x10000 0x00 0x1000000>;
+		interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04 0x4000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0b 0x04 0x4800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0c 0x04>;
+		interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
+	};
+
+	pclk@3M {
+		compatible = "fixed-clock";
+		clock-frequency = <0x2fefd8>;
+		#clock-cells = <0x00>;
+		phandle = <0x03>;
+	};
+
+	rtc@2000 {
+		compatible = "arm,primecell";
+		arm,primecell-periphid = <0x41030>;
+		reg = <0x00 0x2000 0x00 0x1000>;
+		interrupts = <0x00 0x01 0x04>;
+		clock-names = "apb_pclk";
+		clocks = <0x03>;
+	};
+
+	vmwdt@3000 {
+		compatible = "qemu,vcpu-stall-detector";
+		reg = <0x00 0x3000 0x00 0x1000>;
+		clock-frequency = <0x0a>;
+		timeout-sec = <0x08>;
+	};
+
+	__symbols__ {
+		swiotlb = "/reserved-memory/restricted_dma_reserved";
+		intc = "/intc";
+		clk = "/pclk@3M";
+	};
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
index e85b55b..da08694 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
@@ -21,6 +21,33 @@
 			light {
 				compatible = "android,light";
 				version = <0x1 0x2>;
+				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
+				android,pvmfw,phy-sid = <4>, <5>;
+			};
+		};
+	};
+
+	fragment@led {
+		target-path = "/";
+		__overlay__ {
+			led {
+				compatible = "android,led";
+				prop = <0x555>;
+				android,pvmfw,phy-reg = <0x0 0x12000000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x12E40000>;
+				android,pvmfw,phy-sid = <3>;
+			};
+		};
+	};
+
+	fragment@backlight {
+		target-path = "/";
+		__overlay__ {
+			backlight {
+				compatible = "android,backlight";
+				android,backlight,ignore-gctrl-reset;
+				android,pvmfw,phy-reg = <0x0 0x300 0x100>;
 			};
 		};
 	};
@@ -28,5 +55,7 @@
 	__symbols__ {
 		rng = "/fragment@rng/__overlay__/rng";
 		sensor = "/fragment@sensor/__overlay__/light";
+		led = "/fragment@led/__overlay__/led";
+		backlight = "/fragment@backlight/__overlay__/backlight";
 	};
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
index 08444ac..18b9e79 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
@@ -21,6 +21,22 @@
 			light {
 				compatible = "android,light";
 				version = <0x1 0x2>;
+				android,pvmfw,phy-reg = <0x0 0xF00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x40000>, <0x0 0x50000>;
+				android,pvmfw,phy-sid = <4>, <5>;
+			};
+		};
+	};
+
+	fragment@led {
+		target-path = "/";
+		__overlay__ {
+			led {
+				compatible = "android,led";
+				prop;
+				android,pvmfw,phy-reg = <0x0 0x12F00000 0x1000>;
+				android,pvmfw,phy-iommu = <0x0 0x20000>, <0x0 0x30000>;
+				android,pvmfw,phy-sid = <7>, <8>;
 			};
 		};
 	};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
index 199a5ce..70b633c 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -1,54 +1,16 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0>, <&pviommu_1>;
+        iommus = <&pviommu_0 0x0>, <&pviommu_1 0x1>;
     };
 
     pviommu_0: pviommu0 {
@@ -67,7 +29,7 @@
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_0>, <&pviommu_a>, <&pviommu_b>;
+        iommus = <&pviommu_a 0xA>, <&pviommu_b 0xB>;
     };
 
     pviommu_a: pviommua {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index 4906064..7c6d2f2 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -1,61 +1,23 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
-    light@70000000 {
+    led@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_1 0xFF1>, <&pviommu_a 0xFFA>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
@@ -63,16 +25,4 @@
         id = <0x4>;
         #iommu-cells = <1>;
     };
-
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <1>;
-    };
-
-    pviommu_a: pviommua {
-        compatible = "pkvm,pviommu";
-        id = <0x40>;
-        #iommu-cells = <1>;
-    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
index 959cd23..76c99c9 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -1,54 +1,15 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
@@ -57,17 +18,11 @@
         #iommu-cells = <1>;
     };
 
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <1>;
-    };
-
     light@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>, <&pviommu_c 0xFFC>;
+        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>;
     };
 
     pviommu_a: pviommua {
@@ -81,10 +36,4 @@
         id = <0x50>;
         #iommu-cells = <1>;
     };
-
-    pviommu_c: pviommuc {
-        compatible = "pkvm,pviommu";
-        id = <0x60>;
-        #iommu-cells = <1>;
-    };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
index f24fd65..a987098 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
@@ -1,52 +1,21 @@
 /dts-v1/;
 /plugin/;
 
+/include/ "test_crosvm_dt_base.dtsi"
+
 / {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
     rng@90000000 {
         compatible = "android,rng";
         reg = <0x0 0x9 0x0 0xFF>;
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
+        iommus = <&pviommu_0 0xFF0>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <1>;
     };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
deleted file mode 100644
index 8c04b39..0000000
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
+++ /dev/null
@@ -1,59 +0,0 @@
-/dts-v1/;
-/plugin/;
-
-/ {
-	chosen {
-		stdout-path = "/uart@3f8";
-		linux,pci-probe-only = <1>;
-	};
-
-	memory {
-		device_type = "memory";
-		reg = <0x00 0x80000000 0xFFFFFFFF>;
-	};
-
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-		swiotlb: restricted_dma_reserved {
-			compatible = "restricted-dma-pool";
-			reg = <0xFFFFFFFF>;
-			size = <0xFFFFFFFF>;
-			alignment = <0xFFFFFFFF>;
-		};
-
-		dice {
-			compatible = "google,open-dice";
-			no-map;
-			reg = <0xFFFFFFFF>;
-		};
-	};
-
-	cpus {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		cpu@0 {
-			device_type = "cpu";
-		};
-		cpu@1 {
-			device_type = "cpu";
-		    reg = <0x00 0x80000000 0xFFFFFFFF>;
-		};
-    };
-
-    rng@90000000 {
-        compatible = "android,rng";
-        reg = <0x0 0x9 0x0 0xFF>;
-        interrupts = <0x0 0xF 0x4>;
-        google,eh,ignore-gctrl-reset;
-        status = "okay";
-        iommus = <&pviommu_0 0xFF0>;
-    };
-
-    pviommu_0: pviommu0 {
-        compatible = "pkvm,pviommu";
-        id = <0x4>;
-        #iommu-cells = <1>;
-    };
-};
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
new file mode 100644
index 0000000..2036c9c
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+    backlight@90000000 {
+        compatible = "android,backlight";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+    };
+};
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 c1a8394..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(
@@ -226,7 +203,8 @@
     parent_certificate: &X509Certificate,
 ) -> Result<()> {
     let cose_mac = CoseMac0::from_slice(maced_public_key)?;
-    let authority_public_key = EcKey::from_cose_public_key(&cose_mac.payload.unwrap()).unwrap();
+    let authority_public_key =
+        EcKey::from_cose_public_key_slice(&cose_mac.payload.unwrap()).unwrap();
     let (remaining, cert) = X509Certificate::from_der(certificate)?;
     assert!(remaining.is_empty());
 
@@ -244,7 +222,7 @@
     let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
     let csr_payload =
         cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
-    let subject_public_key = EcKey::from_cose_public_key(&csr_payload.public_key).unwrap();
+    let subject_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key).unwrap();
     let expected_spki_data =
         PKey::try_from(subject_public_key).unwrap().subject_public_key_info().unwrap();
     let (remaining, expected_spki) = SubjectPublicKeyInfo::from_der(&expected_spki_data)?;
@@ -261,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/Android.bp b/service_vm/comm/Android.bp
index bdfc099..bf923a4 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -23,6 +23,7 @@
     rustlibs: [
         "libbssl_avf_error_nostd",
         "libciborium_nostd",
+        "libcbor_util_nostd",
         "libcoset_nostd",
         "libder_nostd",
         "liblog_rust_nostd",
@@ -36,6 +37,7 @@
     rustlibs: [
         "libbssl_avf_error",
         "libciborium",
+        "libcbor_util",
         "libcoset",
         "liblog_rust",
         "libserde",
diff --git a/service_vm/comm/src/csr.rs b/service_vm/comm/src/csr.rs
index 2a27f90..a87d28f 100644
--- a/service_vm/comm/src/csr.rs
+++ b/service_vm/comm/src/csr.rs
@@ -17,9 +17,9 @@
 
 use alloc::vec;
 use alloc::vec::Vec;
+use cbor_util::{cbor_value_type, value_to_bytes};
 use ciborium::Value;
 use coset::{self, CborSerializable, CoseError};
-use log::error;
 
 /// Represents a CSR sent from the client VM to the service VM for attestation.
 ///
@@ -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,35 +94,8 @@
             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"))
-    }
-}
-
-/// Reads the type of the provided value `v`.
-pub fn cbor_value_type(v: &Value) -> &'static str {
-    match v {
-        Value::Integer(_) => "int",
-        Value::Bytes(_) => "bstr",
-        Value::Float(_) => "float",
-        Value::Text(_) => "tstr",
-        Value::Bool(_) => "bool",
-        Value::Null => "nul",
-        Value::Tag(_, _) => "tag",
-        Value::Array(_) => "array",
-        Value::Map(_) => "map",
-        _ => "other",
-    }
-}
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index c9de540..bb85a26 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::{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 ddf230b..cfdac2d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -62,7 +62,7 @@
     // the DICE chain in `cose_sign`.
 
     // Verifies the second signature with the public key in the CSR payload.
-    let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+    let ec_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key)?;
     cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
         ecdsa_verify(&ec_public_key, signature, message)
     })?;
@@ -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..557b678 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -14,7 +14,12 @@
 
 //! This module contains functions related to DICE.
 
+use alloc::string::String;
 use alloc::vec::Vec;
+use cbor_util::{
+    cbor_value_type, value_to_array, value_to_byte_array, value_to_bytes, value_to_map,
+    value_to_num, value_to_text,
+};
 use ciborium::value::Value;
 use core::cell::OnceCell;
 use core::result;
@@ -23,7 +28,7 @@
 };
 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::RequestProcessingError;
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
@@ -33,6 +38,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 +99,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 +153,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 +213,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 +230,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,44 +238,167 @@
 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| {
-        error!("The provided value '{context}' is not an array of length {N}: {e:?}");
-        RequestProcessingError::InternalError
-    })
+#[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 to_mode(value: Value) -> Result<DiceMode> {
@@ -264,7 +434,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 +468,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")
     }