Merge "Update for Nix 0.27.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/TEST_MAPPING b/TEST_MAPPING
index 6983fde..4da96c8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -117,6 +117,9 @@
       "path": "packages/modules/Virtualization/service_vm/requests"
     },
     {
+      "path": "packages/modules/Virtualization/virtualizationservice"
+    },
+    {
       "path": "packages/modules/Virtualization/vm"
     },
     {
diff --git a/apex/Android.bp b/apex/Android.bp
index a889d08..acbc0a1 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -143,10 +143,10 @@
         },
         release_avf_enable_vendor_modules: {
             prebuilts: [
-                "microdroid_gki_initrd_debuggable",
-                "microdroid_gki_initrd_normal",
-                "microdroid_gki_kernel",
-                "microdroid_gki.json",
+                "microdroid_gki-6.1_initrd_debuggable",
+                "microdroid_gki-6.1_initrd_normal",
+                "microdroid_gki-6.1_kernel",
+                "microdroid_gki-6.1.json",
             ],
         },
         release_avf_enable_remote_attestation: {
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 7393636..0c5bc72 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -410,21 +410,35 @@
            '--output_vbmeta_image', output]
     RunCommand(args, cmd)
 
+
+gki_versions = ['6.1']
+
 # dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
-virt_apex_files = {
+virt_apex_non_gki_files = {
     'kernel': 'etc/fs/microdroid_kernel',
-    'gki_kernel': 'etc/fs/microdroid_gki_kernel',
     'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
     'super.img': 'etc/fs/microdroid_super.img',
     'initrd_normal.img': 'etc/microdroid_initrd_normal.img',
-    'gki_initrd_normal.img': 'etc/microdroid_gki_initrd_normal.img',
     'initrd_debuggable.img': 'etc/microdroid_initrd_debuggable.img',
-    'gki_initrd_debuggable.img': 'etc/microdroid_gki_initrd_debuggable.img',
 }
 
-
 def TargetFiles(input_dir):
-    return {k: os.path.join(input_dir, v) for k, v in virt_apex_files.items()}
+    ret = {k: os.path.join(input_dir, v) for k, v in virt_apex_non_gki_files.items()}
+
+    for ver in gki_versions:
+        kernel        = os.path.join(input_dir, f'etc/fs/microdroid_gki-{ver}_kernel')
+        initrd_normal = os.path.join(input_dir, f'etc/microdroid_gki-{ver}_initrd_normal.img')
+        initrd_debug  = os.path.join(input_dir, f'etc/microdroid_gki-{ver}_initrd_debuggable.img')
+
+        if os.path.isfile(kernel):
+            ret[f'gki-{ver}_kernel']                = kernel
+            ret[f'gki-{ver}_initrd_normal.img']     = initrd_normal
+            ret[f'gki-{ver}_initrd_debuggable.img'] = initrd_debug
+
+    return ret
+
+def IsInitrdImage(path):
+    return path.endswith('initrd_normal.img') or path.endswith('initrd_debuggable.img')
 
 
 def SignVirtApex(args):
@@ -461,13 +475,9 @@
                      images=images,
                      wait=images_f)
 
-    has_gki_kernel = os.path.isfile(files['gki_kernel'])
-
     vbmeta_bc_f = None
     if not args.do_not_update_bootconfigs:
-        initrd_files = [files['initrd_normal.img'], files['initrd_debuggable.img']]
-        if has_gki_kernel:
-            initrd_files += [files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']]
+        initrd_files = [v for k, v in files.items() if IsInitrdImage(k)]
         vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args, initrd_files,
                             files['vbmeta.img'],
                             wait=[vbmeta_f])
@@ -493,8 +503,12 @@
 
     resign_kernel('kernel', 'initrd_normal.img', 'initrd_debuggable.img')
 
-    if has_gki_kernel:
-        resign_kernel('gki_kernel', 'gki_initrd_normal.img', 'gki_initrd_debuggable.img')
+    for ver in gki_versions:
+        if f'gki-{ver}_kernel' in files:
+            resign_kernel(
+                f'gki-{ver}_kernel',
+                f'gki-{ver}_initrd_normal.img',
+                f'gki-{ver}_initrd_debuggable.img')
 
 
 def VerifyVirtApex(args):
@@ -518,12 +532,11 @@
         assert info is not None, f'no avbinfo: {file}'
         assert info['Public key (sha1)'] == pubkey_digest, f'pubkey mismatch: {file}'
 
-    for f in files.values():
-        if f in (files['initrd_normal.img'], files['initrd_debuggable.img'],
-                 files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']):
+    for k, f in files.items():
+        if IsInitrdImage(k):
             # TODO(b/245277660): Verify that ramdisks contain the correct vbmeta digest
             continue
-        if f == files['super.img']:
+        if k == 'super.img':
             Async(check_avb_pubkey, system_a_img)
         else:
             # Check pubkey for other files using avbtool
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/service/src/main.rs b/authfs/service/src/main.rs
index 78de07a..67e22a5 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -127,6 +127,7 @@
     Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
 }
 
+#[allow(clippy::eq_op)]
 fn try_main() -> Result<()> {
     let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
     let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
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/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index b558f06..9f6ce9c 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -31,6 +31,7 @@
 use std::panic;
 use std::sync::Arc;
 
+#[allow(clippy::eq_op)]
 fn try_main() -> Result<()> {
     let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
     let log_level = if debuggable { log::Level::Debug } else { log::Level::Info };
diff --git a/libs/apexutil/Android.bp b/libs/apexutil/Android.bp
index 92d4e80..beff58d 100644
--- a/libs/apexutil/Android.bp
+++ b/libs/apexutil/Android.bp
@@ -6,11 +6,12 @@
     name: "libapexutil_rust.defaults",
     crate_name: "apexutil",
     defaults: ["avf_build_flags_rust"],
-    host_supported: true,
     srcs: ["src/lib.rs"],
     edition: "2021",
     rustlibs: [
+        "libapex_manifest_rs",
         "liblog_rust",
+        "libprotobuf",
         "libthiserror",
         "libvbmeta_rust",
         "libzip",
@@ -27,18 +28,13 @@
     defaults: ["libapexutil_rust.defaults"],
     prefer_rlib: true,
     test_suites: ["general-tests"],
-    data: ["tests/data/*"],
+    // We're reusing test APEXes from system/apex/apexd
+    data: [
+        ":apex.apexd_test",
+        ":apex.apexd_test_v2_no_pb",
+        ":gen_key_mismatch_with_image_apex",
+    ],
     rustlibs: [
         "libhex",
     ],
-    target: {
-        host: {
-            // TODO(b/204562227): remove once the build does this automatically
-            data_libs: [
-                "libc++",
-                "libcrypto",
-                "libz",
-            ],
-        },
-    },
 }
diff --git a/libs/apexutil/src/lib.rs b/libs/apexutil/src/lib.rs
index 8a934e2..639135f 100644
--- a/libs/apexutil/src/lib.rs
+++ b/libs/apexutil/src/lib.rs
@@ -14,6 +14,8 @@
 
 //! Routines for handling APEX payload
 
+use apex_manifest::apex_manifest::ApexManifest;
+use protobuf::Message;
 use std::fs::File;
 use std::io::{self, Read};
 use thiserror::Error;
@@ -23,28 +25,32 @@
 
 const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
 const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
+const APEX_MANIFEST_ENTRY: &str = "apex_manifest.pb";
 
 /// Errors from parsing an APEX.
 #[derive(Debug, Error)]
 pub enum ApexParseError {
     /// There was an IO error.
-    #[error("IO error")]
+    #[error("IO error: {0}")]
     Io(#[from] io::Error),
     /// The Zip archive was invalid.
-    #[error("Cannot read zip archive")]
+    #[error("Cannot read zip archive: {0}")]
     InvalidZip(&'static str),
-    /// The apex_pubkey file was missing from the APEX.
-    #[error("APEX doesn't contain apex_pubkey")]
-    PubkeyMissing,
-    /// The apex_payload.img file was missing from the APEX.
-    #[error("APEX doesn't contain apex_payload.img")]
-    PayloadMissing,
+    /// An expected file was missing from the APEX.
+    #[error("APEX doesn't contain {0}")]
+    MissingFile(&'static str),
     /// There was no hashtree descriptor in the APEX payload's VBMeta image.
     #[error("Non-hashtree descriptor found in payload's VBMeta image")]
     DescriptorNotHashtree,
     /// There was an error parsing the APEX payload's VBMeta image.
-    #[error("Could not parse payload's VBMeta image")]
+    #[error("Could not parse payload's VBMeta image: {0}")]
     PayloadVbmetaError(#[from] vbmeta::VbMetaImageParseError),
+    /// Data was missing from the VBMeta
+    #[error("Data missing from VBMeta: {0}")]
+    VbmetaMissingData(&'static str),
+    /// An error occurred parsing the APEX manifest as a protobuf
+    #[error("Error parsing manifest protobuf: {0}")]
+    ManifestProtobufError(#[from] protobuf::Error),
 }
 
 /// Errors from verifying an APEX.
@@ -58,29 +64,44 @@
     PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
     /// The APEX payload was not verified with the apex_pubkey.
     #[error("APEX pubkey mismatch")]
-    ApexPubkeyMistmatch,
+    ApexPubkeyMismatch,
 }
 
-/// Verification result holds public key and root digest of apex_payload.img
+/// Information extracted from the APEX during AVB verification.
+#[derive(Debug)]
 pub struct ApexVerificationResult {
+    /// The name of the APEX, from its manifest.
+    pub name: Option<String>,
+    /// The version of the APEX, from its manifest.
+    pub version: Option<i64>,
     /// The public key that verifies the payload signature.
     pub public_key: Vec<u8>,
     /// The root digest of the payload hashtree.
     pub root_digest: Vec<u8>,
 }
 
-/// Verify APEX payload by AVB verification and return public key and root digest
+/// Verify APEX payload by AVB verification and return information about the APEX.
+/// This verifies that the VBMeta is correctly signed by the public key specified in the APEX.
+/// It doesn't verify that that is the correct key, nor does it verify that the payload matches
+/// the signed root hash - that is handled by dm-verity once apexd has mounted the APEX.
 pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
     let apex_file = File::open(path).map_err(ApexParseError::Io)?;
-    let (public_key, image_offset, image_size) = get_public_key_and_image_info(&apex_file)?;
+    let ApexZipInfo { public_key, image_offset, image_size, manifest } =
+        get_apex_zip_info(&apex_file)?;
     let vbmeta = VbMetaImage::verify_reader_region(apex_file, image_offset, image_size)?;
     let root_digest = find_root_digest(&vbmeta)?;
-    match vbmeta.public_key() {
-        Some(payload_public_key) if public_key == payload_public_key => {
-            Ok(ApexVerificationResult { public_key, root_digest })
-        }
-        _ => Err(ApexVerificationError::ApexPubkeyMistmatch),
+    let vbmeta_public_key =
+        vbmeta.public_key().ok_or(ApexParseError::VbmetaMissingData("public key"))?;
+    if vbmeta_public_key != public_key {
+        return Err(ApexVerificationError::ApexPubkeyMismatch);
     }
+    let (name, version) = if cfg!(dice_changes) {
+        let ApexManifestInfo { name, version } = decode_manifest(&manifest)?;
+        (Some(name), Some(version))
+    } else {
+        (None, None)
+    };
+    Ok(ApexVerificationResult { name, version, public_key, root_digest })
 }
 
 fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError> {
@@ -93,46 +114,52 @@
     Err(ApexParseError::DescriptorNotHashtree)
 }
 
-/// Gets the hash of the payload's verified VBMeta image data.
-pub fn get_payload_vbmeta_image_hash(path: &str) -> Result<Vec<u8>, ApexVerificationError> {
-    let apex_file = File::open(path).map_err(ApexParseError::Io)?;
-    let (_, offset, size) = get_public_key_and_image_info(&apex_file)?;
-    let vbmeta = VbMetaImage::verify_reader_region(apex_file, offset, size)?;
-    Ok(vbmeta.hash().ok_or(ApexVerificationError::ApexPubkeyMistmatch)?.to_vec())
+struct ApexZipInfo {
+    public_key: Vec<u8>,
+    image_offset: u64,
+    image_size: u64,
+    manifest: Vec<u8>,
 }
 
-fn get_public_key_and_image_info(apex_file: &File) -> Result<(Vec<u8>, u64, u64), ApexParseError> {
-    let mut z = ZipArchive::new(apex_file).map_err(|err| match err {
-        ZipError::Io(err) => ApexParseError::Io(err),
-        ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-            ApexParseError::InvalidZip(s)
-        }
-        ZipError::FileNotFound => unreachable!(),
-    })?;
+fn get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError> {
+    let mut z = ZipArchive::new(apex_file).map_err(|err| from_zip_error(err, "?"))?;
 
     let mut public_key = Vec::new();
     z.by_name(APEX_PUBKEY_ENTRY)
-        .map_err(|err| match err {
-            ZipError::Io(err) => ApexParseError::Io(err),
-            ZipError::FileNotFound => ApexParseError::PubkeyMissing,
-            ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-                ApexParseError::InvalidZip(s)
-            }
-        })?
+        .map_err(|err| from_zip_error(err, APEX_PUBKEY_ENTRY))?
         .read_to_end(&mut public_key)?;
 
     let (image_offset, image_size) = z
         .by_name(APEX_PAYLOAD_ENTRY)
         .map(|f| (f.data_start(), f.size()))
-        .map_err(|err| match err {
-            ZipError::Io(err) => ApexParseError::Io(err),
-            ZipError::FileNotFound => ApexParseError::PayloadMissing,
-            ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-                ApexParseError::InvalidZip(s)
-            }
-        })?;
+        .map_err(|err| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
 
-    Ok((public_key, image_offset, image_size))
+    let mut manifest = Vec::new();
+    z.by_name(APEX_MANIFEST_ENTRY)
+        .map_err(|err| from_zip_error(err, APEX_MANIFEST_ENTRY))?
+        .read_to_end(&mut manifest)?;
+
+    Ok(ApexZipInfo { public_key, image_offset, image_size, manifest })
+}
+
+struct ApexManifestInfo {
+    name: String,
+    version: i64,
+}
+
+fn decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError> {
+    let manifest = ApexManifest::parse_from_reader(&mut manifest)?;
+    Ok(ApexManifestInfo { name: manifest.name, version: manifest.version })
+}
+
+fn from_zip_error(err: ZipError, name: &'static str) -> ApexParseError {
+    match err {
+        ZipError::Io(err) => ApexParseError::Io(err),
+        ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
+            ApexParseError::InvalidZip(s)
+        }
+        ZipError::FileNotFound => ApexParseError::MissingFile(name),
+    }
 }
 
 #[cfg(test)]
@@ -141,20 +168,68 @@
 
     #[test]
     fn apex_verification_returns_valid_result() {
-        let res = verify("tests/data/test.apex").unwrap();
-        // The expected hex is generated when we ran the method the first time.
+        let res = verify("apex.apexd_test.apex").unwrap();
+        let (expected_name, expected_version) = if cfg!(dice_changes) {
+            (Some("com.android.apex.test_package"), Some(1))
+        } else {
+            (None, None)
+        };
+        assert_eq!(res.name.as_deref(), expected_name);
+        assert_eq!(res.version, expected_version);
+        // The expected hex values were generated when we ran the method the first time.
         assert_eq!(
             hex::encode(res.root_digest),
-            "fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
+            "54265da77ae1fd619e39809ad99fedc576bb20c0c7a8002190fa64438436299f"
+        );
+        assert_eq!(
+            hex::encode(res.public_key),
+            "\
+            00001000963a5527aaf0145b3bb5f899a05034ccc76dafdd671dbf4e42c04df2eeba15\
+            6c884816d7d08ef8d834d4adc27979afed9eaf406694d0d600f0b6d31e3ab85da47d27\
+            9c223a1630e02332d920587617ea766a136057a3a3232a7c42f83fb3763e853be4026c\
+            067524a95fcbfcc6caadfb553210bb5385f5adc5caeb0e3f6a9aa56af88d8899d962eb\
+            807864feabeeacdd868697935fb4cc4843957e0d90ee4293c715c4e5b970e6545a17d1\
+            735f814c7d4dbdeaac97275a84f292e3715c158d38eb00eebd010dd2fa56595c0e5627\
+            06c7a94e566912f993e5e35c04b2a314d1bce1ceb10de6c50f8101ddb6ee993fc79959\
+            2e79ee73b77741ee5c076c89343684344a6d080e5529a046d506d104bf32903e39c363\
+            b020fee9d87e7c6ffdad120b630386e958416ac156bc2d7301836c79e926e8f185a640\
+            be05135e17018c88dde02cd7bd49655e9e9dff7f965fb8e68217236c18d23b6d7e7632\
+            184acb95b088598601c809d5e66c19f5e06b5e5ff1bbae7e3142959d9380db2d4a25c8\
+            757975232ea311016e830703a6023b0986e885f2eda066517fce09f33f359b6ef7cc5a\
+            2fdaced74257661bad184a653ea2d80d1af68de5821c06a472635f0276dc42d699f588\
+            ea6c46189ca1ad544bbd4951a766bc4119b0ea671cb16556762721723bf1db47c83c76\
+            a7cc2fd3b6029efec9908d9d4640294f6ea46f6e1a3195e9252c393e35698911a7c496\
+            138dc2dd8d9dcb470ae1c6d2224d13b160fb3ae4bc235f6133c2ff5f9232fb89adfdba\
+            48dcc47cf29a22cd47dcec0b1a179f352c9848a8e04ac37f35777a24312c821febc591\
+            84c8cdefc88e50b4d6bc9530ca743f4284c9773677d38527e6e8020fe367f0f16a6c49\
+            9a7f2da95ec6471f7382e5c0da98b531702cb55a560de7cafc7b6111aae0f896fb1fed\
+            d4997a954c6c083ef1fd3bb13fef3f95022523fb1fbe7f4a49e12e54a5206f95daa316\
+            ac009b7bee4039f769fd28033db6013df841c86d8345d44418fbc9f669e4ee3294b2ff\
+            29d048f53d768c0a41f9a280f0229d9912e8b2fb734617a9947be973ed1dc7bdeac9e2\
+            6028d59317098a44bacdb3b10ccde6ef02f7c94124461032a033701ce523b13142658c\
+            265385198903ccf227ad5ae88ec31e586cd8f855641fd2646dba8053d0d0924f132505\
+            8141f1c7433aa9686f48e3f3a972b56776eaf8bf22a740d1aea2ef473184d697de1dab\
+            9b62a227611c7500b11dea2e5eb8051807c0d1f2fe032acfd7701c017e629f99c74de5\
+            da4c2a542f17b9833beb14442aa7c2990b828473376ea03fdb4a650b88e821fe5026e8\
+            ffb7002d095c9877ee3a98a4488ed3287e9be4942a223f4e32bc26c2ebd02eec20dc82\
+            7493b44f4efaf9b2e175d4de2b07c32d6d359e234c9e50ef905ffa7f6907c313a3c9f4\
+            40d1efd5ec7cbeef06dcfd649f4c8219ad"
         );
     }
 
     #[test]
-    fn payload_vbmeta_has_valid_image_hash() {
-        let result = get_payload_vbmeta_image_hash("tests/data/test.apex").unwrap();
-        assert_eq!(
-            hex::encode(result),
-            "296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d"
-        );
+    fn apex_no_manifest_fails_verification() {
+        match verify("apex.apexd_test_v2_no_pb.apex").unwrap_err() {
+            ApexVerificationError::ParseError(ApexParseError::MissingFile(_)) => (),
+            e => panic!("Unexpected error {e}"),
+        }
+    }
+
+    #[test]
+    fn apex_signature_mismatch_fails_verification() {
+        match verify("apex.apexd_test_wrong_public_key.apex").unwrap_err() {
+            ApexVerificationError::ApexPubkeyMismatch => (),
+            e => panic!("Unexpected error {e}"),
+        }
     }
 }
diff --git a/libs/apexutil/tests/data/README.md b/libs/apexutil/tests/data/README.md
deleted file mode 100644
index 82ebec6..0000000
--- a/libs/apexutil/tests/data/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Test data
-
-- test.apex: copied from system/apexshim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
\ No newline at end of file
diff --git a/libs/apexutil/tests/data/test.apex b/libs/apexutil/tests/data/test.apex
deleted file mode 100644
index fd79365..0000000
--- a/libs/apexutil/tests/data/test.apex
+++ /dev/null
Binary files differ
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 ff45af9..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",
@@ -46,5 +47,6 @@
     rustlibs: [
         "libbssl_avf_nostd",
         "libcoset_nostd",
+        "libspki_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 89865d4..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,6 +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 7e677c4..894934d 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -23,29 +23,30 @@
 use bssl_avf_error::{ApiName, Error, Result};
 use bssl_ffi::{
     BN_bin2bn, BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, ECDSA_sign, ECDSA_size,
-    ECDSA_verify, EC_GROUP_new_by_curve_name, EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key,
-    EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
-    EC_KEY_new_by_curve_name, EC_KEY_parse_private_key, EC_KEY_set_public_key_affine_coordinates,
-    EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
+    ECDSA_verify, EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, EC_KEY_check_key,
+    EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
+    EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+    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 core::result;
 use coset::{
     iana::{self, EnumI64},
-    CborSerializable, CoseKey, CoseKeyBuilder, Label,
+    CborSerializable, CoseKey, CoseKeyBuilder, KeyType, Label,
 };
 use log::error;
 use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
 
 const ES256_ALGO: iana::Algorithm = iana::Algorithm::ES256;
 const P256_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const P384_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_384;
 const P256_AFFINE_COORDINATE_SIZE: usize = 32;
-
-type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
+const P384_AFFINE_COORDINATE_SIZE: usize = 48;
 
 /// Wrapper of an `EC_KEY` object, representing a public or private EC key.
-pub struct EcKey(NonNull<EC_KEY>);
+pub struct EcKey(pub(crate) NonNull<EC_KEY>);
 
 impl Drop for EcKey {
     fn drop(&mut self) {
@@ -67,41 +68,66 @@
             .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
     }
 
+    /// Creates a new EC P-384 key pair.
+    pub fn new_p384() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let ec_key = unsafe {
+            EC_KEY_new_by_curve_name(NID_secp384r1) // EC P-384 CURVE Nid
+        };
+        NonNull::new(ec_key)
+            .map(Self)
+            .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
+    }
+
     /// 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
         })?;
-        if cose_key.alg != Some(coset::Algorithm::Assigned(ES256_ALGO)) {
-            error!(
-                "Only ES256 algorithm is supported. Algo type in the COSE Key: {:?}",
-                cose_key.alg
-            );
+        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 crv = get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
-        if &Value::from(P256_CURVE.to_i64()) != crv {
-            error!("Only EC P-256 curve is supported. Curve type in the COSE Key: {crv:?}");
-            return Err(Error::Unimplemented);
-        }
+        let ec_key =
+            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 => {
+                    error!(
+                        "Only EC P-256 and P-384 curves are supported. \
+                         Curve type in the COSE Key: {crv:?}"
+                    );
+                    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()))?;
-
-        check_p256_affine_coordinate_size(x)?;
-        check_p256_affine_coordinate_size(y)?;
+        let group = ec_key.ec_group()?;
+        group.check_affine_coordinate_size(x)?;
+        group.check_affine_coordinate_size(y)?;
 
         let x = BigNum::from_slice(x)?;
         let y = BigNum::from_slice(y)?;
 
-        let ec_key = EcKey::new_p256()?;
         // SAFETY: All the parameters are checked non-null and initialized.
         // The function only reads the coordinates x and y within their bounds.
         let ret = unsafe {
             EC_KEY_set_public_key_affine_coordinates(ec_key.0.as_ptr(), x.as_ref(), y.as_ref())
         };
         check_int_result(ret, ApiName::EC_KEY_set_public_key_affine_coordinates)?;
+        ec_key.check_key()?;
         Ok(ec_key)
     }
 
@@ -192,14 +218,13 @@
     /// Returns the `CoseKey` for the public key.
     pub fn cose_public_key(&self) -> Result<CoseKey> {
         let (x, y) = self.public_key_coordinates()?;
-        let key = CoseKeyBuilder::new_ec2_pub_key(P256_CURVE, x.to_vec(), y.to_vec())
-            .algorithm(ES256_ALGO)
-            .build();
+        let curve = self.ec_group()?.coset_curve()?;
+        let key = CoseKeyBuilder::new_ec2_pub_key(curve, x, y).algorithm(ES256_ALGO).build();
         Ok(key)
     }
 
     /// Returns the x and y coordinates of the public key.
-    fn public_key_coordinates(&self) -> Result<(Coordinate, Coordinate)> {
+    fn public_key_coordinates(&self) -> Result<(Vec<u8>, Vec<u8>)> {
         let ec_group = self.ec_group()?;
         let ec_point = self.public_key_ec_point()?;
         let mut x = BigNum::new()?;
@@ -208,10 +233,17 @@
         // SAFETY: All the parameters are checked non-null and initialized when needed.
         // The last parameter `ctx` is generated when needed inside the function.
         let ret = unsafe {
-            EC_POINT_get_affine_coordinates(ec_group, ec_point, x.as_mut_ptr(), y.as_mut_ptr(), ctx)
+            EC_POINT_get_affine_coordinates(
+                ec_group.as_ref(),
+                ec_point,
+                x.as_mut_ptr(),
+                y.as_mut_ptr(),
+                ctx,
+            )
         };
         check_int_result(ret, ApiName::EC_POINT_get_affine_coordinates)?;
-        Ok((x.try_into()?, y.try_into()?))
+        let len = ec_group.affine_coordinate_size()?;
+        Ok((x.to_padded_vec(len)?, y.to_padded_vec(len)?))
     }
 
     /// Returns a pointer to the public key point inside `EC_KEY`. The memory region pointed
@@ -230,7 +262,7 @@
 
     /// Returns a pointer to the `EC_GROUP` object inside `EC_KEY`. The memory region pointed
     /// by the pointer is owned by the `EC_KEY`.
-    fn ec_group(&self) -> Result<*const EC_GROUP> {
+    fn ec_group(&self) -> Result<EcGroup<'_>> {
         let group =
            // SAFETY: It is safe since the key pair has been generated and stored in the
            // `EC_KEY` pointer.
@@ -238,7 +270,9 @@
         if group.is_null() {
             Err(to_call_failed_error(ApiName::EC_KEY_get0_group))
         } else {
-            Ok(group)
+            // SAFETY: The pointer should be valid and points to an initialized `EC_GROUP`
+            // since it is read from a valid `EC_KEY`.
+            Ok(EcGroup(unsafe { &*group }))
         }
     }
 
@@ -290,27 +324,59 @@
     }
 }
 
-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
-    })?)
+/// Wrapper of an `EC_GROUP` reference.
+struct EcGroup<'a>(&'a EC_GROUP);
+
+impl<'a> EcGroup<'a> {
+    /// Returns the NID that identifies the EC group of the key.
+    fn curve_nid(&self) -> i32 {
+        // SAFETY: It is safe since the inner pointer is valid and points to an initialized
+        // instance of `EC_GROUP`.
+        unsafe { EC_GROUP_get_curve_name(self.as_ref()) }
+    }
+
+    fn coset_curve(&self) -> Result<iana::EllipticCurve> {
+        #[allow(non_upper_case_globals)]
+        match self.curve_nid() {
+            NID_X9_62_prime256v1 => Ok(P256_CURVE),
+            NID_secp384r1 => Ok(P384_CURVE),
+            name => {
+                error!("Unsupported curve NID: {}", name);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    fn affine_coordinate_size(&self) -> Result<usize> {
+        #[allow(non_upper_case_globals)]
+        match self.curve_nid() {
+            NID_X9_62_prime256v1 => Ok(P256_AFFINE_COORDINATE_SIZE),
+            NID_secp384r1 => Ok(P384_AFFINE_COORDINATE_SIZE),
+            name => {
+                error!("Unsupported curve NID: {}", name);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    fn check_affine_coordinate_size(&self, coordinate: &[u8]) -> Result<()> {
+        let expected_len = self.affine_coordinate_size()?;
+        if expected_len == coordinate.len() {
+            Ok(())
+        } else {
+            error!(
+                "The size of the affine coordinate '{}' does not match the expected size '{}'",
+                coordinate.len(),
+                expected_len
+            );
+            Err(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)
-}
-
-fn check_p256_affine_coordinate_size(coordinate: &[u8]) -> Result<()> {
-    if P256_AFFINE_COORDINATE_SIZE == coordinate.len() {
-        Ok(())
-    } else {
-        error!(
-            "The size of the affine coordinate '{}' does not match the expected size '{}'",
-            coordinate.len(),
-            P256_AFFINE_COORDINATE_SIZE
-        );
-        Err(Error::CoseKeyDecodingFailed)
+impl<'a> AsRef<EC_GROUP> for EcGroup<'a> {
+    fn as_ref(&self) -> &EC_GROUP {
+        self.0
     }
 }
 
@@ -354,6 +420,16 @@
         NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_new))
     }
 
+    /// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up
+    /// to size `len`. The conversion fails if `len` is smaller than the size of the integer.
+    fn to_padded_vec(&self, len: usize) -> Result<Vec<u8>> {
+        let mut num = vec![0u8; len];
+        // SAFETY: The `BIGNUM` pointer has been created with `BN_new`.
+        let ret = unsafe { BN_bn2bin_padded(num.as_mut_ptr(), num.len(), self.0.as_ptr()) };
+        check_int_result(ret, ApiName::BN_bn2bin_padded)?;
+        Ok(num)
+    }
+
     fn as_mut_ptr(&mut self) -> *mut BIGNUM {
         self.0.as_ptr()
     }
@@ -366,19 +442,3 @@
         unsafe { self.0.as_ref() }
     }
 }
-
-/// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up to
-/// size `N`. The conversion fails if `N` is smaller thanthe size of the integer.
-impl<const N: usize> TryFrom<BigNum> for [u8; N] {
-    type Error = Error;
-
-    fn try_from(bn: BigNum) -> result::Result<Self, Self::Error> {
-        let mut num = [0u8; N];
-        // SAFETY: The `BIGNUM` pointer has been created with `BN_new`.
-        let ret = unsafe { BN_bn2bin_padded(num.as_mut_ptr(), num.len(), bn.0.as_ptr()) };
-        check_int_result(ret, ApiName::BN_bn2bin_padded)?;
-        Ok(num)
-    }
-}
-
-// TODO(b/301068421): Unit tests the EcKey.
diff --git a/libs/bssl/src/evp.rs b/libs/bssl/src/evp.rs
new file mode 100644
index 0000000..fe3d88e
--- /dev/null
+++ b/libs/bssl/src/evp.rs
@@ -0,0 +1,221 @@
+// 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.
+
+//! 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, Error, Result};
+use bssl_ffi::{
+    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 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>,
+    /// 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_ec_key: Option<EcKey>,
+}
+
+impl Drop for PKey {
+    fn drop(&mut self) {
+        // SAFETY: It is safe because `EVP_PKEY` has been allocated by BoringSSL and isn't
+        // used after this.
+        unsafe { EVP_PKEY_free(self.pkey.as_ptr()) }
+    }
+}
+
+/// Creates a new empty `EVP_PKEY`.
+fn new_pkey() -> Result<NonNull<EVP_PKEY>> {
+    // SAFETY: The returned pointer is checked below.
+    let key = unsafe { EVP_PKEY_new() };
+    NonNull::new(key).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new))
+}
+
+impl TryFrom<EcKey> for PKey {
+    type Error = bssl_avf_error::Error;
+
+    fn try_from(key: EcKey) -> Result<Self> {
+        let pkey = new_pkey()?;
+        // 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_ec_key: Some(key) })
+    }
+}
+
+impl PKey {
+    /// Returns a DER-encoded SubjectPublicKeyInfo structure as specified
+    /// in RFC 5280 s4.1.2.7:
+    ///
+    /// https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.7
+    pub fn subject_public_key_info(&self) -> Result<Vec<u8>> {
+        const CAPACITY: usize = 256;
+        let mut buf = [0u8; CAPACITY];
+        let mut cbb = CbbFixed::new(buf.as_mut());
+        // SAFETY: The function only write bytes to the buffer managed by the valid `CBB`.
+        // The inner key in `EVP_PKEY` was set to a valid key when the object was created.
+        // As this struct owns the inner key, the inner key is guaranteed to be valid
+        // throughout the execution of the function.
+        let ret = unsafe { EVP_marshal_public_key(cbb.as_mut(), self.pkey.as_ptr()) };
+        check_int_result(ret, ApiName::EVP_marshal_public_key)?;
+        // SAFETY: This is safe because the CBB pointer is a valid pointer initialized with
+        // `CBB_init_fixed()`.
+        check_int_result(unsafe { CBB_flush(cbb.as_mut()) }, ApiName::CBB_flush)?;
+        // SAFETY: This is safe because the CBB pointer is initialized with `CBB_init_fixed()`,
+        // and it has been flushed, thus it has no active children.
+        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 8e3abcf..a420168 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -24,6 +24,7 @@
 mod digest;
 mod ec_key;
 mod err;
+mod evp;
 mod hkdf;
 mod hmac;
 mod rand;
@@ -37,6 +38,7 @@
 pub use cbs::Cbs;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
+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 3dd243c..9c7eb4f 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,8 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, Result};
+use bssl_avf::{sha256, ApiName, Digester, EcKey, EcdsaError, Error, PKey, Result};
 use coset::CborSerializable;
+use spki::{
+    der::{AnyRef, Decode},
+    AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo,
+};
+
+/// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1.
+const X509_NIST_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
+
+/// OID value in `AlgorithmIdentifier.parameters` for P-256; see RFC 5480 s2.1.1.1.
+const ALGO_PARAM_P256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
 
 const MESSAGE1: &[u8] = b"test message 1";
 const MESSAGE2: &[u8] = b"test message 2";
@@ -30,12 +40,39 @@
 }
 
 #[test]
-fn cose_public_key_serialization() -> Result<()> {
+fn subject_public_key_info_serialization() -> Result<()> {
     let mut ec_key = EcKey::new_p256()?;
     ec_key.generate_key()?;
+    let pkey: PKey = ec_key.try_into()?;
+    let subject_public_key_info = pkey.subject_public_key_info()?;
+
+    let subject_public_key_info = SubjectPublicKeyInfo::from_der(&subject_public_key_info).unwrap();
+    let expected_algorithm = AlgorithmIdentifier {
+        oid: X509_NIST_OID,
+        parameters: Some(AnyRef::from(&ALGO_PARAM_P256_OID)),
+    };
+    assert_eq!(expected_algorithm, subject_public_key_info.algorithm);
+    assert!(!subject_public_key_info.subject_public_key.to_vec().is_empty());
+    Ok(())
+}
+
+#[test]
+fn p256_cose_public_key_serialization() -> Result<()> {
+    let mut ec_key = EcKey::new_p256()?;
+    check_cose_public_key_serialization(&mut ec_key)
+}
+
+#[test]
+fn p384_cose_public_key_serialization() -> Result<()> {
+    let mut ec_key = EcKey::new_p384()?;
+    check_cose_public_key_serialization(&mut ec_key)
+}
+
+fn check_cose_public_key_serialization(ec_key: &mut EcKey) -> Result<()> {
+    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(())
@@ -45,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]
@@ -63,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/dice/open_dice/src/bcc.rs b/libs/dice/open_dice/src/bcc.rs
index 199e1a9..9c9545b 100644
--- a/libs/dice/open_dice/src/bcc.rs
+++ b/libs/dice/open_dice/src/bcc.rs
@@ -20,7 +20,7 @@
     DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverMainFlow,
     DiceAndroidHandoverParse, DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME,
     DICE_ANDROID_CONFIG_COMPONENT_VERSION, DICE_ANDROID_CONFIG_RESETTABLE,
-    DICE_ANDROID_CONFIG_SECURITY_VERSION,
+    DICE_ANDROID_CONFIG_RKP_VM_MARKER, DICE_ANDROID_CONFIG_SECURITY_VERSION,
 };
 use std::{ffi::CStr, ptr};
 
@@ -36,6 +36,8 @@
     pub resettable: bool,
     /// Monotonically increasing version of the component.
     pub security_version: Option<u64>,
+    /// Whether the component can take part in running the RKP VM.
+    pub rkp_vm_marker: bool,
 }
 
 /// Formats a configuration descriptor following the Android Profile for DICE specification.
@@ -58,6 +60,9 @@
         configs |= DICE_ANDROID_CONFIG_SECURITY_VERSION;
         version
     });
+    if values.rkp_vm_marker {
+        configs |= DICE_ANDROID_CONFIG_RKP_VM_MARKER;
+    }
 
     let values =
         DiceAndroidConfigValues { configs, component_name, component_version, security_version };
diff --git a/libs/hyp/src/hypervisor.rs b/libs/hyp/src/hypervisor.rs
index 3d42ccb..c53b886 100644
--- a/libs/hyp/src/hypervisor.rs
+++ b/libs/hyp/src/hypervisor.rs
@@ -24,7 +24,9 @@
 use crate::error::{Error, Result};
 use alloc::boxed::Box;
 use common::Hypervisor;
-pub use common::{MemSharingHypervisor, MmioGuardedHypervisor, MMIO_GUARD_GRANULE_SIZE};
+pub use common::{
+    DeviceAssigningHypervisor, MemSharingHypervisor, MmioGuardedHypervisor, MMIO_GUARD_GRANULE_SIZE,
+};
 pub use geniezone::GeniezoneError;
 use geniezone::GeniezoneHypervisor;
 use gunyah::GunyahHypervisor;
@@ -34,8 +36,6 @@
 use smccc::hvc64;
 use uuid::Uuid;
 
-use self::common::DeviceAssigningHypervisor;
-
 enum HypervisorBackend {
     RegularKvm,
     Gunyah,
diff --git a/libs/hyp/src/hypervisor/common.rs b/libs/hyp/src/hypervisor/common.rs
index 2ea18f3..eaac652 100644
--- a/libs/hyp/src/hypervisor/common.rs
+++ b/libs/hyp/src/hypervisor/common.rs
@@ -79,6 +79,7 @@
     fn granule(&self) -> Result<usize>;
 }
 
+/// Device assigning hypervisor
 pub trait DeviceAssigningHypervisor {
     /// Returns MMIO token.
     fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
diff --git a/libs/hyp/src/lib.rs b/libs/hyp/src/lib.rs
index 505aade..6a23585 100644
--- a/libs/hyp/src/lib.rs
+++ b/libs/hyp/src/lib.rs
@@ -20,6 +20,7 @@
 mod hypervisor;
 mod util;
 
+pub use crate::hypervisor::DeviceAssigningHypervisor;
 pub use error::{Error, Result};
 pub use hypervisor::{
     get_device_assigner, get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE,
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/Android.bp b/microdroid/Android.bp
index b42e416..c1caa56 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -332,22 +332,6 @@
     ],
 }
 
-android_filesystem {
-    name: "microdroid_gki_modules-6.1-arm64",
-    deps: [
-        "microdroid_gki_kernel_modules-6.1-arm64",
-    ],
-    type: "compressed_cpio",
-}
-
-android_filesystem {
-    name: "microdroid_gki_modules-6.1-x86_64",
-    deps: [
-        "microdroid_gki_kernel_modules-6.1-x86_64",
-    ],
-    type: "compressed_cpio",
-}
-
 genrule {
     name: "microdroid_bootconfig_arm64_gen",
     srcs: [
@@ -428,11 +412,6 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki.json",
-    src: "microdroid_gki.json",
-}
-
-prebuilt_etc {
     name: "microdroid_manifest",
     src: "microdroid_manifest.xml",
     filename: "manifest.xml",
@@ -460,11 +439,8 @@
 // python -c "import hashlib; print(hashlib.sha256(b'initrd_normal').hexdigest())"
 initrd_normal_salt = "8041a07d54ac82290f6d90bac1fa8d7fdbc4db974d101d60faf294749d1ebaf8"
 
-avb_gen_vbmeta_image {
-    name: "microdroid_initrd_normal_hashdesc",
-    src: ":microdroid_initrd_normal",
-    partition_name: "initrd_normal",
-    salt: initrd_normal_salt,
+avb_gen_vbmeta_image_defaults {
+    name: "microdroid_initrd_defaults",
     enabled: false,
     arch: {
         // Microdroid kernel is only available in these architectures.
@@ -477,63 +453,38 @@
     },
 }
 
-avb_gen_vbmeta_image {
-    name: "microdroid_gki_initrd_normal_hashdesc",
-    src: ":microdroid_gki_initrd_normal",
+avb_gen_vbmeta_image_defaults {
+    name: "microdroid_initrd_normal_defaults",
+    defaults: ["microdroid_initrd_defaults"],
     partition_name: "initrd_normal",
     salt: initrd_normal_salt,
-    enabled: false,
-    arch: {
-        // Microdroid kernel is only available in these architectures.
-        arm64: {
-            enabled: true,
-        },
-        x86_64: {
-            enabled: true,
-        },
-    },
+}
+
+avb_gen_vbmeta_image {
+    name: "microdroid_initrd_normal_hashdesc",
+    defaults: ["microdroid_initrd_normal_defaults"],
+    src: ":microdroid_initrd_normal",
 }
 
 // python -c "import hashlib; print(hashlib.sha256(b'initrd_debug').hexdigest())"
 initrd_debug_salt = "8ab9dc9cb7e6456700ff6ef18c6b4c3acc24c5fa5381b829563f8d7a415d869a"
 
-avb_gen_vbmeta_image {
-    name: "microdroid_initrd_debug_hashdesc",
-    src: ":microdroid_initrd_debuggable",
+avb_gen_vbmeta_image_defaults {
+    name: "microdroid_initrd_debug_defaults",
+    defaults: ["microdroid_initrd_defaults"],
     partition_name: "initrd_debug",
     salt: initrd_debug_salt,
-    enabled: false,
-    arch: {
-        // Microdroid kernel is only available in these architectures.
-        arm64: {
-            enabled: true,
-        },
-        x86_64: {
-            enabled: true,
-        },
-    },
 }
 
 avb_gen_vbmeta_image {
-    name: "microdroid_gki_initrd_debug_hashdesc",
-    src: ":microdroid_gki_initrd_debuggable",
-    partition_name: "initrd_debug",
-    salt: initrd_debug_salt,
-    enabled: false,
-    arch: {
-        // Microdroid kernel is only available in these architectures.
-        arm64: {
-            enabled: true,
-        },
-        x86_64: {
-            enabled: true,
-        },
-    },
+    name: "microdroid_initrd_debug_hashdesc",
+    defaults: ["microdroid_initrd_debug_defaults"],
+    src: ":microdroid_initrd_debuggable",
 }
 
 soong_config_module_type {
-    name: "flag_aware_avb_add_hash_footer",
-    module_type: "avb_add_hash_footer",
+    name: "flag_aware_avb_add_hash_footer_defaults",
+    module_type: "avb_add_hash_footer_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
         "release_avf_enable_llpvm_changes",
@@ -544,28 +495,21 @@
     ],
 }
 
-flag_aware_avb_add_hash_footer {
-    name: "microdroid_kernel_signed",
+flag_aware_avb_add_hash_footer_defaults {
+    name: "microdroid_kernel_signed_defaults",
     src: ":empty_file",
-    filename: "microdroid_kernel",
     partition_name: "boot",
     private_key: ":microdroid_sign_key",
     salt: bootloader_salt,
     enabled: false,
     arch: {
         arm64: {
-            src: ":microdroid_kernel_prebuilts-6.1-arm64",
             enabled: true,
         },
         x86_64: {
-            src: ":microdroid_kernel_prebuilts-6.1-x86_64",
             enabled: true,
         },
     },
-    include_descriptors_from_images: [
-        ":microdroid_initrd_normal_hashdesc",
-        ":microdroid_initrd_debug_hashdesc",
-    ],
     // Below are properties that are conditionally set depending on value of build flags.
     soong_config_variables: {
         release_avf_enable_llpvm_changes: {
@@ -580,40 +524,22 @@
     },
 }
 
-flag_aware_avb_add_hash_footer {
-    name: "microdroid_gki_kernel_signed",
-    src: ":empty_file",
-    filename: "microdroid_gki_kernel",
-    partition_name: "boot",
-    private_key: ":microdroid_sign_key",
-    salt: bootloader_salt,
-    enabled: false,
+avb_add_hash_footer {
+    name: "microdroid_kernel_signed",
+    defaults: ["microdroid_kernel_signed_defaults"],
+    filename: "microdroid_kernel",
     arch: {
         arm64: {
-            src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
-            enabled: true,
+            src: ":microdroid_kernel_prebuilts-6.1-arm64",
         },
         x86_64: {
-            src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
-            enabled: true,
+            src: ":microdroid_kernel_prebuilts-6.1-x86_64",
         },
     },
     include_descriptors_from_images: [
-        ":microdroid_gki_initrd_normal_hashdesc",
-        ":microdroid_gki_initrd_debug_hashdesc",
+        ":microdroid_initrd_normal_hashdesc",
+        ":microdroid_initrd_debug_hashdesc",
     ],
-    // Below are properties that are conditionally set depending on value of build flags.
-    soong_config_variables: {
-        release_avf_enable_llpvm_changes: {
-            rollback_index: 1,
-            props: [
-                {
-                    name: "com.android.virt.cap",
-                    value: "secretkeeper_protection",
-                },
-            ],
-        },
-    },
 }
 
 prebuilt_etc {
@@ -630,16 +556,54 @@
     },
 }
 
+///////////////////////////////////////
+// GKI-6.1 modules
+///////////////////////////////////////
 prebuilt_etc {
-    name: "microdroid_gki_kernel",
+    name: "microdroid_gki-6.1.json",
+    src: "microdroid_gki-6.1.json",
+}
+
+avb_add_hash_footer {
+    name: "microdroid_gki-6.1_kernel_signed",
+    defaults: ["microdroid_kernel_signed_defaults"],
+    filename: "microdroid_gki-6.1_kernel",
+    arch: {
+        arm64: {
+            src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+        },
+        x86_64: {
+            src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+        },
+    },
+    include_descriptors_from_images: [
+        ":microdroid_gki-6.1_initrd_normal_hashdesc",
+        ":microdroid_gki-6.1_initrd_debug_hashdesc",
+    ],
+}
+
+prebuilt_etc {
+    name: "microdroid_gki-6.1_kernel",
     src: ":empty_file",
     relative_install_path: "fs",
     arch: {
         arm64: {
-            src: ":microdroid_gki_kernel_signed",
+            src: ":microdroid_gki-6.1_kernel_signed",
         },
         x86_64: {
-            src: ":microdroid_gki_kernel_signed",
+            src: ":microdroid_gki-6.1_kernel_signed",
         },
     },
 }
+
+avb_gen_vbmeta_image {
+    name: "microdroid_gki-6.1_initrd_normal_hashdesc",
+    defaults: ["microdroid_initrd_normal_defaults"],
+    src: ":microdroid_gki-6.1_initrd_normal",
+}
+
+avb_gen_vbmeta_image {
+    name: "microdroid_gki-6.1_initrd_debug_hashdesc",
+    defaults: ["microdroid_initrd_debug_defaults"],
+    src: ":microdroid_gki-6.1_initrd_debuggable",
+}
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 6cd84fa..8df4c0f 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -41,7 +41,7 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_gen_arm64",
+    name: "microdroid_gki-6.1_initrd_gen_arm64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_fstab_ramdisk",
@@ -52,7 +52,7 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_gen_x86_64",
+    name: "microdroid_gki-6.1_initrd_gen_x86_64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_fstab_ramdisk",
@@ -96,13 +96,13 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_debuggable_arm64",
+    name: "microdroid_gki-6.1_initrd_debuggable_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki_initrd_gen_arm64",
+        ":microdroid_gki-6.1_initrd_gen_arm64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki_initrd_debuggable_arm64"],
+    out: ["microdroid_gki-6.1_initrd_debuggable_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -118,13 +118,13 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_debuggable_x86_64",
+    name: "microdroid_gki-6.1_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki_initrd_gen_x86_64",
+        ":microdroid_gki-6.1_initrd_gen_x86_64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki_initrd_debuggable_x86_64"],
+    out: ["microdroid_gki-6.1_initrd_debuggable_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -140,13 +140,13 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_normal_arm64",
+    name: "microdroid_gki-6.1_initrd_normal_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki_initrd_gen_arm64",
+        ":microdroid_gki-6.1_initrd_gen_arm64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki_initrd_normal_arm64"],
+    out: ["microdroid_gki-6.1_initrd_normal_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -162,13 +162,13 @@
 }
 
 genrule {
-    name: "microdroid_gki_initrd_normal_x86_64",
+    name: "microdroid_gki-6.1_initrd_normal_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki_initrd_gen_x86_64",
+        ":microdroid_gki-6.1_initrd_gen_x86_64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki_initrd_normal_x86_64"],
+    out: ["microdroid_gki-6.1_initrd_normal_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -188,18 +188,18 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki_initrd_debuggable",
+    name: "microdroid_gki-6.1_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki_initrd_debuggable_x86_64",
+            src: ":microdroid_gki-6.1_initrd_debuggable_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki_initrd_debuggable_arm64",
+            src: ":microdroid_gki-6.1_initrd_debuggable_arm64",
         },
     },
-    filename: "microdroid_gki_initrd_debuggable.img",
+    filename: "microdroid_gki-6.1_initrd_debuggable.img",
 }
 
 prebuilt_etc {
@@ -218,16 +218,16 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki_initrd_normal",
+    name: "microdroid_gki-6.1_initrd_normal",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki_initrd_normal_x86_64",
+            src: ":microdroid_gki-6.1_initrd_normal_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki_initrd_normal_arm64",
+            src: ":microdroid_gki-6.1_initrd_normal_arm64",
         },
     },
-    filename: "microdroid_gki_initrd_normal.img",
+    filename: "microdroid_gki-6.1_initrd_normal.img",
 }
diff --git a/microdroid/microdroid_gki.json b/microdroid/microdroid_gki-6.1.json
similarity index 84%
rename from microdroid/microdroid_gki.json
rename to microdroid/microdroid_gki-6.1.json
index d7ba53e..2115e51 100644
--- a/microdroid/microdroid_gki.json
+++ b/microdroid/microdroid_gki-6.1.json
@@ -1,5 +1,5 @@
 {
-  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki_kernel",
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-6.1_kernel",
   "disks": [
     {
       "partitions": [
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 6b999af..b03d466 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -37,14 +37,18 @@
 }
 
 message ApexPayload {
+  // Next id: 9
+
   // Required.
   string name = 1;
   string partition_name = 2;
 
   // Optional.
-  // When specified, apex payload should be verified with the public key and root digest.
+  // When specified, apex payload should be verified against these values.
   bytes public_key = 3;
   bytes root_digest = 4;
+  int64 manifest_version = 7;
+  string manifest_name = 8;
 
   // Required.
   // The timestamp in seconds when the APEX was last updated. This should match the value in
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 51796f1..4813b35 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -59,8 +59,8 @@
          * Sequence of DER-encoded X.509 certificates that make up the attestation
          * key's certificate chain.
          *
-         * The certificate chain starts with a root certificate and ends with a leaf
-         * certificate covering the attested public key.
+         * The certificate chain starts with a leaf certificate covering the attested
+         * public key and ends with a root certificate.
          */
         Certificate[] certificateChain;
     }
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 6b0775a..0cf7013 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 use crate::dice_driver::DiceDriver;
-use crate::instance::ApkData;
+use crate::instance::{ApexData, ApkData};
 use crate::{is_debuggable, MicrodroidData};
 use anyhow::{bail, Context, Result};
 use ciborium::{cbor, Value};
@@ -26,24 +26,23 @@
 /// Perform an open DICE derivation for the payload.
 pub fn dice_derivation(
     dice: DiceDriver,
-    verified_data: &MicrodroidData,
+    instance_data: &MicrodroidData,
     payload_metadata: &PayloadMetadata,
 ) -> Result<OwnedDiceArtifacts> {
-    let subcomponents = build_subcomponent_list(verified_data);
-
-    let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
+    let subcomponents = build_subcomponent_list(instance_data);
+    let config_descriptor = format_payload_config_descriptor(payload_metadata, subcomponents)
         .context("Building config descriptor")?;
 
     // Calculate compound digests of code and authorities
     let mut code_hash_ctx = Sha512::new();
     let mut authority_hash_ctx = Sha512::new();
-    code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
-    authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
-    for extra_apk in &verified_data.extra_apks_data {
+    code_hash_ctx.update(instance_data.apk_data.root_hash.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 &verified_data.apex_data {
+    for apex in &instance_data.apex_data {
         code_hash_ctx.update(apex.root_digest.as_ref());
         authority_hash_ctx.update(apex.public_key.as_ref());
     }
@@ -54,55 +53,65 @@
     let debuggable = is_debuggable()?;
 
     // Send the details to diced
-    let hidden = verified_data.salt.clone().try_into().unwrap();
+    let hidden = instance_data.salt.clone().try_into().unwrap();
     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: &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.clone(),
+            authority_hash: sha512(&apex.public_key).to_vec(),
         }
     }
 }
 
-fn build_subcomponent_list(verified_data: &MicrodroidData) -> Vec<Subcomponent> {
+fn build_subcomponent_list(instance_data: &MicrodroidData) -> Vec<Subcomponent> {
     if !cfg!(dice_changes) {
         return vec![];
     }
 
-    once(&verified_data.apk_data)
-        .chain(&verified_data.extra_apks_data)
-        .map(Subcomponent::for_apk)
-        .collect()
+    let apks = once(&instance_data.apk_data)
+        .chain(&instance_data.extra_apks_data)
+        .map(Subcomponent::for_apk);
+    let apexes = instance_data.apex_data.iter().map(Subcomponent::for_apex);
+    apks.chain(apexes).collect()
 }
 
 // Returns a configuration descriptor of the given payload. See vm_config.cddl for a definition
 // of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
-    subcomponents: &[Subcomponent],
+    subcomponents: Vec<Subcomponent>,
 ) -> Result<Vec<u8>> {
     let mut map = Vec::new();
     map.push((cbor!(-70002)?, cbor!("Microdroid payload")?));
@@ -118,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)?));
     }
 
@@ -130,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!(
@@ -146,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,
@@ -165,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,
@@ -179,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 6c9e245..7a9f0e0 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -287,23 +287,23 @@
 
 #[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,
+    pub manifest_name: Option<String>,
+    pub manifest_version: Option<i64>,
     pub public_key: Vec<u8>,
     pub root_digest: Vec<u8>,
     pub last_update_seconds: u64,
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 1b41e58..9e167a4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -246,20 +246,20 @@
     }
 
     // Verify the payload before using it.
-    let verified_data = verify_payload(&metadata, saved_data.as_ref())
+    let extracted_data = verify_payload(&metadata, saved_data.as_ref())
         .context("Payload verification failed")
         .map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
 
     // In case identity is ignored (by debug policy), we should reuse existing payload data, even
     // when the payload is changed. This is to keep the derived secret same as before.
-    let verified_data = if let Some(saved_data) = saved_data {
+    let instance_data = if let Some(saved_data) = saved_data {
         if !is_verified_boot() {
-            if saved_data != verified_data {
+            if saved_data != extracted_data {
                 info!("Detected an update of the payload, but continue (regarding debug policy)")
             }
         } else {
             ensure!(
-                saved_data == verified_data,
+                saved_data == extracted_data,
                 MicrodroidError::PayloadChanged(String::from(
                     "Detected an update of the payload which isn't supported yet."
                 ))
@@ -270,9 +270,9 @@
     } else {
         info!("Saving verified data.");
         instance
-            .write_microdroid_data(&verified_data, &dice)
+            .write_microdroid_data(&extracted_data, &dice)
             .context("Failed to write identity data")?;
-        verified_data
+        extracted_data
     };
 
     let payload_metadata = metadata.payload.ok_or_else(|| {
@@ -281,7 +281,7 @@
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    let dice_artifacts = dice_derivation(dice, &verified_data, &payload_metadata)?;
+    let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
     let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
 
     if cfg!(dice_changes) {
@@ -326,10 +326,10 @@
         .ok_or_else(|| MicrodroidError::PayloadInvalidConfig("No task in VM config".to_string()))?;
 
     ensure!(
-        config.extra_apks.len() == verified_data.extra_apks_data.len(),
+        config.extra_apks.len() == instance_data.extra_apks_data.len(),
         "config expects {} extra apks, but found {}",
         config.extra_apks.len(),
-        verified_data.extra_apks_data.len()
+        instance_data.extra_apks_data.len()
     );
     mount_extra_apks(&config, &mut zipfuse)?;
 
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
index a553ce4..98fe24b 100644
--- a/microdroid_manager/src/payload.rs
+++ b/microdroid_manager/src/payload.rs
@@ -17,8 +17,7 @@
 use crate::instance::ApexData;
 use crate::ioutil::wait_for_file;
 use anyhow::Result;
-use apexutil::verify;
-use log::info;
+use log::{info, warn};
 use microdroid_metadata::{read_metadata, ApexPayload, Metadata};
 use std::time::Duration;
 
@@ -38,13 +37,19 @@
         .apexes
         .iter()
         .map(|apex| {
-            let name = apex.name.clone();
             let apex_path = format!("/dev/block/by-name/{}", apex.partition_name);
-            let result = verify(&apex_path)?;
+            let extracted = apexutil::verify(&apex_path)?;
+            if let Some(manifest_name) = &extracted.name {
+                if &apex.name != manifest_name {
+                    warn!("Apex named {} is named {} in its manifest", apex.name, manifest_name);
+                }
+            };
             Ok(ApexData {
-                name,
-                public_key: result.public_key,
-                root_digest: result.root_digest,
+                name: apex.name.clone(),
+                manifest_name: extracted.name,
+                manifest_version: extracted.version,
+                public_key: extracted.public_key,
+                root_digest: extracted.root_digest,
                 last_update_seconds: apex.last_update_seconds,
                 is_factory: apex.is_factory,
             })
@@ -61,6 +66,8 @@
                 name: data.name.clone(),
                 public_key: data.public_key.clone(),
                 root_digest: data.root_digest.clone(),
+                manifest_name: data.manifest_name.clone().unwrap_or_default(),
+                manifest_version: data.manifest_version.unwrap_or_default(),
                 last_update_seconds: data.last_update_seconds,
                 is_factory: data.is_factory,
                 ..Default::default()
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 103619f..d267e2e 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -34,7 +34,6 @@
         "libvmbase",
         "libzerocopy_nostd",
         "libzeroize_nostd",
-        "libspin_nostd",
     ],
 }
 
@@ -45,22 +44,69 @@
     cmd: "touch $(out)",
 }
 
-rust_test {
-    name: "libpvmfw.bootargs.test",
-    host_supported: true,
-    // For now, only bootargs.rs is written to be conditionally compiled with std.
-    srcs: ["src/bootargs.rs"],
+rust_defaults {
+    name: "libpvmfw.test.defaults",
     defaults: ["avf_build_flags_rust"],
     test_suites: ["general-tests"],
     test_options: {
         unit_test: true,
     },
+    prefer_rlib: true,
     rustlibs: [
         "libcstr",
+    ],
+}
+
+rust_test {
+    name: "libpvmfw.bootargs.test",
+    host_supported: true,
+    // For now, only bootargs.rs is written to be conditionally compiled with std.
+    srcs: ["src/bootargs.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
         "libzeroize",
     ],
 }
 
+rust_test {
+    name: "libpvmfw.device_assignment.test",
+    srcs: ["src/device_assignment.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
+        "liblibfdt",
+        "liblog_rust",
+        "libpvmfw_fdt_template",
+    ],
+    data: [
+        ":test_pvmfw_devices_vm_dtbo",
+        ":test_pvmfw_devices_vm_dtbo_without_symbols",
+        ":test_pvmfw_devices_with_rng",
+        ":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,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+
+rust_test {
+    name: "libpvmfw.dice.test",
+    srcs: ["src/dice.rs"],
+    defaults: ["libpvmfw.test.defaults"],
+    rustlibs: [
+        "libcbor_util",
+        "libciborium",
+        "libdiced_open_dice_nostd",
+        "libpvmfw_avb_nostd",
+    ],
+}
+
 genrule {
     name: "test_pvmfw_devices_vm_dtbo",
     defaults: ["dts_to_dtb"],
@@ -75,74 +121,47 @@
     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"],
 }
 
-rust_test {
-    name: "libpvmfw.device_assignment.test",
-    srcs: ["src/device_assignment.rs"],
-    defaults: ["avf_build_flags_rust"],
-    test_suites: ["general-tests"],
-    test_options: {
-        unit_test: true,
-    },
-    prefer_rlib: true,
-    rustlibs: [
-        "libcstr",
-        "liblibfdt",
-        "liblog_rust",
-        "libpvmfw_fdt_template",
-    ],
-    data: [
-        ":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",
-    ],
-    // To use libpvmfw_fdt_template for testing
-    enabled: false,
-    target: {
-        android_arm64: {
-            enabled: true,
-        },
-    },
-}
-
 cc_binary {
     name: "pvmfw",
     defaults: ["vmbase_elf_defaults"],
diff --git a/pvmfw/TEST_MAPPING b/pvmfw/TEST_MAPPING
index f21318e..e948400 100644
--- a/pvmfw/TEST_MAPPING
+++ b/pvmfw/TEST_MAPPING
@@ -10,6 +10,9 @@
     },
     {
       "name" : "libpvmfw.device_assignment.test"
+    },
+    {
+      "name" : "libpvmfw.dice.test"
     }
   ]
 }
diff --git a/pvmfw/avb/src/descriptor/collection.rs b/pvmfw/avb/src/descriptor/collection.rs
index f47bfbd..6784758 100644
--- a/pvmfw/avb/src/descriptor/collection.rs
+++ b/pvmfw/avb/src/descriptor/collection.rs
@@ -18,11 +18,11 @@
 use super::hash::HashDescriptor;
 use super::property::PropertyDescriptor;
 use crate::partition::PartitionName;
-use crate::utils::{self, is_not_null, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
 use crate::PvmfwVerifyError;
+use avb::{IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, VbmetaData};
 use avb_bindgen::{
     avb_descriptor_foreach, avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbDescriptorTag,
-    AvbVBMetaData,
 };
 use core::{ffi::c_void, mem::size_of, slice};
 use tinyvec::ArrayVec;
@@ -36,24 +36,16 @@
 }
 
 impl<'a> Descriptors<'a> {
-    /// Builds `Descriptors` from `AvbVBMetaData`.
-    /// Returns an error if the given `AvbVBMetaData` contains non-hash descriptor, hash
+    /// Builds `Descriptors` from `VbmetaData`.
+    /// Returns an error if the given `VbmetaData` contains non-hash descriptor, hash
     /// descriptor of unknown `PartitionName` or duplicated hash descriptors.
-    ///
-    /// # Safety
-    ///
-    /// Behavior is undefined if any of the following conditions are violated:
-    /// * `vbmeta.vbmeta_data` must be non-null and points to a valid VBMeta.
-    /// * `vbmeta.vbmeta_data` must be valid for reading `vbmeta.vbmeta_size` bytes.
-    pub(crate) unsafe fn from_vbmeta(vbmeta: AvbVBMetaData) -> Result<Self, PvmfwVerifyError> {
-        is_not_null(vbmeta.vbmeta_data).map_err(|_| avb::SlotVerifyError::Io)?;
-        let mut res: Result<Self, avb::IoError> = Ok(Self::default());
-        // SAFETY: It is safe as the raw pointer `vbmeta.vbmeta_data` is a non-null pointer and
-        // points to a valid VBMeta structure.
+    pub(crate) fn from_vbmeta(vbmeta: &'a VbmetaData) -> Result<Self, PvmfwVerifyError> {
+        let mut res: IoResult<Self> = Ok(Self::default());
+        // SAFETY: It is safe as `vbmeta.data()` contains a valid VBMeta structure.
         let output = unsafe {
             avb_descriptor_foreach(
-                vbmeta.vbmeta_data,
-                vbmeta.vbmeta_size,
+                vbmeta.data().as_ptr(),
+                vbmeta.data().len(),
                 Some(check_and_save_descriptor),
                 &mut res as *mut _ as *mut c_void,
             )
@@ -61,7 +53,7 @@
         if output == res.is_ok() {
             res.map_err(PvmfwVerifyError::InvalidDescriptors)
         } else {
-            Err(avb::SlotVerifyError::InvalidMetadata.into())
+            Err(SlotVerifyError::InvalidMetadata.into())
         }
     }
 
@@ -74,11 +66,11 @@
     pub(crate) fn find_hash_descriptor(
         &self,
         partition_name: PartitionName,
-    ) -> Result<&HashDescriptor, avb::SlotVerifyError> {
+    ) -> SlotVerifyNoDataResult<&HashDescriptor> {
         self.hash_descriptors
             .iter()
             .find(|d| d.partition_name == partition_name)
-            .ok_or(avb::SlotVerifyError::InvalidMetadata)
+            .ok_or(SlotVerifyError::InvalidMetadata)
     }
 
     pub(crate) fn has_property_descriptor(&self) -> bool {
@@ -89,27 +81,24 @@
         self.prop_descriptor.as_ref().filter(|desc| desc.key == key).map(|desc| desc.value)
     }
 
-    fn push(&mut self, descriptor: Descriptor<'a>) -> utils::Result<()> {
+    fn push(&mut self, descriptor: Descriptor<'a>) -> IoResult<()> {
         match descriptor {
             Descriptor::Hash(d) => self.push_hash_descriptor(d),
             Descriptor::Property(d) => self.push_property_descriptor(d),
         }
     }
 
-    fn push_hash_descriptor(&mut self, descriptor: HashDescriptor<'a>) -> utils::Result<()> {
+    fn push_hash_descriptor(&mut self, descriptor: HashDescriptor<'a>) -> IoResult<()> {
         if self.hash_descriptors.iter().any(|d| d.partition_name == descriptor.partition_name) {
-            return Err(avb::IoError::Io);
+            return Err(IoError::Io);
         }
         self.hash_descriptors.push(descriptor);
         Ok(())
     }
 
-    fn push_property_descriptor(
-        &mut self,
-        descriptor: PropertyDescriptor<'a>,
-    ) -> utils::Result<()> {
+    fn push_property_descriptor(&mut self, descriptor: PropertyDescriptor<'a>) -> IoResult<()> {
         if self.prop_descriptor.is_some() {
-            return Err(avb::IoError::Io);
+            return Err(IoError::Io);
         }
         self.prop_descriptor.replace(descriptor);
         Ok(())
@@ -120,8 +109,7 @@
 ///
 /// Behavior is undefined if any of the following conditions are violated:
 /// * The `descriptor` pointer must be non-null and points to a valid `AvbDescriptor` struct.
-/// * The `user_data` pointer must be non-null, points to a valid
-///   `Result<Descriptors, avb::IoError>`
+/// * The `user_data` pointer must be non-null, points to a valid `IoResult<Descriptors>`
 ///  struct and is initialized.
 unsafe extern "C" fn check_and_save_descriptor(
     descriptor: *const AvbDescriptor,
@@ -129,8 +117,7 @@
 ) -> bool {
     // SAFETY: It is safe because the caller ensures that `user_data` points to a valid struct and
     // is initialized.
-    let Some(res) = (unsafe { (user_data as *mut Result<Descriptors, avb::IoError>).as_mut() })
-    else {
+    let Some(res) = (unsafe { (user_data as *mut IoResult<Descriptors>).as_mut() }) else {
         return false;
     };
     let Ok(descriptors) = res else {
@@ -154,7 +141,7 @@
 unsafe fn try_check_and_save_descriptor(
     descriptor: *const AvbDescriptor,
     descriptors: &mut Descriptors,
-) -> utils::Result<()> {
+) -> IoResult<()> {
     // SAFETY: It is safe because the caller ensures that `descriptor` is a non-null pointer
     // pointing to a valid struct.
     let descriptor = unsafe { Descriptor::from_descriptor_ptr(descriptor)? };
@@ -171,7 +158,7 @@
     ///
     /// Behavior is undefined if any of the following conditions are violated:
     /// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
-    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
         let avb_descriptor =
         // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
         // a valid `AvbDescriptor`.
@@ -197,7 +184,7 @@
                     unsafe { PropertyDescriptor::from_descriptor_ptr(descriptor, data)? };
                 Ok(Self::Property(descriptor))
             }
-            _ => Err(avb::IoError::NoSuchValue),
+            _ => Err(IoError::NoSuchValue),
         }
     }
 }
diff --git a/pvmfw/avb/src/descriptor/common.rs b/pvmfw/avb/src/descriptor/common.rs
index 31ee0a5..6063a7c 100644
--- a/pvmfw/avb/src/descriptor/common.rs
+++ b/pvmfw/avb/src/descriptor/common.rs
@@ -14,7 +14,8 @@
 
 //! Structs and functions used by all the descriptors.
 
-use crate::utils::{self, is_not_null};
+use crate::utils::is_not_null;
+use avb::{IoError, IoResult};
 use core::mem::MaybeUninit;
 
 /// # Safety
@@ -24,14 +25,14 @@
 pub(super) unsafe fn get_valid_descriptor<T>(
     descriptor_ptr: *const T,
     descriptor_validate_and_byteswap: unsafe extern "C" fn(src: *const T, dest: *mut T) -> bool,
-) -> utils::Result<T> {
+) -> IoResult<T> {
     is_not_null(descriptor_ptr)?;
     // SAFETY: It is safe because the caller ensures that `descriptor_ptr` is a non-null pointer
     // pointing to a valid struct.
     let descriptor = unsafe {
         let mut desc = MaybeUninit::uninit();
         if !descriptor_validate_and_byteswap(descriptor_ptr, desc.as_mut_ptr()) {
-            return Err(avb::IoError::Io);
+            return Err(IoError::Io);
         }
         desc.assume_init()
     };
diff --git a/pvmfw/avb/src/descriptor/hash.rs b/pvmfw/avb/src/descriptor/hash.rs
index 089268f..35db66d 100644
--- a/pvmfw/avb/src/descriptor/hash.rs
+++ b/pvmfw/avb/src/descriptor/hash.rs
@@ -16,7 +16,8 @@
 
 use super::common::get_valid_descriptor;
 use crate::partition::PartitionName;
-use crate::utils::{self, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
+use avb::{IoError, IoResult};
 use avb_bindgen::{
     avb_hash_descriptor_validate_and_byteswap, AvbDescriptor, AvbHashDescriptor,
     AVB_SHA256_DIGEST_SIZE,
@@ -47,19 +48,19 @@
     pub(super) unsafe fn from_descriptor_ptr(
         descriptor: *const AvbDescriptor,
         data: &'a [u8],
-    ) -> utils::Result<Self> {
+    ) -> IoResult<Self> {
         // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
         // a valid `AvbDescriptor`.
         let h = unsafe { HashDescriptorHeader::from_descriptor_ptr(descriptor)? };
         let partition_name = data
             .get(h.partition_name_range()?)
-            .ok_or(avb::IoError::RangeOutsidePartition)?
+            .ok_or(IoError::RangeOutsidePartition)?
             .try_into()?;
         let digest = data
             .get(h.digest_range()?)
-            .ok_or(avb::IoError::RangeOutsidePartition)?
+            .ok_or(IoError::RangeOutsidePartition)?
             .try_into()
-            .map_err(|_| avb::IoError::InvalidValueSize)?;
+            .map_err(|_| IoError::InvalidValueSize)?;
         Ok(Self { partition_name, digest })
     }
 }
@@ -71,7 +72,7 @@
     ///
     /// Behavior is undefined if any of the following conditions are violated:
     /// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
-    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
         // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
         // a valid `AvbDescriptor`.
         unsafe {
@@ -83,16 +84,16 @@
         }
     }
 
-    fn partition_name_end(&self) -> utils::Result<usize> {
+    fn partition_name_end(&self) -> IoResult<usize> {
         usize_checked_add(size_of::<AvbHashDescriptor>(), to_usize(self.0.partition_name_len)?)
     }
 
-    fn partition_name_range(&self) -> utils::Result<Range<usize>> {
+    fn partition_name_range(&self) -> IoResult<Range<usize>> {
         let start = size_of::<AvbHashDescriptor>();
         Ok(start..(self.partition_name_end()?))
     }
 
-    fn digest_range(&self) -> utils::Result<Range<usize>> {
+    fn digest_range(&self) -> IoResult<Range<usize>> {
         let start = usize_checked_add(self.partition_name_end()?, to_usize(self.0.salt_len)?)?;
         let end = usize_checked_add(start, to_usize(self.0.digest_len)?)?;
         Ok(start..end)
diff --git a/pvmfw/avb/src/descriptor/property.rs b/pvmfw/avb/src/descriptor/property.rs
index 336623a..8145d64 100644
--- a/pvmfw/avb/src/descriptor/property.rs
+++ b/pvmfw/avb/src/descriptor/property.rs
@@ -15,7 +15,8 @@
 //! Structs and functions relating to the property descriptor.
 
 use super::common::get_valid_descriptor;
-use crate::utils::{self, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
+use avb::{IoError, IoResult};
 use avb_bindgen::{
     avb_property_descriptor_validate_and_byteswap, AvbDescriptor, AvbPropertyDescriptor,
 };
@@ -34,7 +35,7 @@
     pub(super) unsafe fn from_descriptor_ptr(
         descriptor: *const AvbDescriptor,
         data: &'a [u8],
-    ) -> utils::Result<Self> {
+    ) -> IoResult<Self> {
         // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
         // a valid `AvbDescriptor`.
         let h = unsafe { PropertyDescriptorHeader::from_descriptor_ptr(descriptor)? };
@@ -43,12 +44,12 @@
         Ok(Self { key, value })
     }
 
-    fn get_valid_slice(data: &[u8], start: usize, end: usize) -> utils::Result<&[u8]> {
+    fn get_valid_slice(data: &[u8], start: usize, end: usize) -> IoResult<&[u8]> {
         const NUL_BYTE: u8 = b'\0';
 
         match data.get(end) {
-            Some(&NUL_BYTE) => data.get(start..end).ok_or(avb::IoError::RangeOutsidePartition),
-            _ => Err(avb::IoError::NoSuchValue),
+            Some(&NUL_BYTE) => data.get(start..end).ok_or(IoError::RangeOutsidePartition),
+            _ => Err(IoError::NoSuchValue),
         }
     }
 }
@@ -60,7 +61,7 @@
     ///
     /// Behavior is undefined if any of the following conditions are violated:
     /// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
-    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+    unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
         // SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
         // a valid `AvbDescriptor`.
         unsafe {
@@ -76,16 +77,16 @@
         size_of::<AvbPropertyDescriptor>()
     }
 
-    fn key_end(&self) -> utils::Result<usize> {
+    fn key_end(&self) -> IoResult<usize> {
         usize_checked_add(self.key_start(), to_usize(self.0.key_num_bytes)?)
     }
 
-    fn value_start(&self) -> utils::Result<usize> {
+    fn value_start(&self) -> IoResult<usize> {
         // There is a NUL byte between key and value.
         usize_checked_add(self.key_end()?, 1)
     }
 
-    fn value_end(&self) -> utils::Result<usize> {
+    fn value_end(&self) -> IoResult<usize> {
         usize_checked_add(self.value_start()?, to_usize(self.0.value_num_bytes)?)
     }
 }
diff --git a/pvmfw/avb/src/error.rs b/pvmfw/avb/src/error.rs
index 0f052e8..4e3f27e 100644
--- a/pvmfw/avb/src/error.rs
+++ b/pvmfw/avb/src/error.rs
@@ -15,22 +15,23 @@
 //! This module contains the error thrown by the payload verification API
 //! and other errors used in the library.
 
+use avb::{IoError, SlotVerifyError};
 use core::fmt;
 
-/// Wrapper around `avb::SlotVerifyError` to add custom pvmfw errors.
+/// Wrapper around `SlotVerifyError` to add custom pvmfw errors.
 /// It is the error thrown by the payload verification API `verify_payload()`.
 #[derive(Debug, PartialEq, Eq)]
 pub enum PvmfwVerifyError {
-    /// Passthrough `avb::SlotVerifyError` with no `SlotVerifyData`.
-    AvbError(avb::SlotVerifyError<'static>),
+    /// Passthrough `SlotVerifyError` with no `SlotVerifyData`.
+    AvbError(SlotVerifyError<'static>),
     /// VBMeta has invalid descriptors.
-    InvalidDescriptors(avb::IoError),
+    InvalidDescriptors(IoError),
     /// Unknown vbmeta property.
     UnknownVbmetaProperty,
 }
 
-impl From<avb::SlotVerifyError<'_>> for PvmfwVerifyError {
-    fn from(error: avb::SlotVerifyError) -> Self {
+impl From<SlotVerifyError<'_>> for PvmfwVerifyError {
+    fn from(error: SlotVerifyError) -> Self {
         // We don't use verification data on failure, drop it to get a `'static` lifetime.
         Self::AvbError(error.without_verify_data())
     }
diff --git a/pvmfw/avb/src/ops.rs b/pvmfw/avb/src/ops.rs
index aee93c8..9711f72 100644
--- a/pvmfw/avb/src/ops.rs
+++ b/pvmfw/avb/src/ops.rs
@@ -12,22 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Structs and functions relating to `AvbOps`.
+//! Structs and functions relating to AVB callback operations.
 
 use crate::partition::PartitionName;
-use crate::utils::{self, as_ref, is_not_null, to_nonnull, write};
-use avb::internal::{result_to_io_enum, slot_verify_enum_to_result};
-use avb_bindgen::{
-    avb_slot_verify, avb_slot_verify_data_free, AvbHashtreeErrorMode, AvbIOResult, AvbOps,
-    AvbPartitionData, AvbSlotVerifyData, AvbSlotVerifyFlags, AvbVBMetaData,
+use avb::{
+    slot_verify, HashtreeErrorMode, IoError, IoResult, PublicKeyForPartitionInfo, SlotVerifyData,
+    SlotVerifyFlags, SlotVerifyResult,
 };
-use core::{
-    ffi::{c_char, c_void, CStr},
-    mem::MaybeUninit,
-    ptr, slice,
-};
-
-const NULL_BYTE: &[u8] = b"\0";
+use core::ffi::CStr;
 
 pub(crate) struct Payload<'a> {
     kernel: &'a [u8],
@@ -35,15 +27,6 @@
     trusted_public_key: &'a [u8],
 }
 
-impl<'a> AsRef<Payload<'a>> for AvbOps {
-    fn as_ref(&self) -> &Payload<'a> {
-        let payload = self.user_data as *const Payload;
-        // SAFETY: It is safe to cast the `AvbOps.user_data` to Payload as we have saved a
-        // pointer to a valid value of Payload in user_data when creating AvbOps.
-        unsafe { &*payload }
-    }
-}
-
 impl<'a> Payload<'a> {
     pub(crate) fn new(
         kernel: &'a [u8],
@@ -53,148 +36,116 @@
         Self { kernel, initrd, trusted_public_key }
     }
 
-    fn get_partition(&self, partition_name: *const c_char) -> Result<&[u8], avb::IoError> {
+    fn get_partition(&self, partition_name: &CStr) -> IoResult<&[u8]> {
         match partition_name.try_into()? {
             PartitionName::Kernel => Ok(self.kernel),
             PartitionName::InitrdNormal | PartitionName::InitrdDebug => {
-                self.initrd.ok_or(avb::IoError::NoSuchPartition)
+                self.initrd.ok_or(IoError::NoSuchPartition)
             }
         }
     }
 }
 
-/// `Ops` wraps the class `AvbOps` in libavb. It provides pvmfw customized
-/// operations used in the verification.
-pub(crate) struct Ops(AvbOps);
-
-impl<'a> From<&mut Payload<'a>> for Ops {
-    fn from(payload: &mut Payload<'a>) -> Self {
-        let avb_ops = AvbOps {
-            user_data: payload as *mut _ as *mut c_void,
-            ab_ops: ptr::null_mut(),
-            atx_ops: ptr::null_mut(),
-            read_from_partition: Some(read_from_partition),
-            get_preloaded_partition: Some(get_preloaded_partition),
-            write_to_partition: None,
-            validate_vbmeta_public_key: Some(validate_vbmeta_public_key),
-            read_rollback_index: Some(read_rollback_index),
-            write_rollback_index: None,
-            read_is_device_unlocked: Some(read_is_device_unlocked),
-            get_unique_guid_for_partition: Some(get_unique_guid_for_partition),
-            get_size_of_partition: Some(get_size_of_partition),
-            read_persistent_value: None,
-            write_persistent_value: None,
-            validate_public_key_for_partition: None,
-        };
-        Self(avb_ops)
-    }
+/// Pvmfw customized operations used in the verification.
+pub(crate) struct Ops<'a> {
+    payload: &'a Payload<'a>,
 }
 
-impl Ops {
+impl<'a> Ops<'a> {
+    pub(crate) fn new(payload: &'a Payload<'a>) -> Self {
+        Self { payload }
+    }
+
     pub(crate) fn verify_partition(
         &mut self,
         partition_name: &CStr,
-    ) -> Result<AvbSlotVerifyDataWrap, avb::SlotVerifyError<'static>> {
-        let requested_partitions = [partition_name.as_ptr(), ptr::null()];
-        let ab_suffix = CStr::from_bytes_with_nul(NULL_BYTE).unwrap();
-        let mut out_data = MaybeUninit::uninit();
-        // SAFETY: It is safe to call `avb_slot_verify()` as the pointer arguments (`ops`,
-        // `requested_partitions` and `ab_suffix`) passed to the method are all valid and
-        // initialized.
-        let result = unsafe {
-            avb_slot_verify(
-                &mut self.0,
-                requested_partitions.as_ptr(),
-                ab_suffix.as_ptr(),
-                AvbSlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
-                AvbHashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
-                out_data.as_mut_ptr(),
-            )
-        };
-        slot_verify_enum_to_result(result)?;
-        // SAFETY: This is safe because `out_data` has been properly initialized after
-        // calling `avb_slot_verify` and it returns OK.
-        let out_data = unsafe { out_data.assume_init() };
-        out_data.try_into()
+    ) -> SlotVerifyResult<SlotVerifyData> {
+        slot_verify(
+            self,
+            &[partition_name],
+            None, // No partition slot suffix.
+            SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
+            HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+        )
     }
 }
 
-extern "C" fn read_is_device_unlocked(
-    _ops: *mut AvbOps,
-    out_is_unlocked: *mut bool,
-) -> AvbIOResult {
-    result_to_io_enum(write(out_is_unlocked, false))
+impl<'a> avb::Ops for Ops<'a> {
+    fn read_from_partition(
+        &mut self,
+        partition: &CStr,
+        offset: i64,
+        buffer: &mut [u8],
+    ) -> IoResult<usize> {
+        let partition = self.payload.get_partition(partition)?;
+        copy_data_to_dst(partition, offset, buffer)?;
+        Ok(buffer.len())
+    }
+
+    fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&[u8]> {
+        self.payload.get_partition(partition)
+    }
+
+    fn validate_vbmeta_public_key(
+        &mut self,
+        public_key: &[u8],
+        _public_key_metadata: Option<&[u8]>,
+    ) -> IoResult<bool> {
+        // The public key metadata is not used when we build the VBMeta.
+        Ok(self.payload.trusted_public_key == public_key)
+    }
+
+    fn read_rollback_index(&mut self, _rollback_index_location: usize) -> IoResult<u64> {
+        // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
+        // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback
+        // protection. Hence, we set `out_rollback_index` to 0 to ensure that the rollback_index
+        // (including default: 0) is never smaller than it, thus the rollback index check will pass.
+        Ok(0)
+    }
+
+    fn write_rollback_index(
+        &mut self,
+        _rollback_index_location: usize,
+        _index: u64,
+    ) -> IoResult<()> {
+        Err(IoError::NotImplemented)
+    }
+
+    fn read_is_device_unlocked(&mut self) -> IoResult<bool> {
+        Ok(false)
+    }
+
+    fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> {
+        let partition = self.payload.get_partition(partition)?;
+        u64::try_from(partition.len()).map_err(|_| IoError::InvalidValueSize)
+    }
+
+    fn read_persistent_value(&mut self, _name: &CStr, _value: &mut [u8]) -> IoResult<usize> {
+        Err(IoError::NotImplemented)
+    }
+
+    fn write_persistent_value(&mut self, _name: &CStr, _value: &[u8]) -> IoResult<()> {
+        Err(IoError::NotImplemented)
+    }
+
+    fn erase_persistent_value(&mut self, _name: &CStr) -> IoResult<()> {
+        Err(IoError::NotImplemented)
+    }
+
+    fn validate_public_key_for_partition(
+        &mut self,
+        _partition: &CStr,
+        _public_key: &[u8],
+        _public_key_metadata: Option<&[u8]>,
+    ) -> IoResult<PublicKeyForPartitionInfo> {
+        Err(IoError::NotImplemented)
+    }
 }
 
-extern "C" fn get_preloaded_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    num_bytes: usize,
-    out_pointer: *mut *mut u8,
-    out_num_bytes_preloaded: *mut usize,
-) -> AvbIOResult {
-    result_to_io_enum(try_get_preloaded_partition(
-        ops,
-        partition,
-        num_bytes,
-        out_pointer,
-        out_num_bytes_preloaded,
-    ))
-}
-
-fn try_get_preloaded_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    num_bytes: usize,
-    out_pointer: *mut *mut u8,
-    out_num_bytes_preloaded: *mut usize,
-) -> utils::Result<()> {
-    let ops = as_ref(ops)?;
-    let partition = ops.as_ref().get_partition(partition)?;
-    write(out_pointer, partition.as_ptr() as *mut u8)?;
-    write(out_num_bytes_preloaded, partition.len().min(num_bytes))
-}
-
-extern "C" fn read_from_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    offset: i64,
-    num_bytes: usize,
-    buffer: *mut c_void,
-    out_num_read: *mut usize,
-) -> AvbIOResult {
-    result_to_io_enum(try_read_from_partition(
-        ops,
-        partition,
-        offset,
-        num_bytes,
-        buffer,
-        out_num_read,
-    ))
-}
-
-fn try_read_from_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    offset: i64,
-    num_bytes: usize,
-    buffer: *mut c_void,
-    out_num_read: *mut usize,
-) -> utils::Result<()> {
-    let ops = as_ref(ops)?;
-    let partition = ops.as_ref().get_partition(partition)?;
-    let buffer = to_nonnull(buffer)?;
-    // SAFETY: It is safe to copy the requested number of bytes to `buffer` as `buffer`
-    // is created to point to the `num_bytes` of bytes in memory.
-    let buffer_slice = unsafe { slice::from_raw_parts_mut(buffer.as_ptr() as *mut u8, num_bytes) };
-    copy_data_to_dst(partition, offset, buffer_slice)?;
-    write(out_num_read, buffer_slice.len())
-}
-
-fn copy_data_to_dst(src: &[u8], offset: i64, dst: &mut [u8]) -> utils::Result<()> {
-    let start = to_copy_start(offset, src.len()).ok_or(avb::IoError::InvalidValueSize)?;
-    let end = start.checked_add(dst.len()).ok_or(avb::IoError::InvalidValueSize)?;
-    dst.copy_from_slice(src.get(start..end).ok_or(avb::IoError::RangeOutsidePartition)?);
+fn copy_data_to_dst(src: &[u8], offset: i64, dst: &mut [u8]) -> IoResult<()> {
+    let start = to_copy_start(offset, src.len()).ok_or(IoError::InvalidValueSize)?;
+    let end = start.checked_add(dst.len()).ok_or(IoError::InvalidValueSize)?;
+    dst.copy_from_slice(src.get(start..end).ok_or(IoError::RangeOutsidePartition)?);
     Ok(())
 }
 
@@ -203,143 +154,3 @@
         .ok()
         .or_else(|| isize::try_from(offset).ok().and_then(|v| len.checked_add_signed(v)))
 }
-
-extern "C" fn get_size_of_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    out_size_num_bytes: *mut u64,
-) -> AvbIOResult {
-    result_to_io_enum(try_get_size_of_partition(ops, partition, out_size_num_bytes))
-}
-
-fn try_get_size_of_partition(
-    ops: *mut AvbOps,
-    partition: *const c_char,
-    out_size_num_bytes: *mut u64,
-) -> utils::Result<()> {
-    let ops = as_ref(ops)?;
-    let partition = ops.as_ref().get_partition(partition)?;
-    let partition_size =
-        u64::try_from(partition.len()).map_err(|_| avb::IoError::InvalidValueSize)?;
-    write(out_size_num_bytes, partition_size)
-}
-
-extern "C" fn read_rollback_index(
-    _ops: *mut AvbOps,
-    _rollback_index_location: usize,
-    out_rollback_index: *mut u64,
-) -> AvbIOResult {
-    // This method is used by `avb_slot_verify()` to read the stored_rollback_index at
-    // rollback_index_location.
-
-    // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
-    // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback protection
-    // Hence, we set `out_rollback_index` to 0 to ensure that the
-    // rollback_index (including default: 0) is never smaller than it,
-    // thus the rollback index check will pass.
-    result_to_io_enum(write(out_rollback_index, 0))
-}
-
-extern "C" fn get_unique_guid_for_partition(
-    _ops: *mut AvbOps,
-    _partition: *const c_char,
-    _guid_buf: *mut c_char,
-    _guid_buf_size: usize,
-) -> AvbIOResult {
-    // TODO(b/256148034): Check if it's possible to throw an error here instead of having
-    // an empty method.
-    // This method is required by `avb_slot_verify()`.
-    AvbIOResult::AVB_IO_RESULT_OK
-}
-
-extern "C" fn validate_vbmeta_public_key(
-    ops: *mut AvbOps,
-    public_key_data: *const u8,
-    public_key_length: usize,
-    public_key_metadata: *const u8,
-    public_key_metadata_length: usize,
-    out_is_trusted: *mut bool,
-) -> AvbIOResult {
-    result_to_io_enum(try_validate_vbmeta_public_key(
-        ops,
-        public_key_data,
-        public_key_length,
-        public_key_metadata,
-        public_key_metadata_length,
-        out_is_trusted,
-    ))
-}
-
-fn try_validate_vbmeta_public_key(
-    ops: *mut AvbOps,
-    public_key_data: *const u8,
-    public_key_length: usize,
-    _public_key_metadata: *const u8,
-    _public_key_metadata_length: usize,
-    out_is_trusted: *mut bool,
-) -> utils::Result<()> {
-    // The public key metadata is not used when we build the VBMeta.
-    is_not_null(public_key_data)?;
-    // SAFETY: It is safe to create a slice with the given pointer and length as
-    // `public_key_data` is a valid pointer and it points to an array of length
-    // `public_key_length`.
-    let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) };
-    let ops = as_ref(ops)?;
-    let trusted_public_key = ops.as_ref().trusted_public_key;
-    write(out_is_trusted, public_key == trusted_public_key)
-}
-
-pub(crate) struct AvbSlotVerifyDataWrap(*mut AvbSlotVerifyData);
-
-impl TryFrom<*mut AvbSlotVerifyData> for AvbSlotVerifyDataWrap {
-    type Error = avb::SlotVerifyError<'static>;
-
-    fn try_from(data: *mut AvbSlotVerifyData) -> Result<Self, Self::Error> {
-        is_not_null(data).map_err(|_| avb::SlotVerifyError::Io)?;
-        Ok(Self(data))
-    }
-}
-
-impl Drop for AvbSlotVerifyDataWrap {
-    fn drop(&mut self) {
-        // SAFETY: This is safe because `self.0` is checked nonnull when the
-        // instance is created. We can free this pointer when the instance is
-        // no longer needed.
-        unsafe {
-            avb_slot_verify_data_free(self.0);
-        }
-    }
-}
-
-impl AsRef<AvbSlotVerifyData> for AvbSlotVerifyDataWrap {
-    fn as_ref(&self) -> &AvbSlotVerifyData {
-        // This is safe because `self.0` is checked nonnull when the instance is created.
-        as_ref(self.0).unwrap()
-    }
-}
-
-impl AvbSlotVerifyDataWrap {
-    pub(crate) fn vbmeta_images(&self) -> Result<&[AvbVBMetaData], avb::SlotVerifyError> {
-        let data = self.as_ref();
-        is_not_null(data.vbmeta_images).map_err(|_| avb::SlotVerifyError::Io)?;
-        let vbmeta_images =
-        // SAFETY: It is safe as the raw pointer `data.vbmeta_images` is a nonnull pointer.
-            unsafe { slice::from_raw_parts(data.vbmeta_images, data.num_vbmeta_images) };
-        Ok(vbmeta_images)
-    }
-
-    pub(crate) fn loaded_partitions(&self) -> Result<&[AvbPartitionData], avb::SlotVerifyError> {
-        let data = self.as_ref();
-        is_not_null(data.loaded_partitions).map_err(|_| avb::SlotVerifyError::Io)?;
-        let loaded_partitions =
-        // SAFETY: It is safe as the raw pointer `data.loaded_partitions` is a nonnull pointer and
-        // is guaranteed by libavb to point to a valid `AvbPartitionData` array as part of the
-        // `AvbSlotVerifyData` struct.
-            unsafe { slice::from_raw_parts(data.loaded_partitions, data.num_loaded_partitions) };
-        Ok(loaded_partitions)
-    }
-
-    pub(crate) fn rollback_indexes(&self) -> &[u64] {
-        &self.as_ref().rollback_indexes
-    }
-}
diff --git a/pvmfw/avb/src/partition.rs b/pvmfw/avb/src/partition.rs
index ca450c9..c05a0ac 100644
--- a/pvmfw/avb/src/partition.rs
+++ b/pvmfw/avb/src/partition.rs
@@ -14,8 +14,8 @@
 
 //! Struct and functions relating to well-known partition names.
 
-use crate::utils::is_not_null;
-use core::ffi::{c_char, CStr};
+use avb::IoError;
+use core::ffi::CStr;
 
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 pub(crate) enum PartitionName {
@@ -29,9 +29,9 @@
 impl PartitionName {
     pub(crate) const NUM_OF_KNOWN_PARTITIONS: usize = 3;
 
-    const KERNEL_PARTITION_NAME: &[u8] = b"boot\0";
-    const INITRD_NORMAL_PARTITION_NAME: &[u8] = b"initrd_normal\0";
-    const INITRD_DEBUG_PARTITION_NAME: &[u8] = b"initrd_debug\0";
+    const KERNEL_PARTITION_NAME: &'static [u8] = b"boot\0";
+    const INITRD_NORMAL_PARTITION_NAME: &'static [u8] = b"initrd_normal\0";
+    const INITRD_DEBUG_PARTITION_NAME: &'static [u8] = b"initrd_debug\0";
 
     pub(crate) fn as_cstr(&self) -> &CStr {
         CStr::from_bytes_with_nul(self.as_bytes()).unwrap()
@@ -51,39 +51,28 @@
     }
 }
 
-impl TryFrom<*const c_char> for PartitionName {
-    type Error = avb::IoError;
-
-    fn try_from(partition_name: *const c_char) -> Result<Self, Self::Error> {
-        is_not_null(partition_name)?;
-        // SAFETY: It is safe as the raw pointer `partition_name` is a nonnull pointer.
-        let partition_name = unsafe { CStr::from_ptr(partition_name) };
-        partition_name.try_into()
-    }
-}
-
 impl TryFrom<&CStr> for PartitionName {
-    type Error = avb::IoError;
+    type Error = IoError;
 
     fn try_from(partition_name: &CStr) -> Result<Self, Self::Error> {
         match partition_name.to_bytes_with_nul() {
             Self::KERNEL_PARTITION_NAME => Ok(Self::Kernel),
             Self::INITRD_NORMAL_PARTITION_NAME => Ok(Self::InitrdNormal),
             Self::INITRD_DEBUG_PARTITION_NAME => Ok(Self::InitrdDebug),
-            _ => Err(avb::IoError::NoSuchPartition),
+            _ => Err(IoError::NoSuchPartition),
         }
     }
 }
 
 impl TryFrom<&[u8]> for PartitionName {
-    type Error = avb::IoError;
+    type Error = IoError;
 
     fn try_from(non_null_terminated_name: &[u8]) -> Result<Self, Self::Error> {
         match non_null_terminated_name {
             x if x == Self::Kernel.as_non_null_terminated_bytes() => Ok(Self::Kernel),
             x if x == Self::InitrdNormal.as_non_null_terminated_bytes() => Ok(Self::InitrdNormal),
             x if x == Self::InitrdDebug.as_non_null_terminated_bytes() => Ok(Self::InitrdDebug),
-            _ => Err(avb::IoError::NoSuchPartition),
+            _ => Err(IoError::NoSuchPartition),
         }
     }
 }
diff --git a/pvmfw/avb/src/utils.rs b/pvmfw/avb/src/utils.rs
index f4f15e1..b4f099b 100644
--- a/pvmfw/avb/src/utils.rs
+++ b/pvmfw/avb/src/utils.rs
@@ -14,42 +14,20 @@
 
 //! Common utility functions.
 
-use core::ptr::NonNull;
-use core::result;
+use avb::{IoError, IoResult};
 
-pub(crate) type Result<T> = result::Result<T, avb::IoError>;
-
-pub(crate) fn write<T>(ptr: *mut T, value: T) -> Result<()> {
-    let ptr = to_nonnull(ptr)?;
-    // SAFETY: It is safe as the raw pointer `ptr` is a non-null pointer.
-    unsafe {
-        *ptr.as_ptr() = value;
-    }
-    Ok(())
-}
-
-pub(crate) fn as_ref<'a, T>(ptr: *mut T) -> Result<&'a T> {
-    let ptr = to_nonnull(ptr)?;
-    // SAFETY: It is safe as the raw pointer `ptr` is a non-null pointer.
-    unsafe { Ok(ptr.as_ref()) }
-}
-
-pub(crate) fn to_nonnull<T>(ptr: *mut T) -> Result<NonNull<T>> {
-    NonNull::new(ptr).ok_or(avb::IoError::NoSuchValue)
-}
-
-pub(crate) fn is_not_null<T>(ptr: *const T) -> Result<()> {
+pub(crate) fn is_not_null<T>(ptr: *const T) -> IoResult<()> {
     if ptr.is_null() {
-        Err(avb::IoError::NoSuchValue)
+        Err(IoError::NoSuchValue)
     } else {
         Ok(())
     }
 }
 
-pub(crate) fn to_usize<T: TryInto<usize>>(num: T) -> Result<usize> {
-    num.try_into().map_err(|_| avb::IoError::InvalidValueSize)
+pub(crate) fn to_usize<T: TryInto<usize>>(num: T) -> IoResult<usize> {
+    num.try_into().map_err(|_| IoError::InvalidValueSize)
 }
 
-pub(crate) fn usize_checked_add(x: usize, y: usize) -> Result<usize> {
-    x.checked_add(y).ok_or(avb::IoError::InvalidValueSize)
+pub(crate) fn usize_checked_add(x: usize, y: usize) -> IoResult<usize> {
+    x.checked_add(y).ok_or(IoError::InvalidValueSize)
 }
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 3274033..a85dbbb 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -20,10 +20,9 @@
 use crate::PvmfwVerifyError;
 use alloc::vec;
 use alloc::vec::Vec;
-use avb_bindgen::{AvbPartitionData, AvbVBMetaData};
-use core::ffi::c_char;
+use avb::{PartitionData, SlotVerifyError, SlotVerifyNoDataResult, VbmetaData};
 
-// We use this for the rollback_index field if AvbSlotVerifyDataWrap has empty rollback_indexes
+// We use this for the rollback_index field if SlotVerifyData has empty rollback_indexes
 const DEFAULT_ROLLBACK_INDEX: u64 = 0;
 
 /// Verified data returned when the payload verification succeeds.
@@ -69,9 +68,9 @@
 }
 
 impl Capability {
-    const KEY: &[u8] = b"com.android.virt.cap";
-    const REMOTE_ATTEST: &[u8] = b"remote_attest";
-    const SECRETKEEPER_PROTECTION: &[u8] = b"secretkeeper_protection";
+    const KEY: &'static [u8] = b"com.android.virt.cap";
+    const REMOTE_ATTEST: &'static [u8] = b"remote_attest";
+    const SECRETKEEPER_PROTECTION: &'static [u8] = b"secretkeeper_protection";
     const SEPARATOR: u8 = b'|';
 
     fn get_capabilities(property_value: &[u8]) -> Result<Vec<Self>, PvmfwVerifyError> {
@@ -84,7 +83,7 @@
                 _ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
             };
             if res.contains(&cap) {
-                return Err(avb::SlotVerifyError::InvalidMetadata.into());
+                return Err(SlotVerifyError::InvalidMetadata.into());
             }
             res.push(cap);
         }
@@ -92,55 +91,51 @@
     }
 }
 
-fn verify_only_one_vbmeta_exists(
-    vbmeta_images: &[AvbVBMetaData],
-) -> Result<(), avb::SlotVerifyError<'static>> {
-    if vbmeta_images.len() == 1 {
+fn verify_only_one_vbmeta_exists(vbmeta_data: &[VbmetaData]) -> SlotVerifyNoDataResult<()> {
+    if vbmeta_data.len() == 1 {
         Ok(())
     } else {
-        Err(avb::SlotVerifyError::InvalidMetadata)
+        Err(SlotVerifyError::InvalidMetadata)
     }
 }
 
-fn verify_vbmeta_is_from_kernel_partition(
-    vbmeta_image: &AvbVBMetaData,
-) -> Result<(), avb::SlotVerifyError<'static>> {
-    match (vbmeta_image.partition_name as *const c_char).try_into() {
+fn verify_vbmeta_is_from_kernel_partition(vbmeta_image: &VbmetaData) -> SlotVerifyNoDataResult<()> {
+    match vbmeta_image.partition_name().try_into() {
         Ok(PartitionName::Kernel) => Ok(()),
-        _ => Err(avb::SlotVerifyError::InvalidMetadata),
+        _ => Err(SlotVerifyError::InvalidMetadata),
     }
 }
 
 fn verify_vbmeta_has_only_one_hash_descriptor(
     descriptors: &Descriptors,
-) -> Result<(), avb::SlotVerifyError<'static>> {
+) -> SlotVerifyNoDataResult<()> {
     if descriptors.num_hash_descriptor() == 1 {
         Ok(())
     } else {
-        Err(avb::SlotVerifyError::InvalidMetadata)
+        Err(SlotVerifyError::InvalidMetadata)
     }
 }
 
 fn verify_loaded_partition_has_expected_length(
-    loaded_partitions: &[AvbPartitionData],
+    loaded_partitions: &[PartitionData],
     partition_name: PartitionName,
     expected_len: usize,
-) -> Result<(), avb::SlotVerifyError<'static>> {
+) -> SlotVerifyNoDataResult<()> {
     if loaded_partitions.len() != 1 {
         // Only one partition should be loaded in each verify result.
-        return Err(avb::SlotVerifyError::Io);
+        return Err(SlotVerifyError::Io);
     }
-    let loaded_partition = loaded_partitions[0];
-    if !PartitionName::try_from(loaded_partition.partition_name as *const c_char)
+    let loaded_partition = &loaded_partitions[0];
+    if !PartitionName::try_from(loaded_partition.partition_name())
         .map_or(false, |p| p == partition_name)
     {
         // Only the requested partition should be loaded.
-        return Err(avb::SlotVerifyError::Io);
+        return Err(SlotVerifyError::Io);
     }
-    if loaded_partition.data_size == expected_len {
+    if loaded_partition.data().len() == expected_len {
         Ok(())
     } else {
-        Err(avb::SlotVerifyError::Verification(None))
+        Err(SlotVerifyError::Verification(None))
     }
 }
 
@@ -158,28 +153,40 @@
         .and_then(Capability::get_capabilities)
 }
 
+/// Verifies the given initrd partition, and checks that the resulting contents looks like expected.
+fn verify_initrd(
+    ops: &mut Ops,
+    partition_name: PartitionName,
+    expected_initrd: &[u8],
+) -> SlotVerifyNoDataResult<()> {
+    let result =
+        ops.verify_partition(partition_name.as_cstr()).map_err(|e| e.without_verify_data())?;
+    verify_loaded_partition_has_expected_length(
+        result.partition_data(),
+        partition_name,
+        expected_initrd.len(),
+    )
+}
+
 /// Verifies the payload (signed kernel + initrd) against the trusted public key.
 pub fn verify_payload<'a>(
     kernel: &[u8],
     initrd: Option<&[u8]>,
     trusted_public_key: &'a [u8],
 ) -> Result<VerifiedBootData<'a>, PvmfwVerifyError> {
-    let mut payload = Payload::new(kernel, initrd, trusted_public_key);
-    let mut ops = Ops::from(&mut payload);
+    let payload = Payload::new(kernel, initrd, trusted_public_key);
+    let mut ops = Ops::new(&payload);
     let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
 
-    let vbmeta_images = kernel_verify_result.vbmeta_images()?;
+    let vbmeta_images = kernel_verify_result.vbmeta_data();
     // TODO(b/302093437): Use explicit rollback_index_location instead of default
     // location (first element).
     let rollback_index =
         *kernel_verify_result.rollback_indexes().first().unwrap_or(&DEFAULT_ROLLBACK_INDEX);
     verify_only_one_vbmeta_exists(vbmeta_images)?;
-    let vbmeta_image = vbmeta_images[0];
-    verify_vbmeta_is_from_kernel_partition(&vbmeta_image)?;
-    // SAFETY: It is safe because the `vbmeta_image` is collected from `AvbSlotVerifyData`,
-    // which is returned by `avb_slot_verify()` when the verification succeeds. It is
-    // guaranteed by libavb to be non-null and to point to a valid VBMeta structure.
-    let descriptors = unsafe { Descriptors::from_vbmeta(vbmeta_image)? };
+    let vbmeta_image = &vbmeta_images[0];
+    verify_vbmeta_is_from_kernel_partition(vbmeta_image)?;
+    let descriptors = Descriptors::from_vbmeta(vbmeta_image)?;
     let capabilities = verify_property_and_get_capabilities(&descriptors)?;
     let kernel_descriptor = descriptors.find_hash_descriptor(PartitionName::Kernel)?;
 
@@ -196,20 +203,15 @@
     }
 
     let initrd = initrd.unwrap();
-    let (debug_level, initrd_verify_result, initrd_partition_name) =
-        if let Ok(result) = ops.verify_partition(PartitionName::InitrdNormal.as_cstr()) {
-            (DebugLevel::None, result, PartitionName::InitrdNormal)
-        } else if let Ok(result) = ops.verify_partition(PartitionName::InitrdDebug.as_cstr()) {
-            (DebugLevel::Full, result, PartitionName::InitrdDebug)
+    let mut initrd_ops = Ops::new(&payload);
+    let (debug_level, initrd_partition_name) =
+        if verify_initrd(&mut initrd_ops, PartitionName::InitrdNormal, initrd).is_ok() {
+            (DebugLevel::None, PartitionName::InitrdNormal)
+        } else if verify_initrd(&mut initrd_ops, PartitionName::InitrdDebug, initrd).is_ok() {
+            (DebugLevel::Full, PartitionName::InitrdDebug)
         } else {
-            return Err(avb::SlotVerifyError::Verification(None).into());
+            return Err(SlotVerifyError::Verification(None).into());
         };
-    let loaded_partitions = initrd_verify_result.loaded_partitions()?;
-    verify_loaded_partition_has_expected_length(
-        loaded_partitions,
-        initrd_partition_name,
-        initrd.len(),
-    )?;
     let initrd_descriptor = descriptors.find_hash_descriptor(initrd_partition_name)?;
     Ok(VerifiedBootData {
         debug_level,
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index 84f83c2..6dc5a0a 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -17,6 +17,7 @@
 mod utils;
 
 use anyhow::{anyhow, Result};
+use avb::{IoError, SlotVerifyError};
 use avb_bindgen::{AvbFooter, AvbVBMetaImageHeader};
 use pvmfw_avb::{verify_payload, Capability, DebugLevel, PvmfwVerifyError, VerifiedBootData};
 use std::{fs, mem::size_of, ptr};
@@ -87,7 +88,7 @@
         &fs::read(TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH)?,
         /* initrd= */ None,
         &load_trusted_public_key()?,
-        PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
+        PvmfwVerifyError::InvalidDescriptors(IoError::NoSuchPartition),
     )
 }
 
@@ -97,7 +98,7 @@
         &fs::read(TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH)?,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
+        PvmfwVerifyError::InvalidDescriptors(IoError::NoSuchPartition),
     )
 }
 
@@ -141,7 +142,7 @@
         &fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
         /* initrd= */ None,
         &load_trusted_public_key()?,
-        PvmfwVerifyError::InvalidDescriptors(avb::IoError::Io),
+        PvmfwVerifyError::InvalidDescriptors(IoError::Io),
     )
 }
 
@@ -151,7 +152,7 @@
         &fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
         /* initrd= */ None,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::InvalidMetadata.into(),
+        SlotVerifyError::InvalidMetadata.into(),
     )
 }
 
@@ -171,7 +172,7 @@
         &load_latest_signed_kernel()?,
         /* initrd= */ None,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::InvalidMetadata.into(),
+        SlotVerifyError::InvalidMetadata.into(),
     )
 }
 
@@ -181,7 +182,7 @@
         &load_latest_signed_kernel()?,
         &load_latest_initrd_normal()?,
         /* trusted_public_key= */ &[0u8; 0],
-        avb::SlotVerifyError::PublicKeyRejected.into(),
+        SlotVerifyError::PublicKeyRejected.into(),
     )
 }
 
@@ -191,7 +192,7 @@
         &load_latest_signed_kernel()?,
         &load_latest_initrd_normal()?,
         /* trusted_public_key= */ &[0u8; 512],
-        avb::SlotVerifyError::PublicKeyRejected.into(),
+        SlotVerifyError::PublicKeyRejected.into(),
     )
 }
 
@@ -201,7 +202,7 @@
         &load_latest_signed_kernel()?,
         &load_latest_initrd_normal()?,
         &fs::read(PUBLIC_KEY_RSA2048_PATH)?,
-        avb::SlotVerifyError::PublicKeyRejected.into(),
+        SlotVerifyError::PublicKeyRejected.into(),
     )
 }
 
@@ -211,7 +212,7 @@
         &load_latest_signed_kernel()?,
         /* initrd= */ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )
 }
 
@@ -221,7 +222,7 @@
         &fs::read(UNSIGNED_TEST_IMG_PATH)?,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Io.into(),
+        SlotVerifyError::Io.into(),
     )
 }
 
@@ -234,7 +235,7 @@
         &kernel,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )
 }
 
@@ -272,7 +273,7 @@
             &kernel,
             &load_latest_initrd_normal()?,
             &load_trusted_public_key()?,
-            avb::SlotVerifyError::Io.into(),
+            SlotVerifyError::Io.into(),
         )?;
     }
     Ok(())
@@ -288,7 +289,7 @@
         &kernel,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::InvalidMetadata.into(),
+        SlotVerifyError::InvalidMetadata.into(),
     )
 }
 
@@ -301,7 +302,7 @@
         &load_latest_signed_kernel()?,
         &initrd,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )
 }
 
@@ -317,7 +318,7 @@
         &kernel,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::InvalidMetadata.into(),
+        SlotVerifyError::InvalidMetadata.into(),
     )
 }
 
@@ -340,13 +341,13 @@
         &kernel,
         &load_latest_initrd_normal()?,
         &empty_public_key,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )?;
     assert_payload_verification_with_initrd_fails(
         &kernel,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )
 }
 
@@ -384,7 +385,7 @@
         &kernel,
         &load_latest_initrd_normal()?,
         &load_trusted_public_key()?,
-        avb::SlotVerifyError::Verification(None).into(),
+        SlotVerifyError::Verification(None).into(),
     )
 }
 
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 3f84a8d..14f1fe5 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -55,6 +55,8 @@
     InvalidInterrupts,
     /// Invalid <iommus>
     InvalidIommus,
+    /// Invalid pvIOMMU node
+    InvalidPvIommu,
     /// Too many pvIOMMU
     TooManyPvIommu,
     /// Duplicated pvIOMMU IDs exist
@@ -83,6 +85,7 @@
             ),
             Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
             Self::InvalidIommus => write!(f, "Invalid <iommus>"),
+            Self::InvalidPvIommu => write!(f, "Invalid pvIOMMU node"),
             Self::TooManyPvIommu => write!(
                 f,
                 "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
@@ -195,11 +198,22 @@
 
 impl PvIommu {
     fn parse(node: &FdtNode) -> Result<Self> {
-        let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidIommus)?;
+        let iommu_cells = node
+            .getprop_u32(cstr!("#iommu-cells"))?
+            .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
+        // Ensures <#iommu-cells> = 1. It means that `<iommus>` entry contains pair of
+        // (pvIOMMU ID, vSID)
+        if iommu_cells != 1 {
+            return Err(DeviceAssignmentError::InvalidPvIommu);
+        }
+        let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
         Ok(Self { id })
     }
 }
 
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+struct Vsid(u32);
+
 /// Assigned device information parsed from crosvm DT.
 /// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
 #[derive(Debug, Eq, PartialEq)]
@@ -212,8 +226,8 @@
     reg: Vec<u8>,
     // <interrupts> property from the crosvm DT
     interrupts: Vec<u8>,
-    // Parsed <iommus> property from the crosvm DT.
-    iommus: Vec<PvIommu>,
+    // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
+    iommus: Vec<(PvIommu, Vsid)>,
 }
 
 impl AssignedDeviceInfo {
@@ -233,17 +247,26 @@
     }
 
     // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
-    // TODO(b/277993056): Also keep vSID.
-    // TODO(b/277993056): Validate #iommu-cells values.
-    fn parse_iommus(node: &FdtNode, pviommus: &BTreeMap<Phandle, PvIommu>) -> Result<Vec<PvIommu>> {
+    fn parse_iommus(
+        node: &FdtNode,
+        pviommus: &BTreeMap<Phandle, PvIommu>,
+    ) -> Result<Vec<(PvIommu, Vsid)>> {
         let mut iommus = vec![];
-        let Some(cells) = node.getprop_cells(cstr!("iommus"))? else {
+        let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
             return Ok(iommus);
         };
-        for cell in cells {
+        while let Some(cell) = cells.next() {
+            // Parse pvIOMMU ID
             let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
             let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
-            iommus.push(*pviommu)
+
+            // Parse vSID
+            let Some(cell) = cells.next() else {
+                return Err(DeviceAssignmentError::InvalidIommus);
+            };
+            let vsid = Vsid(cell);
+
+            iommus.push((*pviommu, vsid));
         }
         Ok(iommus)
     }
@@ -275,15 +298,12 @@
         let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
         dst.setprop(cstr!("reg"), &self.reg)?;
         dst.setprop(cstr!("interrupts"), &self.interrupts)?;
-
-        let iommus: Vec<u8> = self
-            .iommus
-            .iter()
-            .flat_map(|pviommu| {
-                let phandle = pviommu_phandles.get(pviommu).unwrap();
-                u32::from(*phandle).to_be_bytes()
-            })
-            .collect();
+        let mut iommus = Vec::with_capacity(8 * self.iommus.len());
+        for (pviommu, vsid) in &self.iommus {
+            let phandle = pviommu_phandles.get(pviommu).unwrap();
+            iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
+            iommus.extend_from_slice(&vsid.0.to_be_bytes());
+        }
         dst.setprop(cstr!("iommus"), &iommus)?;
 
         Ok(())
@@ -303,7 +323,6 @@
     /// Parses pvIOMMUs in fdt
     // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
     fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
-        // TODO(b/277993056): Validated `<#iommu-cells>`.
         let mut pviommus = BTreeMap::new();
         for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
             let Some(phandle) = compatible.get_phandle()? else {
@@ -433,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";
@@ -445,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 {
@@ -460,9 +479,10 @@
                 .getprop(cstr!("interrupts"))?
                 .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
             let mut iommus = vec![];
-            if let Some(cells) = node.getprop_cells(cstr!("iommus"))? {
-                for cell in cells {
-                    let phandle = Phandle::try_from(cell)?;
+            if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
+                while let Some(pviommu_id) = cells.next() {
+                    // pvIOMMU id
+                    let phandle = Phandle::try_from(pviommu_id)?;
                     let pviommu = fdt
                         .node_with_phandle(phandle)?
                         .ok_or(DeviceAssignmentError::InvalidIommus)?;
@@ -474,6 +494,12 @@
                         .getprop_u32(cstr!("id"))?
                         .ok_or(DeviceAssignmentError::InvalidIommus)?;
                     iommus.push(id);
+
+                    // vSID
+                    let Some(vsid) = cells.next() else {
+                        return Err(DeviceAssignmentError::InvalidIommus);
+                    };
+                    iommus.push(vsid);
                 }
             }
             Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
@@ -510,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();
@@ -523,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);
@@ -559,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();
@@ -584,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()
@@ -608,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();
@@ -630,7 +682,7 @@
             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],
+            iommus: vec![0x4, 0xFF0],
         };
 
         let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
@@ -665,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, 0x9],
+                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, 0x50, 0x60],
+                iommus: vec![0x40, 0xFFA, 0x50, 0xFFB],
             },
         ];
 
@@ -680,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]
@@ -708,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, 0x9],
+                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, 0x40],
+                iommus: vec![0x4, 0xFF0],
             },
         ];
 
@@ -724,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/src/dice.rs b/pvmfw/src/dice.rs
index 112c24c..99bf589 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -14,16 +14,13 @@
 
 //! Support for DICE derivation and BCC generation.
 
-use core::ffi::c_void;
 use core::mem::size_of;
-use core::slice;
 use cstr::cstr;
 use diced_open_dice::{
     bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
     Hash, InputValues, HIDDEN_SIZE,
 };
-use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
-use vmbase::memory::flushed_zeroize;
+use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
     match debug_level {
@@ -46,6 +43,7 @@
     pub auth_hash: Hash,
     pub mode: DiceMode,
     pub security_version: u64,
+    pub rkp_vm_marker: bool,
 }
 
 impl PartialInputs {
@@ -55,8 +53,9 @@
         let mode = to_dice_mode(data.debug_level);
         // We use rollback_index from vbmeta as the security_version field in dice certificate.
         let security_version = data.rollback_index;
+        let rkp_vm_marker = data.has_capability(Capability::RemoteAttest);
 
-        Ok(Self { code_hash, auth_hash, mode, security_version })
+        Ok(Self { code_hash, auth_hash, mode, security_version, rkp_vm_marker })
     }
 
     pub fn write_next_bcc(
@@ -66,15 +65,7 @@
         next_bcc: &mut [u8],
     ) -> diced_open_dice::Result<()> {
         let mut config_descriptor_buffer = [0; 128];
-        let config_values = DiceConfigValues {
-            component_name: Some(cstr!("vm_entry")),
-            security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
-            ..Default::default()
-        };
-
-        let config_descriptor_size =
-            bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
-        let config = &config_descriptor_buffer[..config_descriptor_size];
+        let config = self.generate_config_descriptor(&mut config_descriptor_buffer)?;
 
         let dice_inputs = InputValues::new(
             self.code_hash,
@@ -86,17 +77,138 @@
         let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
         Ok(())
     }
+
+    fn generate_config_descriptor<'a>(
+        &self,
+        config_descriptor_buffer: &'a mut [u8],
+    ) -> diced_open_dice::Result<&'a [u8]> {
+        let config_values = DiceConfigValues {
+            component_name: Some(cstr!("vm_entry")),
+            security_version: if cfg!(dice_changes) { Some(self.security_version) } else { None },
+            rkp_vm_marker: self.rkp_vm_marker,
+            ..Default::default()
+        };
+        let config_descriptor_size =
+            bcc_format_config_descriptor(&config_values, config_descriptor_buffer)?;
+        let config = &config_descriptor_buffer[..config_descriptor_size];
+        Ok(config)
+    }
 }
 
 /// Flushes data caches over the provided address range.
 ///
 /// # Safety
 ///
-/// The provided address and size must be to a valid address range (typically on the stack, .bss,
-/// .data, or provided BCC).
+/// The provided address and size must be to an address range that is valid for read and write
+/// (typically on the stack, .bss, .data, or provided BCC) from a single allocation
+/// (e.g. stack array).
 #[no_mangle]
-unsafe extern "C" fn DiceClearMemory(_ctx: *mut c_void, size: usize, addr: *mut c_void) {
-    // SAFETY: We must trust that the slice will be valid arrays/variables on the C code stack.
+#[cfg(not(test))]
+unsafe extern "C" fn DiceClearMemory(
+    _ctx: *mut core::ffi::c_void,
+    size: usize,
+    addr: *mut core::ffi::c_void,
+) {
+    use core::slice;
+    use vmbase::memory::flushed_zeroize;
+
+    // SAFETY: We require our caller to provide a valid range within a single object. The open-dice
+    // always calls this on individual stack-allocated arrays which ensures that.
     let region = unsafe { slice::from_raw_parts_mut(addr as *mut u8, size) };
     flushed_zeroize(region)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ciborium::Value;
+    use std::collections::HashMap;
+    use std::vec;
+
+    const COMPONENT_NAME_KEY: i64 = -70002;
+    const COMPONENT_VERSION_KEY: i64 = -70003;
+    const RESETTABLE_KEY: i64 = -70004;
+    const SECURITY_VERSION_KEY: i64 = -70005;
+    const RKP_VM_MARKER_KEY: i64 = -70006;
+
+    const BASE_VB_DATA: VerifiedBootData = VerifiedBootData {
+        debug_level: DebugLevel::None,
+        kernel_digest: [1u8; size_of::<Digest>()],
+        initrd_digest: Some([2u8; size_of::<Digest>()]),
+        public_key: b"public key",
+        capabilities: vec![],
+        rollback_index: 42,
+    };
+
+    #[test]
+    fn base_data_conversion() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeNormal);
+        assert_eq!(inputs.security_version, 42);
+        assert!(!inputs.rkp_vm_marker);
+
+        // TODO(b/313608219): Consider checks for code_hash and possibly auth_hash.
+    }
+
+    #[test]
+    fn debuggable_conversion() {
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeDebug);
+    }
+
+    #[test]
+    fn rkp_vm_conversion() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert!(inputs.rkp_vm_marker);
+    }
+
+    #[test]
+    fn base_config_descriptor() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs);
+
+        assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+        assert_eq!(config_map.get(&COMPONENT_VERSION_KEY), None);
+        assert_eq!(config_map.get(&RESETTABLE_KEY), None);
+        if cfg!(dice_changes) {
+            assert_eq!(
+                config_map.get(&SECURITY_VERSION_KEY).unwrap().as_integer().unwrap(),
+                42.into()
+            );
+        } else {
+            assert_eq!(config_map.get(&SECURITY_VERSION_KEY), None);
+        }
+        assert_eq!(config_map.get(&RKP_VM_MARKER_KEY), None);
+    }
+
+    #[test]
+    fn config_descriptor_with_rkp_vm() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs);
+
+        assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+    }
+
+    fn decode_config_descriptor(inputs: &PartialInputs) -> HashMap<i64, Value> {
+        let mut buffer = [0; 128];
+        let config_descriptor = inputs.generate_config_descriptor(&mut buffer).unwrap();
+
+        let cbor_map =
+            cbor_util::deserialize::<Value>(config_descriptor).unwrap().into_map().unwrap();
+
+        cbor_map
+            .into_iter()
+            .map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
+            .collect()
+    }
+}
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 4fe2c34..7a89b75 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -201,6 +201,22 @@
     Ok(())
 }
 
+fn read_vendor_public_key_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
+    if let Some(avf_node) = fdt.node(cstr!("/avf"))? {
+        if let Some(vendor_public_key) = avf_node.getprop(cstr!("vendor_public_key"))? {
+            return Ok(Some(vendor_public_key.to_vec()));
+        }
+    }
+    Ok(None)
+}
+
+fn patch_vendor_public_key(fdt: &mut Fdt, vendor_public_key: &[u8]) -> libfdt::Result<()> {
+    let mut root_node = fdt.root_mut()?;
+    let mut avf_node = root_node.add_subnode(cstr!("/avf"))?;
+    avf_node.setprop(cstr!("vendor_public_key"), vendor_public_key)?;
+    Ok(())
+}
+
 #[derive(Debug)]
 struct PciInfo {
     ranges: [PciAddrRange; 2],
@@ -593,6 +609,7 @@
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
+    vendor_public_key: Option<Vec<u8>>,
 }
 
 impl DeviceTreeInfo {
@@ -627,6 +644,11 @@
         RebootReason::InvalidFdt
     })?;
 
+    fdt.unpack().map_err(|e| {
+        error!("Failed to unpack DT for patching: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
     if let Some(device_assignment_info) = &info.device_assignment {
         let vm_dtbo = vm_dtbo.unwrap();
         device_assignment_info.filter(vm_dtbo).map_err(|e| {
@@ -646,6 +668,11 @@
 
     patch_device_tree(fdt, &info)?;
 
+    fdt.pack().map_err(|e| {
+        error!("Failed to unpack DT after patching: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
     Ok(info)
 }
 
@@ -701,6 +728,18 @@
         None => None,
     };
 
+    // TODO(b/285854379) : A temporary solution lives. This is for enabling
+    // microdroid vendor partition for non-protected VM as well. When passing
+    // DT path containing vendor_public_key via fstab, init stage will check
+    // if vendor_public_key exists in the init stage, regardless the protection.
+    // Adding this temporary solution will prevent fatal in init stage for
+    // protected VM. However, this data is not trustable without validating
+    // with vendor public key value comes from ABL.
+    let vendor_public_key = read_vendor_public_key_from(fdt).map_err(|e| {
+        error!("Failed to read vendor_public_key from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
     Ok(DeviceTreeInfo {
         kernel_range,
         initrd_range,
@@ -711,15 +750,11 @@
         serial_info,
         swiotlb_info,
         device_assignment,
+        vendor_public_key,
     })
 }
 
 fn patch_device_tree(fdt: &mut Fdt, info: &DeviceTreeInfo) -> Result<(), RebootReason> {
-    fdt.unpack().map_err(|e| {
-        error!("Failed to unpack DT for patching: {e}");
-        RebootReason::InvalidFdt
-    })?;
-
     if let Some(initrd_range) = &info.initrd_range {
         patch_initrd_range(fdt, initrd_range).map_err(|e| {
             error!("Failed to patch initrd range to DT: {e}");
@@ -768,11 +803,12 @@
             RebootReason::InvalidFdt
         })?;
     }
-
-    fdt.pack().map_err(|e| {
-        error!("Failed to pack DT after patching: {e}");
-        RebootReason::InvalidFdt
-    })?;
+    if let Some(vendor_public_key) = &info.vendor_public_key {
+        patch_vendor_public_key(fdt, vendor_public_key).map_err(|e| {
+            error!("Failed to patch vendor_public_key to DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
+    }
 
     Ok(())
 }
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 f0a7162..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,84 +1,46 @@
 /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 {
         compatible = "pkvm,pviommu";
         id = <0x4>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 
     pviommu_1: pviommu1 {
         compatible = "pkvm,pviommu";
         id = <0x9>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 
     light@70000000 {
         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 {
         compatible = "pkvm,pviommu";
         id = <0x40>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 
     pviommu_b: pviommub {
         compatible = "pkvm,pviommu";
         id = <0x9>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 };
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index d6952fa..7c6d2f2 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -1,78 +1,28 @@
 /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 0xFF0>;
     };
 
-    light@70000000 {
+    led@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_1>, <&pviommu_a>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
         compatible = "pkvm,pviommu";
         id = <0x4>;
-        #iommu-cells = <0>;
-    };
-
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <0>;
-    };
-
-    pviommu_a: pviommua {
-        compatible = "pkvm,pviommu";
-        id = <0x40>;
-        #iommu-cells = <0>;
+        #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 2609c45..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,90 +1,39 @@
 /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 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
         compatible = "pkvm,pviommu";
         id = <0x4>;
-        #iommu-cells = <0>;
-    };
-
-    pviommu_1: pviommu1 {
-        compatible = "pkvm,pviommu";
-        id = <0x9>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 
     light@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_a>, <&pviommu_b>, <&pviommu_c>;
+        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>;
     };
 
     pviommu_a: pviommua {
         compatible = "pkvm,pviommu";
         id = <0x40>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 
     pviommu_b: pviommub {
         compatible = "pkvm,pviommu";
         id = <0x50>;
-        #iommu-cells = <0>;
-    };
-
-    pviommu_c: pviommuc {
-        compatible = "pkvm,pviommu";
-        id = <0x60>;
-        #iommu-cells = <0>;
+        #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 6a5068c..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>;
-    };
-
-    pviommu_0: pviommu0 {
-        compatible = "pkvm,pviommu";
-        id = <0x4>;
-        #iommu-cells = <0>;
-    };
-};
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 728c1eb..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",
@@ -109,17 +109,21 @@
         "android.system.virtualizationservice-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbssl_avf_nostd",
         "libciborium",
         "libclient_vm_csr",
-        "libdiced_sample_inputs",
+        "libcoset",
         "liblibc",
         "liblog_rust",
         "libservice_vm_comm",
+        "libservice_vm_fake_chain",
         "libservice_vm_manager",
         "libvmclient",
+        "libx509_parser",
     ],
     data: [
         ":rialto_unsigned",
+        ":test_rkp_cert_chain",
     ],
     test_suites: ["general-tests"],
     enabled: false,
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index d9cffe0..1215021 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -37,7 +37,8 @@
 use hyp::{get_mem_sharer, get_mmio_guard};
 use libfdt::FdtError;
 use log::{debug, error, info};
-use service_vm_comm::{ServiceVmRequest, VmType};
+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)?;
@@ -178,7 +177,15 @@
 
     let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
     while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
-        let response = process_request(req, bcc_handover.as_ref());
+        let mut response = process_request(req, bcc_handover.as_ref());
+        // TODO(b/185878400): We don't want to issue a certificate to pVM when the client VM
+        // attestation is unfinished. The following code should be removed once the
+        // verification is completed.
+        if vm_type() == VmType::ProtectedVm
+            && matches!(response, Response::RequestClientVmAttestation(_))
+        {
+            response = Response::Err(RequestProcessingError::OperationUnimplemented);
+        }
         vsock_stream.write_response(&response)?;
         vsock_stream.flush()?;
     }
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 0f59350..02a5a28 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,22 +22,35 @@
     binder::{ParcelFileDescriptor, ProcessState},
 };
 use anyhow::{bail, Context, Result};
+use bssl_avf::{sha256, EcKey, PKey};
 use ciborium::value::Value;
 use client_vm_csr::generate_attestation_key_and_csr;
+use coset::{CborSerializable, CoseMac0, CoseSign};
 use log::info;
 use service_vm_comm::{
-    ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
-    RequestProcessingError, Response, VmType,
+    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;
 use std::io;
 use std::panic;
 use std::path::PathBuf;
 use vmclient::VmInstance;
+use x509_parser::{
+    certificate::X509Certificate,
+    der_parser::{ber::BerObject, der::parse_der, oid, oid::Oid},
+    prelude::FromDer,
+    x509::{AlgorithmIdentifier, SubjectPublicKeyInfo, X509Version},
+};
 
 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";
 
 #[test]
 fn process_requests_in_protected_vm() -> Result<()> {
@@ -55,7 +68,7 @@
     check_processing_reverse_request(&mut vm)?;
     let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
     check_processing_generating_certificate_request(&mut vm, &key_pair.maced_public_key)?;
-    check_attestation_request(&mut vm, &key_pair.key_blob)?;
+    check_attestation_request(&mut vm, &key_pair, vm_type)?;
     Ok(())
 }
 
@@ -110,18 +123,30 @@
     }
 }
 
-fn check_attestation_request(vm: &mut ServiceVm, key_blob: &[u8]) -> Result<()> {
+fn check_attestation_request(
+    vm: &mut ServiceVm,
+    remotely_provisioned_key_pair: &EcdsaP256KeyPair,
+    vm_type: VmType,
+) -> Result<()> {
     /// The following data was generated randomly with urandom.
     const CHALLENGE: [u8; 16] = [
         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 = 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)?;
 
+    // Builds the mock parameters for the client VM attestation.
+    // The `csr` and `remotely_provisioned_key_blob` parameters are extracted from the same
+    // libraries as in production.
+    // The `remotely_provisioned_cert` parameter is an RKP certificate extracted from a test
+    // certificate chain retrieved from RKPD.
     let params = ClientVmAttestationParams {
-        csr: attestation_data.csr.into_cbor_vec()?,
-        remotely_provisioned_key_blob: key_blob.to_vec(),
+        csr: attestation_data.csr.clone().into_cbor_vec()?,
+        remotely_provisioned_key_blob: remotely_provisioned_key_pair.key_blob.to_vec(),
+        remotely_provisioned_cert: cert_chain[..(cert_chain.len() - remaining.len())].to_vec(),
     };
     let request = Request::RequestClientVmAttestation(params);
 
@@ -129,12 +154,113 @@
     info!("Received response: {response:?}.");
 
     match response {
-        // TODO(b/309441500): Check the certificate once it is implemented.
-        Response::Err(RequestProcessingError::OperationUnimplemented) => Ok(()),
+        Response::RequestClientVmAttestation(certificate) => {
+            // The end-to-end test for non-protected VM attestation works because both the service
+            // VM and the client VM use the same fake DICE chain.
+            assert_eq!(vm_type, VmType::NonProtectedVm);
+            check_certificate_for_client_vm(
+                &certificate,
+                &remotely_provisioned_key_pair.maced_public_key,
+                &attestation_data.csr,
+                &cert,
+            )?;
+            Ok(())
+        }
+        Response::Err(RequestProcessingError::InvalidDiceChain) => {
+            // The end-to-end test for protected VM attestation doesn't work because the service VM
+            // compares the fake DICE chain in the CSR with the real DICE chain.
+            // We cannot generate a valid DICE chain with the same payloads up to pvmfw.
+            assert_eq!(vm_type, VmType::ProtectedVm);
+            Ok(())
+        }
         _ => bail!("Incorrect response type: {response:?}"),
     }
 }
 
+fn check_vm_components(vm_components: &[BerObject]) -> Result<()> {
+    let expected_components = fake_sub_components();
+    assert_eq!(expected_components.len(), vm_components.len());
+    for i in 0..expected_components.len() {
+        check_vm_component(&vm_components[i], &expected_components[i])?;
+    }
+    Ok(())
+}
+
+fn check_vm_component(vm_component: &BerObject, expected_component: &SubComponent) -> Result<()> {
+    let vm_component = vm_component.as_sequence()?;
+    assert_eq!(4, vm_component.len());
+    assert_eq!(expected_component.name, vm_component[0].as_str()?);
+    assert_eq!(expected_component.version, vm_component[1].as_u64()?);
+    assert_eq!(expected_component.code_hash, vm_component[2].as_slice()?);
+    assert_eq!(expected_component.authority_hash, vm_component[3].as_slice()?);
+    Ok(())
+}
+
+fn check_certificate_for_client_vm(
+    certificate: &[u8],
+    maced_public_key: &[u8],
+    csr: &Csr,
+    parent_certificate: &X509Certificate,
+) -> Result<()> {
+    let cose_mac = CoseMac0::from_slice(maced_public_key)?;
+    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());
+
+    // Checks the certificate signature against the authority public key.
+    const ECDSA_WITH_SHA_256: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2);
+    let expected_algorithm =
+        AlgorithmIdentifier { algorithm: ECDSA_WITH_SHA_256, parameters: None };
+    assert_eq!(expected_algorithm, cert.signature_algorithm);
+    let digest = sha256(cert.tbs_certificate.as_ref()).unwrap();
+    authority_public_key
+        .ecdsa_verify(cert.signature_value.as_ref(), &digest)
+        .expect("Failed to verify the certificate signature with the authority public key");
+
+    // Checks that the certificate's subject public key is equal to the key in the CSR.
+    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_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)?;
+    assert!(remaining.is_empty());
+    assert_eq!(&expected_spki, cert.public_key());
+
+    // Checks the certificate extension.
+    const ATTESTATION_EXTENSION_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .11129 .2 .1 .29 .1);
+    let extensions = cert.extensions();
+    assert_eq!(1, extensions.len());
+    let extension = &extensions[0];
+    assert_eq!(ATTESTATION_EXTENSION_OID, extension.oid);
+    assert!(!extension.critical);
+    let (remaining, extension) = parse_der(extension.value)?;
+    assert!(remaining.is_empty());
+    let attestation_ext = extension.as_sequence()?;
+    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());
+    assert_eq!(parent_certificate.validity(), cert.validity());
+    assert_eq!(
+        String::from("CN=Android Protected Virtual Machine Key"),
+        cert.subject().to_string()
+    );
+    assert_eq!(parent_certificate.subject(), cert.issuer());
+
+    Ok(())
+}
+
 /// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
 fn check_csr(csr: Vec<u8>) -> Result<()> {
     let mut reader = io::Cursor::new(csr);
diff --git a/secretkeeper/comm/Android.bp b/secretkeeper/comm/Android.bp
deleted file mode 100644
index cb3e713..0000000
--- a/secretkeeper/comm/Android.bp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_defaults {
-    name: "libsecretkeeper_comm.defaults",
-    crate_name: "secretkeeper_comm",
-    defaults: ["avf_build_flags_rust"],
-    edition: "2021",
-    lints: "android",
-    rustlibs: [
-        "libciborium",
-        "libcoset",
-    ],
-    proc_macros: ["libenumn"],
-    vendor_available: true,
-}
-
-rust_library {
-    name: "libsecretkeeper_comm_nostd",
-    defaults: ["libsecretkeeper_comm.defaults"],
-    srcs: ["src/lib.rs"],
-}
-
-rust_test {
-    name: "libsecretkeeper_comm.test",
-    defaults: [
-        "libsecretkeeper_comm.defaults",
-        "rdroidtest.defaults",
-    ],
-    srcs: ["tests/*.rs"],
-    test_suites: ["general-tests"],
-    rustlibs: [
-        "libsecretkeeper_comm_nostd",
-    ],
-}
diff --git a/secretkeeper/comm/src/cbor_convert.rs b/secretkeeper/comm/src/cbor_convert.rs
deleted file mode 100644
index ab6ca3f..0000000
--- a/secretkeeper/comm/src/cbor_convert.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.
- */
-
-//! Implements various useful CBOR conversion method.
-
-use crate::data_types::error::Error;
-use alloc::vec::Vec;
-use ciborium::Value;
-
-/// Decodes the provided binary CBOR-encoded value and returns a
-/// [`ciborium::Value`] struct wrapped in Result.
-pub fn value_from_bytes(mut bytes: &[u8]) -> Result<Value, Error> {
-    let value = ciborium::de::from_reader(&mut bytes).map_err(|_| Error::ConversionError)?;
-    // Ciborium tries to read one Value, but doesn't care if there is trailing data after it. We do
-    if !bytes.is_empty() {
-        return Err(Error::ConversionError);
-    }
-    Ok(value)
-}
-
-/// Encodes a [`ciborium::Value`] into bytes.
-pub fn value_to_bytes(value: &Value) -> Result<Vec<u8>, Error> {
-    let mut bytes: Vec<u8> = Vec::new();
-    ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| Error::UnexpectedError)?;
-    Ok(bytes)
-}
-
-// Useful to convert [`ciborium::Value`] to integer, we return largest integer range for
-// convenience, callers should downcast into appropriate type.
-pub fn value_to_integer(value: &Value) -> Result<i128, Error> {
-    let num = value.as_integer().ok_or(Error::ConversionError)?.into();
-    Ok(num)
-}
diff --git a/secretkeeper/comm/src/data_types/error.rs b/secretkeeper/comm/src/data_types/error.rs
deleted file mode 100644
index 6a5e24f..0000000
--- a/secretkeeper/comm/src/data_types/error.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-//! Error-like data structures. See `ResponsePacketError` in the CDDL
-
-// derive(N) generates a method that is missing a docstring.
-#![allow(missing_docs)]
-
-use crate::cbor_convert::value_to_integer;
-use crate::data_types::response::Response;
-use alloc::boxed::Box;
-use alloc::vec::Vec;
-use ciborium::Value;
-use enumn::N;
-
-/// 'Error code' corresponding to successful response.
-pub const ERROR_OK: u16 = 0; // All real errors must have non-zero error_codes
-
-/// Errors from Secretkeeper API. Keep in sync with `ErrorCode` defined for Secretkeeper HAL
-/// at SecretManagement.cddl
-#[derive(Clone, Copy, Debug, Eq, N, PartialEq)]
-pub enum SecretkeeperError {
-    // This is the Error code used if no other error codes explains the issue.
-    UnexpectedServerError = 1,
-    // Indicates the Request was malformed & hence couldn't be served.
-    RequestMalformed = 2,
-    // TODO(b/291228655): Add other errors such as DicePolicyError.
-}
-
-// [`SecretkeeperError`] is a valid [`Response`] type.
-// For more information see `ErrorCode` in SecretManagement.cddl alongside ISecretkeeper.aidl
-impl Response for SecretkeeperError {
-    fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error> {
-        // TODO(b/291228655): This method currently discards the second value in response_cbor,
-        // which contains additional human-readable context in error. Include it!
-        if response_cbor.is_empty() || response_cbor.len() > 2 {
-            return Err(Error::ResponseMalformed);
-        }
-        let error_code: u16 = value_to_integer(&response_cbor[0])?.try_into()?;
-        SecretkeeperError::n(error_code)
-            .map_or_else(|| Err(Error::ResponseMalformed), |sk_err| Ok(Box::new(sk_err)))
-    }
-
-    fn error_code(&self) -> u16 {
-        *self as u16
-    }
-}
-
-/// Errors thrown internally by the library.
-#[derive(Debug, PartialEq)]
-pub enum Error {
-    /// Request was malformed.
-    RequestMalformed,
-    /// Response received from the server was malformed.
-    ResponseMalformed,
-    /// An error happened when serializing to/from a [`Value`].
-    CborValueError,
-    /// An error happened while casting a type to different type,
-    /// including one [`Value`] type to another.
-    ConversionError,
-    /// These are unexpected errors, which should never really happen.
-    UnexpectedError,
-}
-
-impl From<ciborium::value::Error> for Error {
-    fn from(_e: ciborium::value::Error) -> Self {
-        Self::CborValueError
-    }
-}
-
-impl From<ciborium::Value> for Error {
-    fn from(_e: ciborium::Value) -> Self {
-        Self::ConversionError
-    }
-}
-
-impl From<core::num::TryFromIntError> for Error {
-    fn from(_e: core::num::TryFromIntError) -> Self {
-        Self::ConversionError
-    }
-}
diff --git a/secretkeeper/comm/src/data_types/mod.rs b/secretkeeper/comm/src/data_types/mod.rs
deleted file mode 100644
index 096777f..0000000
--- a/secretkeeper/comm/src/data_types/mod.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.
- */
-
-//! Implements the data structures specified by SecretManagement.cddl in Secretkeeper HAL.
-//!  Data structures specified by SecretManagement.cddl in Secretkeeper HAL.
-//!  Note this library must stay in sync with:
-//!      platform/hardware/interfaces/security/\
-//!      secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
-
-pub mod error;
-pub mod packet;
-pub mod request;
-pub mod request_response_impl;
-pub mod response;
diff --git a/secretkeeper/comm/src/data_types/packet.rs b/secretkeeper/comm/src/data_types/packet.rs
deleted file mode 100644
index 7a1e575..0000000
--- a/secretkeeper/comm/src/data_types/packet.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.
- */
-
-//! Defines the packet structures passed between functional layer & the layer below.
-
-pub use ciborium::Value;
-
-use crate::cbor_convert::{value_from_bytes, value_to_bytes, value_to_integer};
-use crate::data_types::error::Error;
-use crate::data_types::error::ERROR_OK;
-use crate::data_types::request_response_impl::Opcode;
-use alloc::vec::Vec;
-
-/// Encapsulate Request-like data that functional layer operates on. All structures
-/// that implements `data_types::request::Request` can be serialized to [`ResponsePacket`].
-/// Similarly all [`RequestPacket`] can be deserialized to concrete Requests.
-/// Keep in sync with HAL spec (in particular RequestPacket):
-///     security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
-#[derive(Clone, Debug, PartialEq)]
-pub struct RequestPacket(Vec<Value>);
-
-impl RequestPacket {
-    /// Construct a [`RequestPacket`] from array of `ciborium::Value`
-    pub fn from(request_cbor: Vec<Value>) -> Self {
-        Self(request_cbor)
-    }
-
-    /// Get the containing CBOR. This can be used for getting concrete response objects.
-    /// Keep in sync with [`crate::data_types::request::Request::serialize_to_packet()`]
-    pub fn into_inner(self) -> Vec<Value> {
-        self.0
-    }
-
-    /// Extract [`Opcode`] corresponding to this packet. As defined in by the spec, this is
-    /// the first value in the CBOR array.
-    pub fn opcode(&self) -> Result<Opcode, Error> {
-        if self.0.is_empty() {
-            return Err(Error::RequestMalformed);
-        }
-        let num: u16 = value_to_integer(&self.0[0])?.try_into()?;
-
-        Opcode::n(num).ok_or(Error::RequestMalformed)
-    }
-
-    /// Serialize the [`ResponsePacket`] to bytes
-    pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
-        value_to_bytes(&Value::Array(self.0))
-    }
-
-    /// Deserialize the bytes into [`ResponsePacket`]
-    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
-        Ok(RequestPacket(value_from_bytes(bytes)?.into_array()?))
-    }
-}
-
-/// Encapsulate Response like data that the functional layer operates on. All structures
-/// that implements `data_types::response::Response` can be serialized to [`ResponsePacket`].
-/// Similarly all [`ResponsePacket`] can be deserialized to concrete Response.
-#[derive(Clone, Debug, PartialEq)]
-pub struct ResponsePacket(Vec<Value>);
-
-impl ResponsePacket {
-    /// Construct a [`ResponsePacket`] from array of `ciborium::Value`
-    pub fn from(response_cbor: Vec<Value>) -> Self {
-        Self(response_cbor)
-    }
-
-    /// Get raw content. This can be used for getting concrete response objects.
-    /// Keep in sync with `crate::data_types::response::Response::serialize_to_packet`
-    pub fn into_inner(self) -> Vec<Value> {
-        self.0
-    }
-
-    /// A [`ResponsePacket`] encapsulates different types of responses, find which one!
-    pub fn response_type(&self) -> Result<ResponseType, Error> {
-        if self.0.is_empty() {
-            return Err(Error::ResponseMalformed);
-        }
-        let error_code: u16 = value_to_integer(&self.0[0])?.try_into()?;
-        if error_code == ERROR_OK {
-            Ok(ResponseType::Success)
-        } else {
-            Ok(ResponseType::Error)
-        }
-    }
-
-    /// Serialize the [`ResponsePacket`] to bytes
-    pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
-        value_to_bytes(&Value::Array(self.0))
-    }
-
-    /// Deserialize the bytes into [`ResponsePacket`]
-    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
-        Ok(ResponsePacket(value_from_bytes(bytes)?.into_array()?))
-    }
-}
-
-/// Responses can be different type - `Success`-like or `Error`-like.
-#[derive(Debug, Eq, PartialEq)]
-pub enum ResponseType {
-    /// Indicates successful operation. See `ResponsePacketSuccess` in SecretManagement.cddl
-    Success,
-    /// Indicate failed operation. See `ResponsePacketError` in SecretManagement.cddl
-    Error,
-}
diff --git a/secretkeeper/comm/src/data_types/request.rs b/secretkeeper/comm/src/data_types/request.rs
deleted file mode 100644
index 0d54bcd..0000000
--- a/secretkeeper/comm/src/data_types/request.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-//! Defines the shared behaviour of all request like data structures.
-
-use crate::data_types::error::Error;
-use crate::data_types::packet::RequestPacket;
-use crate::data_types::request_response_impl::Opcode;
-use alloc::boxed::Box;
-use alloc::vec::Vec;
-use ciborium::Value;
-
-/// Collection of methods defined for Secretkeeper's request-like data structures,
-/// e.g. `GetVersionRequestPacket` in the HAL spec.
-///
-/// Keep in sync with SecretManagement.cddl, in particular `RequestPacket` type.
-pub trait Request {
-    /// [`Opcode`] of the request: Each Request type is associated with an opcode. See `Opcode` in
-    /// SecretManagement.cddl.
-    const OPCODE: Opcode;
-
-    /// Constructor of the [`Request`] object. Implementation of this constructor should check
-    /// the args' type adheres to the HAL spec.
-    ///
-    /// # Arguments
-    /// * `args` - The vector of arguments associated with this request. Each argument is a
-    ///   `ciborium::Value` type. See `Params` in `RequestPacket` in SecretManagement.cddl
-    fn new(args: Vec<Value>) -> Result<Box<Self>, Error>;
-
-    /// Get the 'arguments' of this request.
-    fn args(&self) -> Vec<Value>;
-
-    /// Serialize the request to a [`RequestPacket`], which, as per SecretManagement.cddl is:
-    /// ```
-    ///      RequestPacket<Opcode, Params> = [
-    ///         Opcode,
-    ///         Params
-    ///      ]
-    /// ```
-    fn serialize_to_packet(&self) -> RequestPacket {
-        let mut res = self.args();
-        res.insert(0, Value::from(Self::OPCODE as u16));
-        RequestPacket::from(res)
-    }
-
-    /// Construct the [`Request`] struct from given [`RequestPacket`].
-    fn deserialize_from_packet(packet: RequestPacket) -> Result<Box<Self>, Error> {
-        let mut req = packet.into_inner();
-        if req.get(0) != Some(&Value::from(Self::OPCODE as u16)) {
-            return Err(Error::RequestMalformed);
-        }
-        req.remove(0);
-        Self::new(req)
-    }
-}
diff --git a/secretkeeper/comm/src/data_types/request_response_impl.rs b/secretkeeper/comm/src/data_types/request_response_impl.rs
deleted file mode 100644
index a7d29cc..0000000
--- a/secretkeeper/comm/src/data_types/request_response_impl.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.
- */
-
-//! Implementation of request & response like data structures.
-
-// derive(N) generates a method that is missing a docstring.
-#![allow(missing_docs)]
-
-use crate::cbor_convert::value_to_integer;
-use crate::data_types::error::Error;
-use crate::data_types::error::ERROR_OK;
-use crate::data_types::request::Request;
-use crate::data_types::response::Response;
-use alloc::boxed::Box;
-use alloc::vec;
-use alloc::vec::Vec;
-use ciborium::Value;
-use enumn::N;
-
-/// Set of all possible `Opcode` supported by SecretManagement API of the HAL.
-/// See `Opcode` in SecretManagement.cddl
-#[derive(Clone, Copy, Debug, N, PartialEq)]
-#[non_exhaustive]
-pub enum Opcode {
-    /// Get version of the SecretManagement API.
-    GetVersion = 1,
-    /// Store a secret
-    StoreSecret = 2,
-    /// Get the secret
-    GetSecret = 3,
-}
-
-/// Corresponds to `GetVersionRequestPacket` defined in SecretManagement.cddl
-#[derive(Debug, Eq, PartialEq)]
-pub struct GetVersionRequest;
-
-impl Request for GetVersionRequest {
-    const OPCODE: Opcode = Opcode::GetVersion;
-
-    fn new(args: Vec<Value>) -> Result<Box<Self>, Error> {
-        if !args.is_empty() {
-            return Err(Error::RequestMalformed);
-        }
-        Ok(Box::new(Self))
-    }
-
-    fn args(&self) -> Vec<Value> {
-        Vec::new()
-    }
-}
-
-/// Success response corresponding to `GetVersionResponsePacket`.
-#[derive(Debug, Eq, PartialEq)]
-pub struct GetVersionResponse {
-    /// Version of SecretManagement API
-    version: u64,
-}
-
-impl GetVersionResponse {
-    pub fn new(version: u64) -> Self {
-        Self { version }
-    }
-    pub fn version(&self) -> u64 {
-        self.version
-    }
-}
-
-impl Response for GetVersionResponse {
-    fn new(res: Vec<Value>) -> Result<Box<Self>, Error> {
-        if res.len() != 2 {
-            return Err(Error::ResponseMalformed);
-        }
-        let error_code: u16 = value_to_integer(&res[0])?.try_into()?;
-        if error_code != ERROR_OK {
-            return Err(Error::ResponseMalformed);
-        }
-        let version: u64 = value_to_integer(&res[1])?.try_into()?;
-        Ok(Box::new(Self::new(version)))
-    }
-
-    fn result(&self) -> Vec<Value> {
-        vec![self.version.into()]
-    }
-}
diff --git a/secretkeeper/comm/src/data_types/response.rs b/secretkeeper/comm/src/data_types/response.rs
deleted file mode 100644
index e975ebc..0000000
--- a/secretkeeper/comm/src/data_types/response.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.
- */
-
-//! Defines the shared behaviour of all response like data structures.
-
-use crate::data_types::error::{Error, ERROR_OK};
-use crate::data_types::packet::ResponsePacket;
-use alloc::boxed::Box;
-use alloc::vec::Vec;
-use ciborium::Value;
-
-/// Shared behaviour of all Secretkeeper's response-like data structures,
-/// e.g. `GetVersionResponsePacket`. Note - A valid [`Response`] can be error as well, like
-/// `SecretkeeperError::RequestMalformed`.
-///
-/// Keep in sync with SecretManagement.cddl, in particular `ResponsePacket` type.
-pub trait Response {
-    /// Constructor of the Response object.
-    /// # Arguments
-    /// * `response_cbor`: A vector of `[ciborium::Value]` such that:
-    /// ```
-    ///     For success-like responses:
-    ///         ResponsePacketSuccess = [
-    ///             0,                          ; Indicates successful Response
-    ///             result : Result
-    ///         ]
-    ///     For error responses:
-    ///         ResponsePacketError = [
-    ///             error_code: ErrorCode,      ; Indicate the error
-    ///             error_message: tstr         ; Additional human-readable context
-    ///         ]
-    /// ```
-    /// See ResponsePacket<Result> in SecretManagement.cddl alongside ISecretkeeper.aidl
-    fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error>;
-
-    /// The result in the `Response`. By default this is empty, but [`Response`] structures like
-    /// `GetVersionResponse` must overwrite these to return the expected non-empty result.
-    fn result(&self) -> Vec<Value> {
-        Vec::new()
-    }
-
-    /// Error code corresponding to the response. The default value is 0 but that will work only
-    /// for successful responses. Error-like response structures must overwrite this method.
-    fn error_code(&self) -> u16 {
-        ERROR_OK // Indicates success
-    }
-
-    /// Serialize the response to a [`ResponsePacket`].
-    fn serialize_to_packet(&self) -> ResponsePacket {
-        let mut res = self.result();
-        res.insert(0, Value::from(self.error_code()));
-        ResponsePacket::from(res)
-    }
-
-    /// Construct the response struct from given [`ResponsePacket`].
-    fn deserialize_from_packet(packet: ResponsePacket) -> Result<Box<Self>, Error> {
-        let res = packet.into_inner();
-        // Empty response packet is not allowed, all responses in Secretkeeper HAL at least
-        // have `error_code` or '0'; so throw an error!
-        if res.is_empty() {
-            return Err(Error::ResponseMalformed);
-        }
-        Self::new(res)
-    }
-}
diff --git a/secretkeeper/comm/tests/data_types.rs b/secretkeeper/comm/tests/data_types.rs
deleted file mode 100644
index 68964fd..0000000
--- a/secretkeeper/comm/tests/data_types.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.
- */
-
-//! Unit tests for testing serialization & deserialization of exported data_types.
-
-use ciborium::Value;
-use secretkeeper_comm::data_types::error::{Error, SecretkeeperError, ERROR_OK};
-use secretkeeper_comm::data_types::packet::{RequestPacket, ResponsePacket, ResponseType};
-use secretkeeper_comm::data_types::request::Request;
-use secretkeeper_comm::data_types::request_response_impl::Opcode;
-use secretkeeper_comm::data_types::request_response_impl::{GetVersionRequest, GetVersionResponse};
-use secretkeeper_comm::data_types::response::Response;
-
-#[cfg(test)]
-rdroidtest::test_main!();
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rdroidtest::test;
-
-    test!(request_serialization_deserialization);
-    fn request_serialization_deserialization() {
-        let req = GetVersionRequest {};
-        let packet = req.serialize_to_packet();
-        assert_eq!(packet.opcode().unwrap(), Opcode::GetVersion);
-        assert_eq!(
-            RequestPacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
-            packet
-        );
-        let req_deserialized = *GetVersionRequest::deserialize_from_packet(packet).unwrap();
-        assert_eq!(req, req_deserialized);
-    }
-
-    test!(success_response_serialization_deserialization);
-    fn success_response_serialization_deserialization() {
-        let response = GetVersionResponse::new(1);
-        let packet = response.serialize_to_packet();
-        assert_eq!(packet.response_type().unwrap(), ResponseType::Success);
-        assert_eq!(
-            ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
-            packet
-        );
-        let response_deserialized = *GetVersionResponse::deserialize_from_packet(packet).unwrap();
-        assert_eq!(response, response_deserialized);
-    }
-
-    test!(error_response_serialization_deserialization);
-    fn error_response_serialization_deserialization() {
-        let response = SecretkeeperError::RequestMalformed;
-        let packet = response.serialize_to_packet();
-        assert_eq!(packet.response_type().unwrap(), ResponseType::Error);
-        assert_eq!(
-            ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
-            packet
-        );
-        let response_deserialized = *SecretkeeperError::deserialize_from_packet(packet).unwrap();
-        assert_eq!(response, response_deserialized);
-    }
-
-    test!(request_creation);
-    fn request_creation() {
-        let req: GetVersionRequest = *Request::new(vec![]).unwrap();
-        assert_eq!(req, GetVersionRequest {});
-    }
-
-    test!(response_creation);
-    fn response_creation() {
-        let res: GetVersionResponse =
-            *Response::new(vec![Value::from(ERROR_OK), Value::from(5)]).unwrap();
-        assert_eq!(res.version(), 5);
-    }
-
-    test!(invalid_get_version_request_creation);
-    fn invalid_get_version_request_creation() {
-        // A request with non-zero arg is considered invalid.
-        assert_eq!(
-            <GetVersionRequest as Request>::new(vec![Value::Null]).unwrap_err(),
-            Error::RequestMalformed
-        );
-    }
-
-    test!(invalid_get_version_response_creation);
-    fn invalid_get_version_response_creation() {
-        // A response with non-zero error_code is an invalid success response.
-        assert_eq!(
-            <GetVersionResponse as Response>::new(vec![
-                Value::from(SecretkeeperError::RequestMalformed as u16),
-                Value::from(5)
-            ])
-            .unwrap_err(),
-            Error::ResponseMalformed
-        );
-
-        // A response with incorrect size of array is invalid.
-        assert_eq!(
-            <GetVersionResponse as Response>::new(vec![
-                Value::from(ERROR_OK),
-                Value::from(5),
-                Value::from(7)
-            ])
-            .unwrap_err(),
-            Error::ResponseMalformed
-        );
-
-        // A response with incorrect type is invalid.
-        <GetVersionResponse as Response>::new(vec![Value::from(ERROR_OK), Value::from("a tstr")])
-            .unwrap_err();
-    }
-
-    test!(invalid_error_response_creation);
-    fn invalid_error_response_creation() {
-        // A response with ERROR_OK(0) as the error_code is an invalid error response.
-        assert_eq!(
-            <SecretkeeperError as Response>::new(vec![Value::from(ERROR_OK)]).unwrap_err(),
-            Error::ResponseMalformed
-        );
-    }
-}
diff --git a/secretkeeper/dice_policy/Android.bp b/secretkeeper/dice_policy/Android.bp
deleted file mode 100644
index 4f1e8b6..0000000
--- a/secretkeeper/dice_policy/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_defaults {
-    name: "libdice_policy.defaults",
-    crate_name: "dice_policy",
-    defaults: ["avf_build_flags_rust"],
-    srcs: ["src/lib.rs"],
-    edition: "2021",
-    prefer_rlib: true,
-    rustlibs: [
-        "libanyhow",
-        "libciborium",
-        "libcoset",
-        "libnum_traits",
-    ],
-    proc_macros: ["libnum_derive"],
-}
-
-rust_library {
-    name: "libdice_policy",
-    defaults: ["libdice_policy.defaults"],
-}
-
-rust_test {
-    name: "libdice_policy.test",
-    defaults: [
-        "libdice_policy.defaults",
-        "rdroidtest.defaults",
-    ],
-    test_suites: ["general-tests"],
-    rustlibs: [
-        "librustutils",
-        "libscopeguard",
-    ],
-}
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
deleted file mode 100644
index 076ba3b..0000000
--- a/secretkeeper/dice_policy/src/lib.rs
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * 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.
- */
-
-//! A “DICE policy” is a format for setting constraints on a DICE chain. A DICE chain policy
-//! verifier takes a policy and a DICE chain, and returns a boolean indicating whether the
-//! DICE chain meets the constraints set out on a policy.
-//!
-//! This forms the foundation of Dice Policy aware Authentication (DPA-Auth), where the server
-//! authenticates a client by comparing its dice chain against a set policy.
-//!
-//! Another use is "sealing", where clients can use an appropriately constructed dice policy to
-//! seal a secret. Unsealing is only permitted if dice chain of the component requesting unsealing
-//! complies with the policy.
-//!
-//! A typical policy will assert things like:
-//! # DK_pub must have this value
-//! # The DICE chain must be exactly five certificates long
-//! # authorityHash in the third certificate must have this value
-//! securityVersion in the fourth certificate must be an integer greater than 8
-//!
-//! These constraints used to express policy are (for now) limited to following 2 types:
-//! 1. Exact Match: useful for enforcing rules like authority hash should be exactly equal.
-//! 2. Greater than or equal to: Useful for setting policies that seal
-//! Anti-rollback protected entities (should be accessible to versions >= present).
-//!
-//! Dice Policy CDDL:
-//!
-//! dicePolicy = [
-//! 1, ; dice policy version
-//! + nodeConstraintList ; for each entry in dice chain
-//! ]
-//!
-//! nodeConstraintList = [
-//!     * nodeConstraint
-//! ]
-//!
-//! ; We may add a hashConstraint item later
-//! nodeConstraint = exactMatchConstraint / geConstraint
-//!
-//! exactMatchConstraint = [1, keySpec, value]
-//! geConstraint = [2, keySpec, int]
-//!
-//! keySpec = [value+]
-//!
-//! value = bool / int / tstr / bstr
-
-use anyhow::{anyhow, bail, ensure, Context, Result};
-use ciborium::Value;
-use coset::{AsCborValue, CoseSign1};
-use num_derive::FromPrimitive;
-use num_traits::FromPrimitive;
-use std::borrow::Cow;
-use std::iter::zip;
-
-const DICE_POLICY_VERSION: u64 = 1;
-
-/// Constraint Types supported in Dice policy.
-#[repr(u16)]
-#[non_exhaustive]
-#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
-pub enum ConstraintType {
-    /// Enforce exact match criteria, indicating the policy should match
-    /// if the dice chain has exact same specified values.
-    ExactMatch = 1,
-    /// Enforce Greater than or equal to criteria. When applied on security_version, this
-    /// can be useful to set policy that matches dice chains with same or upgraded images.
-    GreaterOrEqual = 2,
-}
-
-/// ConstraintSpec is used to specify which constraint type to apply and
-/// on which all entries in a dice node.
-/// See documentation of `from_dice_chain()` for examples.
-pub struct ConstraintSpec {
-    constraint_type: ConstraintType,
-    // path is essentially a list of label/int.
-    // It identifies which entry (in a dice node) to be applying constraints on.
-    path: Vec<i64>,
-}
-
-impl ConstraintSpec {
-    /// Construct the ConstraintSpec.
-    pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> {
-        Ok(ConstraintSpec { constraint_type, path })
-    }
-}
-
-// TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr).
-// and maybe convert it into struct.
-/// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value)
-#[derive(Debug, PartialEq)]
-struct Constraint(u16, Vec<i64>, Value);
-
-/// List of all constraints on a dice node.
-#[derive(Debug, PartialEq)]
-struct NodeConstraints(Box<[Constraint]>);
-
-/// Module for working with dice policy.
-#[derive(Debug, PartialEq)]
-pub struct DicePolicy {
-    version: u64,
-    node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain.
-}
-
-impl DicePolicy {
-    /// Construct a dice policy from a given dice chain.
-    /// This can be used by clients to construct a policy to seal secrets.
-    /// Constraints on all but first dice node is applied using constraint_spec argument.
-    /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node.
-    ///
-    /// # Arguments
-    /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE.
-    /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
-    ///
-    /// `constraint_spec`: List of constraints to be applied on dice node.
-    /// Each constraint is a ConstraintSpec object.
-    ///
-    /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion).
-    ///
-    /// Examples of constraint_spec:
-    ///  1. For exact_match on auth_hash & greater_or_equal on security_version
-    ///    constraint_spec =[
-    ///     (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
-    ///     (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
-    ///    ];
-    ///
-    /// 2. For hypothetical (and highly simplified) dice chain:
-    ///
-    ///    [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
-    ///    The following can be used
-    ///    constraint_spec =[
-    ///     ConstraintSpec(ConstraintType::ExactMatch, vec![1]),         // exact_matches value 'a'
-    ///     ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
-    ///    ];
-    pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
-        let dice_chain = deserialize_dice_chain(dice_chain)?;
-        let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
-        let mut it = dice_chain.into_iter();
-
-        constraints_list.push(NodeConstraints(Box::new([Constraint(
-            ConstraintType::ExactMatch as u16,
-            Vec::new(),
-            it.next().unwrap(),
-        )])));
-
-        for (n, value) in it.enumerate() {
-            let entry = cbor_value_from_cose_sign(value)
-                .with_context(|| format!("Unable to get Cose payload at: {}", n))?;
-            constraints_list.push(payload_to_constraints(entry, constraint_spec)?);
-        }
-
-        Ok(DicePolicy {
-            version: DICE_POLICY_VERSION,
-            node_constraints_list: constraints_list.into_boxed_slice(),
-        })
-    }
-
-    /// Dice chain policy verifier - Compare the input dice chain against this Dice policy.
-    /// The method returns Ok() if the dice chain meets the constraints set in Dice policy,
-    /// otherwise returns error in case of mismatch.
-    /// TODO(b/291238565) Create a separate error module for DicePolicy mismatches.
-    pub fn matches_dice_chain(&self, dice_chain: &[u8]) -> Result<()> {
-        let dice_chain = deserialize_dice_chain(dice_chain)?;
-        ensure!(
-            dice_chain.len() == self.node_constraints_list.len(),
-            format!(
-                "Dice chain size({}) does not match policy({})",
-                dice_chain.len(),
-                self.node_constraints_list.len()
-            )
-        );
-
-        for (n, (dice_node, node_constraints)) in
-            zip(dice_chain, self.node_constraints_list.iter()).enumerate()
-        {
-            let dice_node_payload = if n == 0 {
-                dice_node
-            } else {
-                cbor_value_from_cose_sign(dice_node)
-                    .with_context(|| format!("Unable to get Cose payload at: {}", n))?
-            };
-            check_constraints_on_node(node_constraints, &dice_node_payload)
-                .context(format!("Mismatch found at {}", n))?;
-        }
-        Ok(())
-    }
-}
-
-fn check_constraints_on_node(node_constraints: &NodeConstraints, dice_node: &Value) -> Result<()> {
-    for constraint in node_constraints.0.iter() {
-        check_constraint_on_node(constraint, dice_node)?;
-    }
-    Ok(())
-}
-
-fn check_constraint_on_node(constraint: &Constraint, dice_node: &Value) -> Result<()> {
-    let Constraint(cons_type, path, value_in_constraint) = constraint;
-    let value_in_node = lookup_value_in_nested_map(dice_node, path)?;
-    match ConstraintType::from_u16(*cons_type).ok_or(anyhow!("Unexpected Constraint type"))? {
-        ConstraintType::ExactMatch => ensure!(value_in_node == *value_in_constraint),
-        ConstraintType::GreaterOrEqual => {
-            let value_in_node = value_in_node
-                .as_integer()
-                .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
-            let value_min = value_in_constraint
-                .as_integer()
-                .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
-            ensure!(value_in_node >= value_min);
-        }
-    };
-    Ok(())
-}
-
-// Take the payload of a dice node & construct the constraints on it.
-fn payload_to_constraints(
-    payload: Value,
-    constraint_spec: &[ConstraintSpec],
-) -> Result<NodeConstraints> {
-    let mut node_constraints: Vec<Constraint> = Vec::with_capacity(constraint_spec.len());
-    for constraint_item in constraint_spec {
-        let constraint_path = constraint_item.path.to_vec();
-        if constraint_path.is_empty() {
-            bail!("Expected non-empty key spec");
-        }
-        let val = lookup_value_in_nested_map(&payload, &constraint_path)
-            .context(format!("Value not found for constraint_path {:?}", constraint_path))?;
-        let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val);
-        node_constraints.push(constraint);
-    }
-    Ok(NodeConstraints(node_constraints.into_boxed_slice()))
-}
-
-// Lookup value corresponding to constraint path in nested map.
-// This function recursively calls itself.
-// The depth of recursion is limited by the size of constraint_path.
-fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> {
-    if constraint_path.is_empty() {
-        return Ok(cbor_map.clone());
-    }
-    let explicit_map = get_map_from_value(cbor_map)?;
-    let val = lookup_value_in_map(&explicit_map, constraint_path[0])
-        .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?;
-    lookup_value_in_nested_map(val, &constraint_path[1..])
-}
-
-fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> {
-    match cbor_map {
-        Value::Bytes(b) => value_from_bytes(b)?
-            .into_map()
-            .map(Cow::Owned)
-            .map_err(|e| anyhow!("Expected a CBOR map: {:?}", e)),
-        Value::Map(map) => Ok(Cow::Borrowed(map)),
-        _ => bail!("Expected a CBOR map {:?}", cbor_map),
-    }
-}
-
-fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> {
-    let key = Value::Integer(key.into());
-    for (k, v) in map.iter() {
-        if k == &key {
-            return Some(v);
-        }
-    }
-    None
-}
-
-/// Extract the payload from the COSE Sign
-fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> {
-    let sign1 =
-        CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?;
-    match sign1.payload {
-        None => bail!("Missing payload"),
-        Some(payload) => Ok(value_from_bytes(&payload)?),
-    }
-}
-fn deserialize_dice_chain(dice_chain_bytes: &[u8]) -> Result<Vec<Value>> {
-    // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
-    // format and if not, convert it.
-    let dice_chain =
-        value_from_bytes(dice_chain_bytes).context("Unable to decode top-level CBOR")?;
-    let dice_chain = match dice_chain {
-        Value::Array(array) if array.len() >= 2 => array,
-        _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
-    };
-    Ok(dice_chain)
-}
-
-/// Decodes the provided binary CBOR-encoded value and returns a
-/// ciborium::Value struct wrapped in Result.
-fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
-    let value = ciborium::de::from_reader(&mut bytes)?;
-    // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do.
-    if !bytes.is_empty() {
-        bail!("Unexpected trailing data while converting to CBOR value");
-    }
-    Ok(value)
-}
-
-#[cfg(test)]
-rdroidtest::test_main!();
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use ciborium::cbor;
-    use coset::{CoseKey, Header, ProtectedHeader};
-    use rdroidtest::test;
-
-    const AUTHORITY_HASH: i64 = -4670549;
-    const CONFIG_DESC: i64 = -4670548;
-    const COMPONENT_NAME: i64 = -70002;
-    const KEY_MODE: i64 = -4670551;
-
-    // Helper struct to encapsulate artifacts that are useful for unit tests.
-    struct TestArtifacts {
-        // A dice chain.
-        input_dice: Vec<u8>,
-        // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy.
-        constraint_spec: Vec<ConstraintSpec>,
-        // The expected dice policy if above constraint_spec is applied to input_dice.
-        expected_dice_policy: DicePolicy,
-        // Another dice chain, which is almost same as the input_dice, but (roughly) imitates
-        // an 'updated' one, ie, some int entries are higher than corresponding entry
-        // in input_chain.
-        updated_input_dice: Vec<u8>,
-    }
-
-    impl TestArtifacts {
-        // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
-        // chain of certificates & a list of constraint_spec on this.
-        fn get_example() -> Self {
-            const EXAMPLE_NUM_1: i64 = 59765;
-            const EXAMPLE_NUM_2: i64 = 59766;
-            const EXAMPLE_STRING: &str = "testing_dice_policy";
-            const UNCONSTRAINED_STRING: &str = "unconstrained_string";
-            const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string";
-
-            let rot_key = CoseKey::default().to_cbor_value().unwrap();
-            let input_dice = Self::get_dice_chain_helper(
-                rot_key.clone(),
-                EXAMPLE_NUM_1,
-                EXAMPLE_STRING,
-                UNCONSTRAINED_STRING,
-            );
-
-            // Now construct constraint_spec on the input dice, note this will use the keys
-            // which are also hardcoded within the get_dice_chain_helper.
-
-            let constraint_spec = vec![
-                ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
-                // Notice how key "2" is (deliberately) absent in ConstraintSpec
-                // so policy should not constrain it.
-                ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
-            ];
-            let expected_dice_policy = DicePolicy {
-                version: 1,
-                node_constraints_list: Box::new([
-                    NodeConstraints(Box::new([Constraint(
-                        ConstraintType::ExactMatch as u16,
-                        vec![],
-                        rot_key.clone(),
-                    )])),
-                    NodeConstraints(Box::new([
-                        Constraint(
-                            ConstraintType::ExactMatch as u16,
-                            vec![1],
-                            Value::Text(EXAMPLE_STRING.to_string()),
-                        ),
-                        Constraint(
-                            ConstraintType::GreaterOrEqual as u16,
-                            vec![3, 100],
-                            Value::from(EXAMPLE_NUM_1),
-                        ),
-                    ])),
-                ]),
-            };
-
-            let updated_input_dice = Self::get_dice_chain_helper(
-                rot_key.clone(),
-                EXAMPLE_NUM_2,
-                EXAMPLE_STRING,
-                ANOTHER_UNCONSTRAINED_STRING,
-            );
-            Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice }
-        }
-
-        // Helper method method to generate a dice chain with a given rot_key.
-        // Other arguments are ad-hoc values in the nested map. Callers use these to
-        // construct appropriate constrains in dice policies.
-        fn get_dice_chain_helper(
-            rot_key: Value,
-            version: i64,
-            constrained_string: &str,
-            unconstrained_string: &str,
-        ) -> Vec<u8> {
-            let nested_payload = cbor!({
-                100 => version
-            })
-            .unwrap();
-
-            let payload = cbor!({
-                1 => constrained_string,
-                2 => unconstrained_string,
-                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
-            })
-            .unwrap();
-            let payload = value_to_bytes(&payload).unwrap();
-            let dice_node = CoseSign1 {
-                protected: ProtectedHeader::default(),
-                unprotected: Header::default(),
-                payload: Some(payload),
-                signature: b"ddef".to_vec(),
-            }
-            .to_cbor_value()
-            .unwrap();
-            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
-
-            value_to_bytes(&input_dice).unwrap()
-        }
-    }
-
-    test!(policy_structure_check);
-    fn policy_structure_check() {
-        let example = TestArtifacts::get_example();
-        let policy =
-            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec).unwrap();
-
-        // Assert policy is exactly as expected!
-        assert_eq!(policy, example.expected_dice_policy);
-    }
-
-    test!(policy_matches_original_dice_chain);
-    fn policy_matches_original_dice_chain() {
-        let example = TestArtifacts::get_example();
-        assert!(
-            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
-                .unwrap()
-                .matches_dice_chain(&example.input_dice)
-                .is_ok(),
-            "The dice chain did not match the policy constructed out of it!"
-        );
-    }
-
-    test!(policy_matches_updated_dice_chain);
-    fn policy_matches_updated_dice_chain() {
-        let example = TestArtifacts::get_example();
-        assert!(
-            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
-                .unwrap()
-                .matches_dice_chain(&example.updated_input_dice)
-                .is_ok(),
-            "The updated dice chain did not match the original policy!"
-        );
-    }
-
-    test!(policy_mismatch_downgraded_dice_chain);
-    fn policy_mismatch_downgraded_dice_chain() {
-        let example = TestArtifacts::get_example();
-        assert!(
-            DicePolicy::from_dice_chain(&example.updated_input_dice, &example.constraint_spec)
-                .unwrap()
-                .matches_dice_chain(&example.input_dice)
-                .is_err(),
-            "The (downgraded) dice chain matched the policy constructed out of the 'updated'\
-            dice chain!!"
-        );
-    }
-
-    test!(policy_dice_size_is_same);
-    fn policy_dice_size_is_same() {
-        // This is the number of certs in compos bcc (including the first ROT)
-        // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
-        // `hwtrust --verbose dice-chain [path]/composbcc`
-        let compos_dice_chain_size: usize = 5;
-        let input_dice = include_bytes!("../testdata/composbcc");
-        let constraint_spec = [
-            ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
-            ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(),
-            ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME])
-                .unwrap(),
-        ];
-        let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
-        assert_eq!(policy.node_constraints_list.len(), compos_dice_chain_size);
-    }
-
-    /// Encodes a ciborium::Value into bytes.
-    fn value_to_bytes(value: &Value) -> Result<Vec<u8>> {
-        let mut bytes: Vec<u8> = Vec::new();
-        ciborium::ser::into_writer(&value, &mut bytes)?;
-        Ok(bytes)
-    }
-}
diff --git a/secretkeeper/dice_policy/testdata/composbcc b/secretkeeper/dice_policy/testdata/composbcc
deleted file mode 100644
index fb3e006..0000000
--- a/secretkeeper/dice_policy/testdata/composbcc
+++ /dev/null
Binary files differ
diff --git a/service_vm/comm/Android.bp b/service_vm/comm/Android.bp
index 6e05587..bf923a4 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -23,7 +23,9 @@
     rustlibs: [
         "libbssl_avf_error_nostd",
         "libciborium_nostd",
+        "libcbor_util_nostd",
         "libcoset_nostd",
+        "libder_nostd",
         "liblog_rust_nostd",
         "libserde_nostd",
     ],
@@ -35,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 757d080..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,33 +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")?,
         })
     }
 }
-
-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"))
-    }
-}
-
-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/message.rs b/service_vm/comm/src/message.rs
index 6dd0ccd..80a9608 100644
--- a/service_vm/comm/src/message.rs
+++ b/service_vm/comm/src/message.rs
@@ -66,6 +66,13 @@
 
     /// The key blob retrieved from RKPD by virtualizationservice.
     pub remotely_provisioned_key_blob: Vec<u8>,
+
+    /// The leaf certificate of the certificate chain retrieved from RKPD by
+    /// virtualizationservice.
+    ///
+    /// This certificate is a DER-encoded X.509 certificate that includes the remotely
+    /// provisioned public key.
+    pub remotely_provisioned_cert: Vec<u8>,
 }
 
 /// Represents a response to a request sent to the service VM.
@@ -120,6 +127,12 @@
 
     /// The requested operation has not been implemented.
     OperationUnimplemented,
+
+    /// An error happened during the DER encoding/decoding.
+    DerError,
+
+    /// The DICE chain from the client VM is invalid.
+    InvalidDiceChain,
 }
 
 impl fmt::Display for RequestProcessingError {
@@ -142,6 +155,12 @@
             Self::OperationUnimplemented => {
                 write!(f, "The requested operation has not been implemented")
             }
+            Self::DerError => {
+                write!(f, "An error happened during the DER encoding/decoding")
+            }
+            Self::InvalidDiceChain => {
+                write!(f, "The DICE chain from the client VM is invalid")
+            }
         }
     }
 }
@@ -166,6 +185,14 @@
     }
 }
 
+#[cfg(not(feature = "std"))]
+impl From<der::Error> for RequestProcessingError {
+    fn from(e: der::Error) -> Self {
+        error!("DER encoding/decoding error: {e}");
+        Self::DerError
+    }
+}
+
 /// Represents the params passed to GenerateCertificateRequest
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct GenerateCertificateRequestParams {
diff --git a/service_vm/comm/src/vsock.rs b/service_vm/comm/src/vsock.rs
index aa7166d..7f7cf25 100644
--- a/service_vm/comm/src/vsock.rs
+++ b/service_vm/comm/src/vsock.rs
@@ -18,7 +18,7 @@
 const NON_PROTECTED_VM_PORT: u32 = 5680;
 
 /// VM Type.
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum VmType {
     /// Protected VM.
     ProtectedVm,
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/secretkeeper/comm/src/lib.rs b/service_vm/fake_chain/src/lib.rs
similarity index 67%
rename from secretkeeper/comm/src/lib.rs
rename to service_vm/fake_chain/src/lib.rs
index 9a10ac0..a5ab828 100644
--- a/secretkeeper/comm/src/lib.rs
+++ b/service_vm/fake_chain/src/lib.rs
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-//! This library exposes data structures and methods that can be used by Secretkeeper HAL & client
-//! implementation. This is compatible with Secretkeeper HAL specification.
+//! Provides functions to build a test chain for non-protected rialto and tests.
 
-#![no_std]
+#![cfg_attr(not(feature = "std"), no_std)]
+
 extern crate alloc;
 
-mod cbor_convert;
-pub mod data_types;
+// `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/Android.bp b/service_vm/requests/Android.bp
index ecede8b..52b54b4 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -21,10 +21,13 @@
         "libcbor_util_nostd",
         "libciborium_nostd",
         "libcoset_nostd",
+        "libder_nostd",
         "libdiced_open_dice_nostd",
         "liblog_rust_nostd",
         "libserde_nostd",
         "libservice_vm_comm_nostd",
+        "libspki_nostd",
+        "libx509_cert_nostd",
         "libzeroize_nostd",
     ],
 }
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
new file mode 100644
index 0000000..73828a7
--- /dev/null
+++ b/service_vm/requests/src/cert.rs
@@ -0,0 +1,171 @@
+// 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.
+
+//! Generation of certificates and attestation extensions.
+
+use crate::dice::SubComponent;
+use alloc::vec;
+use alloc::vec::Vec;
+use der::{
+    asn1::{BitStringRef, ObjectIdentifier, UIntRef, Utf8StringRef},
+    oid::AssociatedOid,
+    Decode, Sequence,
+};
+use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo};
+use x509_cert::{
+    certificate::{Certificate, TbsCertificate, Version},
+    ext::Extension,
+    name::Name,
+    time::Validity,
+};
+
+/// OID value for ECDSA with SHA-256, see RFC 5912 s6.
+const ECDSA_WITH_SHA_256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
+
+/// OID value for the protected VM remote attestation extension.
+///
+/// This OID value was added at cl/584542390.
+const AVF_ATTESTATION_EXTENSION_V1: ObjectIdentifier =
+    ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.29.1");
+
+/// Attestation extension contents
+///
+/// ```asn1
+/// AttestationDescription ::= SEQUENCE {
+///     attestationChallenge       OCTET_STRING,
+///     isVmSecure                 BOOLEAN,
+///     vmComponents               SEQUENCE OF VmComponent,
+/// }
+/// ```
+#[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> {
+    const OID: ObjectIdentifier = AVF_ATTESTATION_EXTENSION_V1;
+}
+
+impl<'a> AttestationExtension<'a> {
+    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,
+        })
+    }
+}
+
+/// Builds an X.509 `Certificate` as defined in RFC 5280 Section 4.1:
+///
+/// ```asn1
+/// Certificate  ::=  SEQUENCE  {
+///   tbsCertificate       TBSCertificate,
+///   signatureAlgorithm   AlgorithmIdentifier,
+///   signature            BIT STRING
+/// }
+/// ```
+pub(crate) fn build_certificate<'a>(
+    tbs_cert: TbsCertificate<'a>,
+    signature: &'a [u8],
+) -> der::Result<Certificate<'a>> {
+    Ok(Certificate {
+        signature_algorithm: tbs_cert.signature,
+        tbs_certificate: tbs_cert,
+        signature: BitStringRef::new(0, signature)?,
+    })
+}
+
+/// Builds an X.509 `TbsCertificate` as defined in RFC 5280 Section 4.1:
+///
+/// ```asn1
+/// TBSCertificate  ::=  SEQUENCE  {
+///   version         [0]  EXPLICIT Version DEFAULT v1,
+///   serialNumber         CertificateSerialNumber,
+///   signature            AlgorithmIdentifier,
+///   issuer               Name,
+///   validity             Validity,
+///   subject              Name,
+///   subjectPublicKeyInfo SubjectPublicKeyInfo,
+///   issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+///                        -- If present, version MUST be v2 or v3
+///   subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+///                        -- If present, version MUST be v2 or v3
+///   extensions      [3]  Extensions OPTIONAL
+///                        -- If present, version MUST be v3 --
+/// }
+/// ```
+pub(crate) fn build_tbs_certificate<'a>(
+    serial_number: &'a [u8],
+    issuer: Name<'a>,
+    subject: Name<'a>,
+    validity: Validity,
+    subject_public_key_info: &'a [u8],
+    attestation_ext: &'a [u8],
+) -> der::Result<TbsCertificate<'a>> {
+    let signature = AlgorithmIdentifier { oid: ECDSA_WITH_SHA_256, parameters: None };
+    let subject_public_key_info = SubjectPublicKeyInfo::from_der(subject_public_key_info)?;
+    let extensions = vec![Extension {
+        extn_id: AttestationExtension::OID,
+        critical: false,
+        extn_value: attestation_ext,
+    }];
+    Ok(TbsCertificate {
+        version: Version::V3,
+        serial_number: UIntRef::new(serial_number)?,
+        signature,
+        issuer,
+        validity,
+        subject,
+        subject_public_key_info,
+        issuer_unique_id: None,
+        subject_unique_id: None,
+        extensions: Some(extensions),
+    })
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 612605f..cfdac2d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -15,14 +15,18 @@
 //! This module contains functions related to the attestation of the
 //! client VM.
 
+use crate::cert;
+use crate::dice::{validate_client_vm_dice_chain_prefix_match, ClientVmDiceChain};
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
-use bssl_avf::{sha256, EcKey};
+use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
 use core::result;
 use coset::{CborSerializable, CoseSign};
+use der::{Decode, Encode};
 use diced_open_dice::DiceArtifacts;
 use log::error;
 use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
+use x509_cert::{certificate::Certificate, name::Name};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
@@ -40,31 +44,71 @@
     })?;
     let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
 
+    // Validates the prefix of the Client VM DICE chain in the CSR.
+    let service_vm_dice_chain =
+        dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
+    let client_vm_dice_chain =
+        validate_client_vm_dice_chain_prefix_match(&csr.dice_cert_chain, service_vm_dice_chain)?;
+    // Validates the signatures in the Client VM DICE chain and extracts the partially decoded
+    // DiceChainEntryPayloads.
+    let client_vm_dice_chain =
+        ClientVmDiceChain::validate_signatures_and_parse_dice_chain(client_vm_dice_chain)?;
+
     // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
     let aad = &[];
 
+    // Verifies the first signature with the leaf private key in the DICE chain.
     // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
     // the DICE chain in `cose_sign`.
 
-    let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+    // Verifies the second signature with the public key in the CSR payload.
+    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)
     })?;
+    let subject_public_key_info = PKey::try_from(ec_public_key)?.subject_public_key_info()?;
 
-    // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
-    // cert with RKP VM's DICE chain.
+    // Builds the TBSCertificate.
+    // The serial number can be up to 20 bytes according to RFC5280 s4.1.2.2.
+    // In this case, a serial number with a length of 20 bytes is used to ensure that each
+    // certificate signed by RKP VM has a unique serial number.
+    let mut serial_number = [0u8; 20];
+    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(
+        &serial_number,
+        rkp_cert.tbs_certificate.subject,
+        Name::from_der(&subject)?,
+        rkp_cert.tbs_certificate.validity,
+        &subject_public_key_info,
+        &attestation_ext,
+    )?;
 
+    // Signs the TBSCertificate and builds the Certificate.
+    // The two private key structs below will be zeroed out on drop.
     let private_key =
         decrypt_private_key(&params.remotely_provisioned_key_blob, dice_artifacts.cdi_seal())
             .map_err(|e| {
                 error!("Failed to decrypt the remotely provisioned key blob: {e}");
                 RequestProcessingError::FailedToDecryptKeyBlob
             })?;
-    let _ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
-
-    // TODO(b/309441500): Build a new certificate signed with the remotely provisioned
-    // `_private_key`.
-    Err(RequestProcessingError::OperationUnimplemented)
+    let ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
+    let signature = ecdsa_sign(&ec_private_key, &tbs_cert.to_vec()?)?;
+    let certificate = cert::build_certificate(tbs_cert, &signature)?;
+    Ok(certificate.to_vec()?)
 }
 
 fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
@@ -72,3 +116,8 @@
     let digest = sha256(message)?;
     key.ecdsa_verify(signature, &digest)
 }
+
+fn ecdsa_sign(key: &EcKey, message: &[u8]) -> bssl_avf::Result<Vec<u8>> {
+    let digest = sha256(message)?;
+    key.ecdsa_sign(&digest)
+}
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
new file mode 100644
index 0000000..557b678
--- /dev/null
+++ b/service_vm/requests/src/dice.rs
@@ -0,0 +1,491 @@
+// 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.
+
+//! 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;
+use coset::{
+    self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation,
+};
+use diced_open_dice::{DiceMode, HASH_SIZE};
+use log::error;
+use service_vm_comm::RequestProcessingError;
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+const CODE_HASH: i64 = -4670545;
+const CONFIG_DESC: i64 = -4670548;
+const AUTHORITY_HASH: i64 = -4670549;
+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:
+///
+/// DiceCertChain = [
+///     PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384,  ; UDS_Pub
+///     + DiceChainEntry,               ; First CDI_Certificate -> Last CDI_Certificate
+/// ]
+#[derive(Debug, Clone)]
+pub(crate) struct ClientVmDiceChain {
+    pub(crate) payloads: Vec<DiceChainEntryPayload>,
+}
+
+impl ClientVmDiceChain {
+    /// Validates the signatures of the entries in the `client_vm_dice_chain` as following:
+    ///
+    /// - The first entry of the `client_vm_dice_chain` must be signed with the root public key.
+    /// - After the first entry, each entry of the `client_vm_dice_chain` must be signed with the
+    ///  subject public key of the previous entry.
+    ///
+    /// Returns a partially decoded client VM's DICE chain if the verification succeeds.
+    pub(crate) fn validate_signatures_and_parse_dice_chain(
+        mut client_vm_dice_chain: Vec<Value>,
+    ) -> Result<Self> {
+        let root_public_key =
+            CoseKey::from_cbor_value(client_vm_dice_chain.remove(0))?.try_into()?;
+
+        let mut payloads = Vec::with_capacity(client_vm_dice_chain.len());
+        let mut previous_public_key = &root_public_key;
+        for (i, value) in client_vm_dice_chain.into_iter().enumerate() {
+            let payload = DiceChainEntryPayload::validate_cose_signature_and_extract_payload(
+                value,
+                previous_public_key,
+            )
+            .map_err(|e| {
+                error!("Failed to verify the DICE chain entry {}: {:?}", i, e);
+                e
+            })?;
+            payloads.push(payload);
+            previous_public_key = &payloads.last().unwrap().subject_public_key;
+        }
+        // After successfully calling `validate_client_vm_dice_chain_prefix_match`, we can be
+        // certain that the client VM's DICE chain must contain at least three entries that
+        // describe:
+        // - pvmfw
+        // - Microdroid kernel
+        // - Apk/Apexes
+        assert!(
+            payloads.len() >= 3,
+            "The client VM DICE chain must contain at least three DiceChainEntryPayloads"
+        );
+        let chain = Self { payloads };
+        chain.validate_microdroid_components_names()?;
+        Ok(chain)
+    }
+
+    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.
+    pub(crate) fn all_entries_are_secure(&self) -> bool {
+        self.payloads.iter().all(|p| p.mode == DiceMode::kDiceModeNormal)
+    }
+}
+
+/// Validates that the `client_vm_dice_chain` matches the `service_vm_dice_chain` up to the pvmfw
+/// entry.
+///
+/// Returns a CBOR value array of the client VM's DICE chain if the verification succeeds.
+pub(crate) fn validate_client_vm_dice_chain_prefix_match(
+    client_vm_dice_chain: &[u8],
+    service_vm_dice_chain: &[u8],
+) -> Result<Vec<Value>> {
+    let client_vm_dice_chain =
+        value_to_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
+    let 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:
+        //   - pvmfw
+        //   - Service VM kernel
+        error!("The service VM DICE chain must contain at least three entries");
+        return Err(RequestProcessingError::InternalError);
+    }
+    // Ignores the last entry that describes service VM
+    let entries_up_to_pvmfw = &service_vm_dice_chain[0..(service_vm_dice_chain.len() - 1)];
+    if entries_up_to_pvmfw.len() + 2 != client_vm_dice_chain.len() {
+        // Client VM DICE chain = entries_up_to_pvmfw
+        //    + Microdroid kernel entry (added in pvmfw)
+        //    + Apk/Apexes entry (added in microdroid)
+        error!("The client VM's DICE chain must contain exactly two extra entries");
+        return Err(RequestProcessingError::InvalidDiceChain);
+    }
+    if entries_up_to_pvmfw != &client_vm_dice_chain[0..entries_up_to_pvmfw.len()] {
+        error!(
+            "The client VM's DICE chain does not match service VM's DICE chain up to \
+             the pvmfw entry"
+        );
+        return Err(RequestProcessingError::InvalidDiceChain);
+    }
+    Ok(client_vm_dice_chain)
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct PublicKey(CoseKey);
+
+impl TryFrom<CoseKey> for PublicKey {
+    type Error = RequestProcessingError;
+
+    fn try_from(key: CoseKey) -> Result<Self> {
+        if !key.key_ops.contains(&KeyOperation::Assigned(iana::KeyOperation::Verify)) {
+            error!("Public key does not support verification");
+            return Err(RequestProcessingError::InvalidDiceChain);
+        }
+        Ok(Self(key))
+    }
+}
+
+/// Represents a partially decoded `DiceChainEntryPayload`. The whole payload is defined in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
+#[derive(Debug, Clone)]
+pub(crate) struct DiceChainEntryPayload {
+    /// TODO(b/310931749): Verify the DICE chain entry using the subject public key.
+    #[allow(dead_code)]
+    subject_public_key: PublicKey,
+    mode: DiceMode,
+    /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
+    #[allow(dead_code)]
+    code_hash: [u8; HASH_SIZE],
+    #[allow(dead_code)]
+    authority_hash: [u8; HASH_SIZE],
+    config_descriptor: ConfigDescriptor,
+}
+
+impl DiceChainEntryPayload {
+    /// Validates the signature of the provided CBOR value with the provided public key and
+    /// extracts payload from the value.
+    fn validate_cose_signature_and_extract_payload(
+        value: Value,
+        _authority_public_key: &PublicKey,
+    ) -> Result<Self> {
+        let cose_sign1 = CoseSign1::from_cbor_value(value)?;
+        // TODO(b/310931749): Verify the DICE chain entry using `authority_public_key`.
+
+        let payload = cose_sign1.payload.ok_or_else(|| {
+            error!("No payload found in the DICE chain entry");
+            RequestProcessingError::InvalidDiceChain
+        })?;
+        let entries = value_to_map(Value::from_slice(&payload)?, "DiceChainEntryPayload")?;
+        build_payload(entries)
+    }
+}
+
+fn build_payload(entries: Vec<(Value, Value)>) -> Result<DiceChainEntryPayload> {
+    let mut builder = PayloadBuilder::default();
+    for (key, value) in entries.into_iter() {
+        let key: i64 = value_to_num(key, "DiceChainEntryPayload key")?;
+        match key {
+            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 => {
+                let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+                builder.code_hash(code_hash)?;
+            }
+            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()
+}
+
+/// Represents a partially decoded `ConfigurationDescriptor`.
+///
+/// The whole `ConfigurationDescriptor` is defined in:
+///
+/// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+/// generateCertificateRequestV2.cddl
+#[derive(Debug, Clone)]
+pub(crate) struct ConfigDescriptor {
+    component_name: String,
+    sub_components: Option<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptor {
+    fn from_slice(data: &[u8]) -> Result<Self> {
+        let value = Value::from_slice(data)?;
+        let entries = value_to_map(value, "ConfigDescriptor")?;
+        let mut builder = ConfigDescriptorBuilder::default();
+        for (key, value) in entries.into_iter() {
+            let key: i64 = value_to_num(key, "ConfigDescriptor key")?;
+            match key {
+                CONFIG_DESC_COMPONENT_NAME => {
+                    let name = value_to_text(value, "ConfigDescriptor component_name")?;
+                    builder.component_name(name)?;
+                }
+                CONFIG_DESC_SUB_COMPONENTS => {
+                    let sub_components = value_to_array(value, "ConfigDescriptor sub_components")?;
+                    let sub_components = sub_components
+                        .into_iter()
+                        .map(SubComponent::try_from)
+                        .collect::<Result<Vec<_>>>()?;
+                    builder.sub_components(sub_components)?
+                }
+                _ => {}
+            }
+        }
+        builder.build()
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+struct ConfigDescriptorBuilder {
+    component_name: OnceCell<String>,
+    sub_components: OnceCell<Vec<SubComponent>>,
+}
+
+impl ConfigDescriptorBuilder {
+    fn component_name(&mut self, component_name: String) -> Result<()> {
+        set_once(&self.component_name, component_name, "ConfigDescriptor component_name")
+    }
+
+    fn sub_components(&mut self, sub_components: Vec<SubComponent>) -> Result<()> {
+        set_once(&self.sub_components, sub_components, "ConfigDescriptor sub_components")
+    }
+
+    fn build(mut self) -> Result<ConfigDescriptor> {
+        let component_name =
+            take_value(&mut self.component_name, "ConfigDescriptor component_name")?;
+        let sub_components = self.sub_components.take();
+        Ok(ConfigDescriptor { component_name, sub_components })
+    }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct SubComponent {
+    pub(crate) name: String,
+    pub(crate) version: u64,
+    pub(crate) code_hash: Vec<u8>,
+    pub(crate) authority_hash: Vec<u8>,
+}
+
+impl TryFrom<Value> for SubComponent {
+    type Error = RequestProcessingError;
+
+    fn try_from(value: Value) -> Result<Self> {
+        let entries = value_to_map(value, "SubComponent")?;
+        let mut builder = SubComponentBuilder::default();
+        for (key, value) in entries.into_iter() {
+            let key: i64 = value_to_num(key, "SubComponent key")?;
+            match key {
+                SUB_COMPONENT_NAME => {
+                    builder.name(value_to_text(value, "SubComponent component_name")?)?
+                }
+                SUB_COMPONENT_VERSION => {
+                    builder.version(value_to_num(value, "SubComponent version")?)?
+                }
+                SUB_COMPONENT_CODE_HASH => {
+                    builder.code_hash(value_to_bytes(value, "SubComponent code_hash")?)?
+                }
+                SUB_COMPONENT_AUTHORITY_HASH => {
+                    builder.authority_hash(value_to_bytes(value, "SubComponent authority_hash")?)?
+                }
+                k => {
+                    error!("Unknown key in SubComponent: {}", k);
+                    return Err(RequestProcessingError::InvalidDiceChain);
+                }
+            }
+        }
+        builder.build()
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+struct SubComponentBuilder {
+    name: OnceCell<String>,
+    version: OnceCell<u64>,
+    code_hash: OnceCell<Vec<u8>>,
+    authority_hash: OnceCell<Vec<u8>>,
+}
+
+impl SubComponentBuilder {
+    fn name(&mut self, name: String) -> Result<()> {
+        set_once(&self.name, name, "SubComponent name")
+    }
+
+    fn version(&mut self, version: u64) -> Result<()> {
+        set_once(&self.version, version, "SubComponent version")
+    }
+
+    fn code_hash(&mut self, code_hash: Vec<u8>) -> Result<()> {
+        set_once(&self.code_hash, code_hash, "SubComponent code_hash")
+    }
+
+    fn authority_hash(&mut self, authority_hash: Vec<u8>) -> Result<()> {
+        set_once(&self.authority_hash, authority_hash, "SubComponent authority_hash")
+    }
+
+    fn build(mut self) -> Result<SubComponent> {
+        let name = take_value(&mut self.name, "SubComponent name")?;
+        let version = take_value(&mut self.version, "SubComponent version")?;
+        let code_hash = take_value(&mut self.code_hash, "SubComponent code_hash")?;
+        let authority_hash = take_value(&mut self.authority_hash, "SubComponent authority_hash")?;
+        Ok(SubComponent { name, version, code_hash, authority_hash })
+    }
+}
+
+fn to_mode(value: Value) -> Result<DiceMode> {
+    let mode = match value {
+        // Mode is supposed to be encoded as a 1-byte bstr, but some implementations instead
+        // encode it as an integer. Accept either. See b/273552826.
+        // If Mode is omitted, it should be treated as if it was NotConfigured, according to
+        // the Open Profile for DICE spec.
+        Value::Bytes(bytes) => {
+            if bytes.len() != 1 {
+                error!("Bytes array with invalid length for mode: {:?}", bytes.len());
+                return Err(RequestProcessingError::InvalidDiceChain);
+            }
+            bytes[0].into()
+        }
+        Value::Integer(i) => i,
+        v => return Err(CoseError::UnexpectedItem(cbor_value_type(&v), "bstr or int").into()),
+    };
+    let mode = match mode {
+        x if x == (DiceMode::kDiceModeNormal as i64).into() => DiceMode::kDiceModeNormal,
+        x if x == (DiceMode::kDiceModeDebug as i64).into() => DiceMode::kDiceModeDebug,
+        x if x == (DiceMode::kDiceModeMaintenance as i64).into() => DiceMode::kDiceModeMaintenance,
+        // If Mode is invalid, it should be treated as if it was NotConfigured, according to
+        // the Open Profile for DICE spec.
+        _ => DiceMode::kDiceModeNotInitialized,
+    };
+    Ok(mode)
+}
+
+#[derive(Default, Debug, Clone)]
+struct PayloadBuilder {
+    subject_public_key: OnceCell<PublicKey>,
+    mode: OnceCell<DiceMode>,
+    code_hash: OnceCell<[u8; HASH_SIZE]>,
+    authority_hash: OnceCell<[u8; HASH_SIZE]>,
+    config_descriptor: OnceCell<ConfigDescriptor>,
+}
+
+fn set_once<T>(field: &OnceCell<T>, value: T, field_name: &str) -> Result<()> {
+    field.set(value).map_err(|_| {
+        error!("Field '{field_name}' is duplicated in the Payload");
+        RequestProcessingError::InvalidDiceChain
+    })
+}
+
+fn take_value<T>(field: &mut OnceCell<T>, field_name: &str) -> Result<T> {
+    field.take().ok_or_else(|| {
+        error!("Field '{field_name}' is missing in the Payload");
+        RequestProcessingError::InvalidDiceChain
+    })
+}
+
+impl PayloadBuilder {
+    fn subject_public_key(&mut self, key: PublicKey) -> Result<()> {
+        set_once(&self.subject_public_key, key, "subject_public_key")
+    }
+
+    fn mode(&mut self, mode: DiceMode) -> Result<()> {
+        set_once(&self.mode, mode, "mode")
+    }
+
+    fn code_hash(&mut self, code_hash: [u8; HASH_SIZE]) -> Result<()> {
+        set_once(&self.code_hash, code_hash, "code_hash")
+    }
+
+    fn authority_hash(&mut self, authority_hash: [u8; HASH_SIZE]) -> Result<()> {
+        set_once(&self.authority_hash, authority_hash, "authority_hash")
+    }
+
+    fn config_descriptor(&mut self, config_descriptor: ConfigDescriptor) -> Result<()> {
+        set_once(&self.config_descriptor, config_descriptor, "config_descriptor")
+    }
+
+    fn build(mut self) -> Result<DiceChainEntryPayload> {
+        let subject_public_key = take_value(&mut self.subject_public_key, "subject_public_key")?;
+        // If Mode is omitted, it should be treated as if it was NotConfigured, according to
+        // the Open Profile for DICE spec.
+        let mode = self.mode.take().unwrap_or(DiceMode::kDiceModeNotInitialized);
+        let code_hash = take_value(&mut self.code_hash, "code_hash")?;
+        let authority_hash = take_value(&mut self.authority_hash, "authority_hash")?;
+        let config_descriptor = take_value(&mut self.config_descriptor, "config_descriptor")?;
+        Ok(DiceChainEntryPayload {
+            subject_public_key,
+            mode,
+            code_hash,
+            authority_hash,
+            config_descriptor,
+        })
+    }
+}
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index b2db298..0dfac09 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -19,7 +19,9 @@
 extern crate alloc;
 
 mod api;
+mod cert;
 mod client_vm;
+mod dice;
 mod keyblob;
 mod pub_key;
 mod rkp;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 886ca81..60f3e52 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -733,19 +733,10 @@
                 .isLessThan(atomVmExited.getElapsedTimeMillis());
     }
 
-    @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
-    public void testMicrodroidBoots() throws Exception {
+    private void testMicrodroidBootsWithBuilder(MicrodroidBuilder builder) throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
-        final String configPath = "assets/vm_config.json"; // path inside the APK
-        mMicrodroidDevice =
-                MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
-                        .debugLevel("full")
-                        .memoryMib(minMemorySize())
-                        .cpuTopology("match_host")
-                        .protectedVm(mProtectedVm)
-                        .build(getAndroidDevice());
+        mMicrodroidDevice = builder.build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
 
@@ -809,6 +800,35 @@
     }
 
     @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+    public void testMicrodroidBoots() throws Exception {
+        final String configPath = "assets/vm_config.json"; // path inside the APK
+        testMicrodroidBootsWithBuilder(
+                MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+                        .debugLevel("full")
+                        .memoryMib(minMemorySize())
+                        .cpuTopology("match_host")
+                        .protectedVm(mProtectedVm));
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+    public void testMicrodroidBootsWithGki() throws Exception {
+        List<String> supportedVersions = getSupportedGKIVersions();
+        assumeFalse("no available gki", supportedVersions.isEmpty());
+        for (String ver : supportedVersions) {
+            final String configPath = "assets/vm_config.json"; // path inside the APK
+            testMicrodroidBootsWithBuilder(
+                    MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+                            .debugLevel("full")
+                            .memoryMib(minMemorySize())
+                            .cpuTopology("match_host")
+                            .protectedVm(mProtectedVm)
+                            .gki(ver));
+        }
+    }
+
+    @Test
     public void testMicrodroidRamUsage() throws Exception {
         final String configPath = "assets/vm_config.json";
         mMicrodroidDevice =
@@ -1031,21 +1051,28 @@
                         && device.doesFileExist("/sys/bus/platform/drivers/vfio-platform"));
     }
 
-    private List<String> getAssignableDevices() throws Exception {
+    private List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
         String result = android.run("/apex/com.android.virt/bin/vm", "info");
-        List<String> devices = new ArrayList<>();
+        List<String> ret = new ArrayList<>();
         for (String line : result.split("\n")) {
-            final String header = "Assignable devices: ";
             if (!line.startsWith(header)) continue;
 
             JSONArray jsonArray = new JSONArray(line.substring(header.length()));
             for (int i = 0; i < jsonArray.length(); i++) {
-                devices.add(jsonArray.getString(i));
+                ret.add(jsonArray.getString(i));
             }
             break;
         }
-        return devices;
+        return ret;
+    }
+
+    private List<String> getAssignableDevices() throws Exception {
+        return parseStringArrayFieldsFromVmInfo("Assignable devices: ");
+    }
+
+    private List<String> getSupportedGKIVersions() throws Exception {
+        return parseStringArrayFieldsFromVmInfo("Available gki versions: ");
     }
 
     private TestDevice getAndroidDevice() {
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index acd182c..c63ed4c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -66,6 +66,7 @@
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use nix::unistd::pipe;
+use regex::Regex;
 use rpcbinder::RpcServer;
 use rustutils::system_properties;
 use semver::VersionReq;
@@ -101,8 +102,6 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
-const MICRODROID_GKI_OS_NAME: &str = "microdroid_gki";
-
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
 /// Roughly estimated sufficient size for storing vendor public key into DTBO.
@@ -115,6 +114,8 @@
     pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
         wait_for_interface(BINDER_SERVICE_IDENTIFIER)
             .expect("Could not connect to VirtualizationServiceInternal");
+    static ref MICRODROID_GKI_OS_NAME_PATTERN: Regex =
+        Regex::new(r"^microdroid_gki-\d+\.\d+$").expect("Failed to construct Regex");
 }
 
 fn create_or_update_idsig_file(
@@ -708,12 +709,12 @@
 
 fn is_valid_os(os_name: &str) -> bool {
     if os_name == MICRODROID_OS_NAME {
-        return true;
+        true
+    } else if cfg!(vendor_modules) && MICRODROID_GKI_OS_NAME_PATTERN.is_match(os_name) {
+        PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name)).exists()
+    } else {
+        false
     }
-    if cfg!(vendor_modules) && os_name == MICRODROID_GKI_OS_NAME {
-        return true;
-    }
-    false
 }
 
 fn load_app_config(
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 5cf2a39..3f8d193 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -2,8 +2,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_binary {
-    name: "virtualizationservice",
+rust_defaults {
+    name: "virtualizationservice_defaults",
     crate_name: "virtualizationservice",
     defaults: ["avf_build_flags_rust"],
     edition: "2021",
@@ -45,13 +45,39 @@
         "libserde_xml_rs",
         "libservice_vm_comm",
         "libservice_vm_manager",
+        "libx509_parser",
     ],
     apex_available: ["com.android.virt"],
 }
 
+rust_binary {
+    name: "virtualizationservice",
+    defaults: ["virtualizationservice_defaults"],
+}
+
 xsd_config {
     name: "assignable_devices",
     srcs: ["assignable_devices.xsd"],
     api_dir: "schema",
     package_name: "android.system.virtualizationservice",
 }
+
+rust_test {
+    name: "virtualizationservice_test",
+    defaults: ["virtualizationservice_defaults"],
+    test_suites: ["general-tests"],
+    data: [
+        ":test_rkp_cert_chain",
+    ],
+}
+
+// The chain originates from a CTS test for Keymint, with the Keymint certificate
+// (leaf certificate) truncated.
+//
+// The certificate chain begins with a leaf certificate obtained from RKP and ends
+// with a root certificate. Each certificate in the chain possesses a signature that
+// is signed by the private key of the subsequent certificate in the chain.
+filegroup {
+    name: "test_rkp_cert_chain",
+    srcs: ["testdata/rkp_cert_chain.der"],
+}
diff --git a/virtualizationservice/TEST_MAPPING b/virtualizationservice/TEST_MAPPING
new file mode 100644
index 0000000..4fef83c
--- /dev/null
+++ b/virtualizationservice/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+  "avf-presubmit" : [
+    {
+      "name" : "virtualizationservice_test"
+    }
+  ]
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7cdfdc6..3ac1e60 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -52,6 +52,7 @@
 use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
 use vsock::{VsockListener, VsockStream};
 use nix::unistd::{chown, Uid};
+use x509_parser::{traits::FromDer, certificate::X509Certificate};
 
 /// The unique ID of a VM used (together with a port number) for vsock communication.
 pub type Cid = u32;
@@ -166,35 +167,46 @@
         requester_uid: i32,
     ) -> binder::Result<Vec<Certificate>> {
         check_manage_access()?;
-        info!("Received csr. Requestting attestation...");
-        if cfg!(remote_attestation) {
-            let attestation_key = get_rkpd_attestation_key(
-                REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
-                requester_uid as u32,
-            )
-            .context("Failed to retrieve the remotely provisioned keys")
-            .with_log()
-            .or_service_specific_exception(-1)?;
-            let certificate = request_attestation(csr, &attestation_key.keyBlob)
-                .context("Failed to request attestation")
-                .with_log()
-                .or_service_specific_exception(-1)?;
-            // TODO(b/309780089): Parse the remotely provisioned certificate chain into
-            // individual certificates.
-            let mut certificate_chain =
-                vec![Certificate { encodedCertificate: attestation_key.encodedCertChain }];
-            certificate_chain.push(Certificate { encodedCertificate: certificate });
-            Ok(certificate_chain)
-        } else {
-            Err(Status::new_exception_str(
+        if !cfg!(remote_attestation) {
+            return Err(Status::new_exception_str(
                 ExceptionCode::UNSUPPORTED_OPERATION,
                 Some(
                     "requestAttestation is not supported with the remote_attestation feature \
                      disabled",
                 ),
             ))
-            .with_log()
+            .with_log();
         }
+        info!("Received csr. Requestting attestation...");
+        let attestation_key = get_rkpd_attestation_key(
+            REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+            requester_uid as u32,
+        )
+        .context("Failed to retrieve the remotely provisioned keys")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+        let mut certificate_chain = split_x509_certificate_chain(&attestation_key.encodedCertChain)
+            .context("Failed to split the remotely provisioned certificate chain")
+            .with_log()
+            .or_service_specific_exception(-1)?;
+        if certificate_chain.is_empty() {
+            return Err(Status::new_service_specific_error_str(
+                -1,
+                Some("The certificate chain should contain at least 1 certificate"),
+            ))
+            .with_log();
+        }
+        let certificate = request_attestation(
+            csr.to_vec(),
+            attestation_key.keyBlob,
+            certificate_chain[0].encodedCertificate.clone(),
+        )
+        .context("Failed to request attestation")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+        certificate_chain.insert(0, Certificate { encodedCertificate: certificate });
+
+        Ok(certificate_chain)
     }
 
     fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
@@ -288,6 +300,17 @@
     Ok(devices)
 }
 
+fn split_x509_certificate_chain(mut cert_chain: &[u8]) -> Result<Vec<Certificate>> {
+    let mut out = Vec::new();
+    while !cert_chain.is_empty() {
+        let (remaining, _) = X509Certificate::from_der(cert_chain)?;
+        let end = cert_chain.len() - remaining.len();
+        out.push(Certificate { encodedCertificate: cert_chain[..end].to_vec() });
+        cert_chain = remaining;
+    }
+    Ok(out)
+}
+
 #[derive(Debug, Default)]
 struct GlobalVmInstance {
     /// The unique CID assigned to the VM for vsock communication.
@@ -561,3 +584,24 @@
 fn check_use_custom_virtual_machine() -> binder::Result<()> {
     check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs;
+
+    const TEST_RKP_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
+
+    #[test]
+    fn splitting_x509_certificate_chain_succeeds() -> Result<()> {
+        let bytes = fs::read(TEST_RKP_CERT_CHAIN_PATH)?;
+        let cert_chain = split_x509_certificate_chain(&bytes)?;
+
+        assert_eq!(4, cert_chain.len());
+        for cert in cert_chain {
+            let (remaining, _) = X509Certificate::from_der(&cert.encodedCertificate)?;
+            assert!(remaining.is_empty());
+        }
+        Ok(())
+    }
+}
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 5087120..79e09b0 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -24,15 +24,14 @@
 use service_vm_manager::ServiceVm;
 
 pub(crate) fn request_attestation(
-    csr: &[u8],
-    remotely_provisioned_keyblob: &[u8],
+    csr: Vec<u8>,
+    remotely_provisioned_key_blob: Vec<u8>,
+    remotely_provisioned_cert: Vec<u8>,
 ) -> Result<Vec<u8>> {
     let mut vm = ServiceVm::start()?;
 
-    let params = ClientVmAttestationParams {
-        csr: csr.to_vec(),
-        remotely_provisioned_key_blob: remotely_provisioned_keyblob.to_vec(),
-    };
+    let params =
+        ClientVmAttestationParams { csr, remotely_provisioned_key_blob, remotely_provisioned_cert };
     let request = Request::RequestClientVmAttestation(params);
     match vm.process_request(request).context("Failed to process request")? {
         Response::RequestClientVmAttestation(cert) => Ok(cert),
diff --git a/virtualizationservice/testdata/rkp_cert_chain.der b/virtualizationservice/testdata/rkp_cert_chain.der
new file mode 100644
index 0000000..f32065d
--- /dev/null
+++ b/virtualizationservice/testdata/rkp_cert_chain.der
Binary files differ
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 87278bc..9a92f13 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,6 +27,7 @@
 use clap::{Args, Parser};
 use create_idsig::command_create_idsig;
 use create_partition::command_create_partition;
+use glob::glob;
 use run::{command_run, command_run_app, command_run_microdroid};
 use std::num::NonZeroU16;
 use std::path::{Path, PathBuf};
@@ -107,10 +108,10 @@
     #[arg(long)]
     devices: Vec<PathBuf>,
 
-    /// If set, use GKI instead of microdroid kernel
+    /// Version of GKI to use. If set, use instead of microdroid kernel
     #[cfg(vendor_modules)]
     #[arg(long)]
-    gki: bool,
+    gki: Option<String>,
 }
 
 impl MicrodroidConfig {
@@ -125,13 +126,13 @@
     }
 
     #[cfg(vendor_modules)]
-    fn gki(&self) -> bool {
-        self.gki
+    fn gki(&self) -> Option<&str> {
+        self.gki.as_deref()
     }
 
     #[cfg(not(vendor_modules))]
-    fn gki(&self) -> bool {
-        false
+    fn gki(&self) -> Option<&str> {
+        None
     }
 
     #[cfg(device_assignment)]
@@ -315,6 +316,12 @@
     Ok(())
 }
 
+fn extract_gki_version(gki_config: &Path) -> Option<&str> {
+    let name = gki_config.file_name()?;
+    let name_str = name.to_str()?;
+    name_str.strip_prefix("microdroid_gki-")?.strip_suffix(".json")
+}
+
 /// Print information about supported VM types.
 fn command_info() -> Result<(), Error> {
     let non_protected_vm_supported = hypervisor_props::is_vm_supported()?;
@@ -354,6 +361,12 @@
     let devices = devices.into_iter().map(|x| x.node).collect::<Vec<_>>();
     println!("Assignable devices: {}", serde_json::to_string(&devices)?);
 
+    let gki_configs =
+        glob("/apex/com.android.virt/etc/microdroid_gki-*.json")?.collect::<Result<Vec<_>, _>>()?;
+    let gki_versions =
+        gki_configs.iter().filter_map(|x| extract_gki_version(x)).collect::<Vec<_>>();
+    println!("Available gki versions: {}", serde_json::to_string(&gki_versions)?);
+
     Ok(())
 }
 
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 44ba9af..8721e71 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -111,8 +111,11 @@
         }
         Payload::ConfigPath(config_path)
     } else if let Some(payload_binary_name) = config.payload_binary_name {
-        let os_name =
-            if config.microdroid.gki() { "microdroid_gki" } else { "microdroid" }.to_owned();
+        let os_name = if let Some(ver) = config.microdroid.gki() {
+            format!("microdroid_gki-{ver}")
+        } else {
+            "microdroid".to_owned()
+        };
         Payload::PayloadConfig(VirtualMachinePayloadConfig {
             payloadBinaryName: payload_binary_name,
             osName: os_name,
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 78cd80d..3483e1d 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -224,8 +224,8 @@
  * Gets the number of certificates in the certificate chain.
  *
  * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
- * the attestation key's certificate chain. It starts with a root certificate and ends with a
- * leaf certificate covering the attested public key.
+ * the attestation key's certificate chain. It starts with a leaf certificate covering the attested
+ * public key and ends with a root certificate.
  *
  * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
  *               when the attestation succeeds.
@@ -240,8 +240,8 @@
  * attestation result.
  *
  * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
- * the attestation key's certificate chain. It starts with a root certificate and ends with a
- * leaf certificate covering the attested public key.
+ * the attestation key's certificate chain. It starts with a leaf certificate covering the attested
+ * public key and ends with a root certificate.
  *
  * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
  *               when the attestation succeeds.