Merge "Let protected VM be able to run with microdroid vendor partition." into main
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/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/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index a187259..511b133 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -23,14 +23,14 @@
 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 ciborium::Value;
 use core::ptr::{self, NonNull};
-use core::result;
 use coset::{
     iana::{self, EnumI64},
     CborSerializable, CoseKey, CoseKeyBuilder, Label,
@@ -40,9 +40,9 @@
 
 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(pub(crate) NonNull<EC_KEY>);
@@ -67,35 +67,45 @@
             .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> {
         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
-            );
-            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()))?;
 
-        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 {
@@ -193,14 +203,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()?;
@@ -209,10 +218,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
@@ -231,7 +247,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.
@@ -239,7 +255,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 }))
         }
     }
 
@@ -302,16 +320,59 @@
     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)
+/// 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)
+        }
+    }
+}
+
+impl<'a> AsRef<EC_GROUP> for EcGroup<'a> {
+    fn as_ref(&self) -> &EC_GROUP {
+        self.0
     }
 }
 
@@ -355,6 +416,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()
     }
@@ -367,19 +438,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
index 30bfc21..5362925 100644
--- a/libs/bssl/src/evp.rs
+++ b/libs/bssl/src/evp.rs
@@ -26,14 +26,14 @@
 use core::ptr::NonNull;
 
 /// Wrapper of an `EVP_PKEY` object, representing a public or private key.
-pub struct EvpPKey {
+pub struct PKey {
     pkey: NonNull<EVP_PKEY>,
     /// Since this struct owns the inner key, the inner key remains valid as
     /// long as the pointer to `EVP_PKEY` is valid.
     _inner_key: EcKey,
 }
 
-impl Drop for EvpPKey {
+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.
@@ -48,7 +48,7 @@
     NonNull::new(key).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new))
 }
 
-impl TryFrom<EcKey> for EvpPKey {
+impl TryFrom<EcKey> for PKey {
     type Error = bssl_avf_error::Error;
 
     fn try_from(key: EcKey) -> Result<Self> {
@@ -64,7 +64,7 @@
     }
 }
 
-impl EvpPKey {
+impl PKey {
     /// Returns a DER-encoded SubjectPublicKeyInfo structure as specified
     /// in RFC 5280 s4.1.2.7:
     ///
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index e378386..25b0163 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -38,7 +38,7 @@
 pub use cbs::Cbs;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
-pub use evp::EvpPKey;
+pub use evp::PKey;
 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 968af63..4f0697c 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, EvpPKey, Result};
+use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, PKey, Result};
 use coset::CborSerializable;
 use spki::{
     der::{AnyRef, Decode},
@@ -43,7 +43,7 @@
 fn subject_public_key_info_serialization() -> Result<()> {
     let mut ec_key = EcKey::new_p256()?;
     ec_key.generate_key()?;
-    let pkey: EvpPKey = ec_key.try_into()?;
+    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();
@@ -57,8 +57,18 @@
 }
 
 #[test]
-fn cose_public_key_serialization() -> Result<()> {
+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();
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/pvmfw/Android.bp b/pvmfw/Android.bp
index f49bbce..c6befb4 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -34,7 +34,6 @@
         "libvmbase",
         "libzerocopy_nostd",
         "libzeroize_nostd",
-        "libspin_nostd",
     ],
 }
 
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..3fe9479 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 {
@@ -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..ac015e0 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.
@@ -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/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index 3f84a8d..60bf21c 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 {
@@ -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 })
@@ -630,7 +656,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 +691,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, 0x9, 0xFF1],
             },
             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, 0x60, 0xFFC],
             },
         ];
 
@@ -708,13 +734,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, 0x9, 0xFF1],
             },
             AssignedDeviceNode {
                 path: CString::new("/light").unwrap(),
                 reg: into_fdt_prop(vec![0x100, 0x9]),
                 interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
-                iommus: vec![0x9, 0x40],
+                iommus: vec![0x9, 0xFF1, 0x40, 0xFFA],
             },
         ];
 
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..199a5ce 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -54,13 +54,13 @@
     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 {
@@ -73,12 +73,12 @@
     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..4906064 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -48,31 +48,31 @@
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0>, <&pviommu_1>;
+        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
     };
 
     light@70000000 {
         compatible = "android,light";
         reg = <0x100 0x9>;
         interrupts = <0x0 0xF 0x5>;
-        iommus = <&pviommu_1>, <&pviommu_a>;
+        iommus = <&pviommu_1 0xFF1>, <&pviommu_a 0xFFA>;
     };
 
     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>;
     };
 
     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..959cd23 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -48,43 +48,43 @@
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0>, <&pviommu_1>;
+        iommus = <&pviommu_0 0xFF0>, <&pviommu_1 0xFF1>;
     };
 
     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_a>, <&pviommu_b>, <&pviommu_c>;
+        iommus = <&pviommu_a 0xFFA>, <&pviommu_b 0xFFB>, <&pviommu_c 0xFFC>;
     };
 
     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>;
+        #iommu-cells = <1>;
     };
 
     pviommu_c: pviommuc {
         compatible = "pkvm,pviommu";
         id = <0x60>;
-        #iommu-cells = <0>;
+        #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
index 6a5068c..8c04b39 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
@@ -48,12 +48,12 @@
         interrupts = <0x0 0xF 0x4>;
         google,eh,ignore-gctrl-reset;
         status = "okay";
-        iommus = <&pviommu_0>;
+        iommus = <&pviommu_0 0xFF0>;
     };
 
     pviommu_0: pviommu0 {
         compatible = "pkvm,pviommu";
         id = <0x4>;
-        #iommu-cells = <0>;
+        #iommu-cells = <1>;
     };
 };
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 1ab02e9..90008a9 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -113,6 +113,8 @@
         "libciborium",
         "libclient_vm_csr",
         "libcoset",
+        "libcstr",
+        "libdiced_open_dice",
         "libdiced_sample_inputs",
         "liblibc",
         "liblog_rust",
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index d9cffe0..0bdc927 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -37,7 +37,7 @@
 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_requests::process_request;
 use virtio_drivers::{
     device::socket::{VsockAddr, VMADDR_CID_HOST},
@@ -178,7 +178,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 85c3efe..c1a8394 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,14 +22,19 @@
     binder::{ParcelFileDescriptor, ProcessState},
 };
 use anyhow::{bail, Context, Result};
-use bssl_avf::{sha256, EcKey, EvpPKey};
+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 cstr::cstr;
+use diced_open_dice::{
+    retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
+    DiceConfigValues, DiceMode, InputValues, OwnedDiceArtifacts, HASH_SIZE, HIDDEN_SIZE,
+};
 use log::info;
 use service_vm_comm::{
     ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
-    Request, Response, VmType,
+    Request, RequestProcessingError, Response, VmType,
 };
 use service_vm_manager::ServiceVm;
 use std::fs;
@@ -48,6 +53,19 @@
 const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
 const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
 const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
+/// The following data are generated randomly with urandom.
+const CODE_HASH_MICRODROID: [u8; HASH_SIZE] = [
+    0x08, 0x78, 0xc2, 0x5b, 0xe7, 0xea, 0x3d, 0x62, 0x70, 0x22, 0xd9, 0x1c, 0x4f, 0x3c, 0x2e, 0x2f,
+    0x0f, 0x97, 0xa4, 0x6f, 0x6d, 0xd5, 0xe6, 0x4a, 0x6d, 0xbe, 0x34, 0x2e, 0x56, 0x04, 0xaf, 0xef,
+    0x74, 0x3f, 0xec, 0xb8, 0x44, 0x11, 0xf4, 0x2f, 0x05, 0xb2, 0x06, 0xa3, 0x0e, 0x75, 0xb7, 0x40,
+    0x9a, 0x4c, 0x58, 0xab, 0x96, 0xe7, 0x07, 0x97, 0x07, 0x86, 0x5c, 0xa1, 0x42, 0x12, 0xf0, 0x34,
+];
+const AUTHORITY_HASH_MICRODROID: [u8; HASH_SIZE] = [
+    0xc7, 0x97, 0x5b, 0xa9, 0x9e, 0xbf, 0x0b, 0xeb, 0xe7, 0x7f, 0x69, 0x8f, 0x8e, 0xcf, 0x04, 0x7d,
+    0x2c, 0x0f, 0x4d, 0xbe, 0xcb, 0xf5, 0xf1, 0x4c, 0x1d, 0x1c, 0xb7, 0x44, 0xdf, 0xf8, 0x40, 0x90,
+    0x09, 0x65, 0xab, 0x01, 0x34, 0x3e, 0xc2, 0xc4, 0xf7, 0xa2, 0x3a, 0x5c, 0x4e, 0x76, 0x4f, 0x42,
+    0xa8, 0x6c, 0xc9, 0xf1, 0x7b, 0x12, 0x80, 0xa4, 0xef, 0xa2, 0x4d, 0x72, 0xa1, 0x21, 0xe2, 0x47,
+];
 
 #[test]
 fn process_requests_in_protected_vm() -> Result<()> {
@@ -65,7 +83,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)?;
+    check_attestation_request(&mut vm, &key_pair, vm_type)?;
     Ok(())
 }
 
@@ -123,6 +141,7 @@
 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] = [
@@ -130,6 +149,7 @@
         0x5c,
     ];
     let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+    let dice_artifacts = extend_dice_artifacts_with_microdroid_payload(&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)?;
@@ -151,6 +171,9 @@
 
     match response {
         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,
@@ -159,10 +182,43 @@
             )?;
             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 extend_dice_artifacts_with_microdroid_payload(
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<OwnedDiceArtifacts> {
+    let config_values = DiceConfigValues {
+        component_name: Some(cstr!("Microdroid payload")),
+        component_version: Some(1),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        CODE_HASH_MICRODROID,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_MICRODROID,
+        DiceMode::kDiceModeDebug,
+        [0u8; HIDDEN_SIZE], // hidden
+    );
+    retry_bcc_main_flow(
+        dice_artifacts.cdi_attest(),
+        dice_artifacts.cdi_seal(),
+        dice_artifacts.bcc().unwrap(),
+        &input_values,
+    )
+    .context("Failed to run BCC main flow for Microdroid")
+}
+
 fn check_certificate_for_client_vm(
     certificate: &[u8],
     maced_public_key: &[u8],
@@ -190,7 +246,7 @@
         cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
     let subject_public_key = EcKey::from_cose_public_key(&csr_payload.public_key).unwrap();
     let expected_spki_data =
-        EvpPKey::try_from(subject_public_key).unwrap().subject_public_key_info().unwrap();
+        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());
@@ -205,8 +261,13 @@
     let (remaining, extension) = parse_der(extension.value)?;
     assert!(remaining.is_empty());
     let attestation_ext = extension.as_sequence()?;
-    assert_eq!(1, attestation_ext.len());
+    assert_eq!(2, 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"
+    );
 
     // Checks other fields on the certificate
     assert_eq!(X509Version::V3, cert.version());
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/src/lib.rs b/secretkeeper/comm/src/lib.rs
deleted file mode 100644
index 9a10ac0..0000000
--- a/secretkeeper/comm/src/lib.rs
+++ /dev/null
@@ -1,24 +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.
- */
-
-//! This library exposes data structures and methods that can be used by Secretkeeper HAL & client
-//! implementation. This is compatible with Secretkeeper HAL specification.
-
-#![no_std]
-extern crate alloc;
-
-mod cbor_convert;
-pub mod data_types;
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/src/csr.rs b/service_vm/comm/src/csr.rs
index 757d080..2a27f90 100644
--- a/service_vm/comm/src/csr.rs
+++ b/service_vm/comm/src/csr.rs
@@ -100,7 +100,8 @@
     }
 }
 
-fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
+/// Reads the provided value `v` as bytes array.
+pub fn try_as_bytes(v: Value, context: &str) -> coset::Result<Vec<u8>> {
     if let Value::Bytes(data) = v {
         Ok(data)
     } else {
@@ -110,7 +111,8 @@
     }
 }
 
-fn cbor_value_type(v: &Value) -> &'static str {
+/// 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",
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index bb85a26..c9de540 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -23,7 +23,7 @@
 mod message;
 mod vsock;
 
-pub use csr::{Csr, CsrPayload};
+pub use csr::{cbor_value_type, try_as_bytes, Csr, CsrPayload};
 pub use message::{
     ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
     RequestProcessingError, Response, ServiceVmRequest,
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
index 87c8378..80a9608 100644
--- a/service_vm/comm/src/message.rs
+++ b/service_vm/comm/src/message.rs
@@ -130,6 +130,9 @@
 
     /// An error happened during the DER encoding/decoding.
     DerError,
+
+    /// The DICE chain from the client VM is invalid.
+    InvalidDiceChain,
 }
 
 impl fmt::Display for RequestProcessingError {
@@ -155,6 +158,9 @@
             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")
+            }
         }
     }
 }
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/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 68ca382..2baca2a 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -49,6 +49,8 @@
 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,
 }
 
 impl<'a> AssociatedOid for AttestationExtension<'a> {
@@ -56,8 +58,8 @@
 }
 
 impl<'a> AttestationExtension<'a> {
-    pub(crate) fn new(challenge: &'a [u8]) -> Self {
-        Self { attestation_challenge: challenge }
+    pub(crate) fn new(attestation_challenge: &'a [u8], is_vm_secure: bool) -> Self {
+        Self { attestation_challenge, is_vm_secure }
     }
 }
 
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index e1f345c..ddf230b 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -16,9 +16,10 @@
 //! 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::{rand_bytes, sha256, EcKey, EvpPKey};
+use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
 use core::result;
 use coset::{CborSerializable, CoseSign};
 use der::{Decode, Encode};
@@ -43,20 +44,29 @@
     })?;
     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`.
 
+    // Verifies the second signature with the public key in the CSR payload.
     let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
     cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
         ecdsa_verify(&ec_public_key, signature, message)
     })?;
-    let subject_public_key_info = EvpPKey::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.
+    let subject_public_key_info = PKey::try_from(ec_public_key)?.subject_public_key_info()?;
 
     // Builds the TBSCertificate.
     // The serial number can be up to 20 bytes according to RFC5280 s4.1.2.2.
@@ -66,7 +76,11 @@
     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 attestation_ext = cert::AttestationExtension::new(&csr_payload.challenge).to_vec()?;
+    let attestation_ext = cert::AttestationExtension::new(
+        &csr_payload.challenge,
+        client_vm_dice_chain.all_entries_are_secure(),
+    )
+    .to_vec()?;
     let tbs_cert = cert::build_tbs_certificate(
         &serial_number,
         rkp_cert.tbs_certificate.subject,
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
new file mode 100644
index 0000000..c220af6
--- /dev/null
+++ b/service_vm/requests/src/dice.rs
@@ -0,0 +1,321 @@
+// 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::vec::Vec;
+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::{cbor_value_type, try_as_bytes, 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;
+
+/// 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"
+        );
+        Ok(Self { payloads })
+    }
+
+    /// 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 =
+        try_as_value_array(Value::from_slice(client_vm_dice_chain)?, "client_vm_dice_chain")?;
+    let service_vm_dice_chain =
+        try_as_value_array(Value::from_slice(service_vm_dice_chain)?, "service_vm_dice_chain")?;
+    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],
+    /// TODO(b/313815907): Parse the config descriptor and read Apk/Apexes info in it.
+    #[allow(dead_code)]
+    config_descriptor: Vec<u8>,
+}
+
+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 payload = Value::from_slice(&payload)?;
+        let Value::Map(entries) = payload else {
+            return Err(CoseError::UnexpectedItem(cbor_value_type(&payload), "map").into());
+        };
+        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 Some(Ok(key)) = key.as_integer().map(i64::try_from) else {
+            error!("Invalid key found in the DICE chain entry: {:?}", key);
+            return Err(RequestProcessingError::InvalidDiceChain);
+        };
+        match key {
+            SUBJECT_PUBLIC_KEY => {
+                let subject_public_key = try_as_bytes(value, "subject_public_key")?;
+                let subject_public_key = CoseKey::from_slice(&subject_public_key)?.try_into()?;
+                builder.subject_public_key(subject_public_key)?;
+            }
+            MODE => builder.mode(to_mode(value)?)?,
+            CODE_HASH => builder.code_hash(try_as_byte_array(value, "code_hash")?)?,
+            AUTHORITY_HASH => {
+                builder.authority_hash(try_as_byte_array(value, "authority_hash")?)?
+            }
+            CONFIG_DESC => builder.config_descriptor(try_as_bytes(value, "config_descriptor")?)?,
+            _ => {}
+        }
+    }
+    builder.build()
+}
+
+fn try_as_value_array(v: Value, context: &str) -> coset::Result<Vec<Value>> {
+    if let Value::Array(data) = v {
+        Ok(data)
+    } else {
+        let v_type = cbor_value_type(&v);
+        error!("The provided value type '{v_type}' is not of type 'bytes': {context}");
+        Err(CoseError::UnexpectedItem(v_type, "array"))
+    }
+}
+
+fn try_as_byte_array<const N: usize>(v: Value, context: &str) -> Result<[u8; N]> {
+    let data = try_as_bytes(v, context)?;
+    data.try_into().map_err(|e| {
+        error!("The provided value '{context}' is not an array of length {N}: {e:?}");
+        RequestProcessingError::InternalError
+    })
+}
+
+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<Vec<u8>>,
+}
+
+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: Vec<u8>) -> 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 3f687a4..0dfac09 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -21,6 +21,7 @@
 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() {