Merge "pvmfw: Fix #iommu-cells in platform DT" into main
diff --git a/authfs/Android.bp b/authfs/Android.bp
index a4151c2..8ac600d 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -19,6 +19,7 @@
         "libclap",
         "libfsverity_digests_proto_rust",
         "libfuse_rust",
+        "libhex",
         "liblibc",
         "liblog_rust",
         "libnix",
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 9ff0ae3..e14b771 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -169,21 +169,6 @@
     })
 }
 
-fn from_hex_string(s: &str) -> Result<Vec<u8>> {
-    if s.len() % 2 == 1 {
-        bail!("Incomplete hex string: {}", s);
-    } else {
-        let results = (0..s.len())
-            .step_by(2)
-            .map(|i| {
-                u8::from_str_radix(&s[i..i + 2], 16)
-                    .map_err(|e| anyhow!("Cannot parse hex {}: {}", &s[i..i + 2], e))
-            })
-            .collect::<Result<Vec<_>>>();
-        Ok(results?)
-    }
-}
-
 fn new_remote_verified_file_entry(
     service: file::VirtFdService,
     remote_fd: i32,
@@ -193,7 +178,7 @@
         reader: LazyVerifiedReadonlyFile::prepare_by_fd(
             service,
             remote_fd,
-            from_hex_string(expected_digest)?,
+            hex::decode(expected_digest)?,
         ),
     })
 }
@@ -332,18 +317,3 @@
         std::process::exit(1);
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn parse_hex_string() {
-        assert_eq!(from_hex_string("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
-        assert_eq!(from_hex_string("DEADBEEF").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
-        assert_eq!(from_hex_string("").unwrap(), Vec::<u8>::new());
-
-        assert!(from_hex_string("deadbee").is_err());
-        assert!(from_hex_string("X").is_err());
-    }
-}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index c0dca2e..df79104 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -81,9 +81,14 @@
     EVP_AEAD_CTX_new,
     EVP_AEAD_CTX_open,
     EVP_AEAD_CTX_seal,
+    EVP_Digest,
+    EVP_MD_CTX_new,
     EVP_PKEY_new,
+    EVP_PKEY_new_raw_public_key,
     EVP_PKEY_set1_EC_KEY,
     EVP_marshal_public_key,
+    EVP_DigestVerify,
+    EVP_DigestVerifyInit,
     HKDF,
     HMAC,
     RAND_bytes,
diff --git a/libs/bssl/src/digest.rs b/libs/bssl/src/digest.rs
index 49e66e6..e986a38 100644
--- a/libs/bssl/src/digest.rs
+++ b/libs/bssl/src/digest.rs
@@ -14,7 +14,18 @@
 
 //! Wrappers of the digest functions in BoringSSL digest.h.
 
-use bssl_ffi::{EVP_MD_size, EVP_sha256, EVP_sha512, EVP_MD};
+use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+    EVP_Digest, EVP_MD_CTX_free, EVP_MD_CTX_new, EVP_MD_size, EVP_sha256, EVP_sha384, EVP_sha512,
+    EVP_MAX_MD_SIZE, EVP_MD, EVP_MD_CTX,
+};
+use core::ptr::{self, NonNull};
+use log::error;
+
+const MAX_DIGEST_SIZE: usize = EVP_MAX_MD_SIZE as usize;
 
 /// Message digester wrapping `EVP_MD`.
 #[derive(Clone, Debug)]
@@ -28,7 +39,17 @@
         let p = unsafe { EVP_sha256() };
         // SAFETY: The returned pointer should always be valid and points to a static
         // `EVP_MD`.
-        Self(unsafe { &*p })
+        Self(unsafe { p.as_ref().unwrap() })
+    }
+
+    /// Returns a `Digester` implementing `SHA-384` algorithm.
+    pub fn sha384() -> Self {
+        // SAFETY: This function does not access any Rust variables and simply returns
+        // a pointer to the static variable in BoringSSL.
+        let p = unsafe { EVP_sha384() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_MD`.
+        Self(unsafe { p.as_ref().unwrap() })
     }
 
     /// Returns a `Digester` implementing `SHA-512` algorithm.
@@ -38,7 +59,7 @@
         let p = unsafe { EVP_sha512() };
         // SAFETY: The returned pointer should always be valid and points to a static
         // `EVP_MD`.
-        Self(unsafe { &*p })
+        Self(unsafe { p.as_ref().unwrap() })
     }
 
     /// Returns the digest size in bytes.
@@ -46,4 +67,64 @@
         // SAFETY: The inner pointer is fetched from EVP_* hash functions in BoringSSL digest.h
         unsafe { EVP_MD_size(self.0) }
     }
+
+    /// Computes the digest of the provided `data`.
+    pub fn digest(&self, data: &[u8]) -> Result<Vec<u8>> {
+        let mut out = vec![0u8; MAX_DIGEST_SIZE];
+        let mut out_size = 0;
+        let engine = ptr::null_mut(); // Use the default engine.
+        let ret =
+            // SAFETY: This function reads `data` and writes to `out` within its bounds.
+            // `out` has `MAX_DIGEST_SIZE` bytes of space for write as required in the
+            // BoringSSL spec.
+            // The digester is a valid pointer to a static `EVP_MD` as it is returned by
+            // BoringSSL API during the construction of this struct.
+            unsafe {
+                EVP_Digest(
+                    data.as_ptr() as *const _,
+                    data.len(),
+                    out.as_mut_ptr(),
+                    &mut out_size,
+                    self.0,
+                    engine,
+                )
+            };
+        check_int_result(ret, ApiName::EVP_Digest)?;
+        let out_size = usize::try_from(out_size).map_err(|e| {
+            error!("Failed to convert digest size to usize: {:?}", e);
+            Error::InternalError
+        })?;
+        if self.size() != out_size {
+            return Err(to_call_failed_error(ApiName::EVP_Digest));
+        }
+        out.truncate(out_size);
+        Ok(out)
+    }
+}
+
+/// Message digester context wrapping `EVP_MD_CTX`.
+#[derive(Clone, Debug)]
+pub struct DigesterContext(NonNull<EVP_MD_CTX>);
+
+impl Drop for DigesterContext {
+    fn drop(&mut self) {
+        // SAFETY: This function frees any resources owned by `EVP_MD_CTX` and resets it to a
+        // freshly initialised state and then frees the context.
+        // It is safe because `EVP_MD_CTX` has been allocated by BoringSSL and isn't used after
+        // this.
+        unsafe { EVP_MD_CTX_free(self.0.as_ptr()) }
+    }
+}
+
+impl DigesterContext {
+    /// Creates a new `DigesterContext` wrapping a freshly allocated and initialised `EVP_MD_CTX`.
+    pub fn new() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let ctx = unsafe { EVP_MD_CTX_new() };
+        NonNull::new(ctx).map(Self).ok_or(to_call_failed_error(ApiName::EVP_MD_CTX_new))
+    }
+
+    pub(crate) fn as_mut_ptr(&mut self) -> *mut EVP_MD_CTX {
+        self.0.as_ptr()
+    }
 }
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index a187259..0c944cd 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -17,32 +17,34 @@
 
 use crate::cbb::CbbFixed;
 use crate::cbs::Cbs;
-use crate::util::{check_int_result, to_call_failed_error};
+use crate::util::{
+    check_int_result, get_label_value, get_label_value_as_bytes, to_call_failed_error,
+};
 use alloc::vec;
 use alloc::vec::Vec;
 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,
+    CborSerializable, CoseKey, CoseKeyBuilder, KeyType, Label,
 };
 use log::error;
 use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
 
 const ES256_ALGO: iana::Algorithm = iana::Algorithm::ES256;
 const P256_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const P384_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_384;
 const P256_AFFINE_COORDINATE_SIZE: usize = 32;
-
-type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
+const P384_AFFINE_COORDINATE_SIZE: usize = 48;
 
 /// Wrapper of an `EC_KEY` object, representing a public or private EC key.
 pub struct EcKey(pub(crate) NonNull<EC_KEY>);
@@ -67,35 +69,59 @@
             .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
     }
 
+    /// Creates a new EC P-384 key pair.
+    pub fn new_p384() -> Result<Self> {
+        // SAFETY: The returned pointer is checked below.
+        let ec_key = unsafe {
+            EC_KEY_new_by_curve_name(NID_secp384r1) // EC P-384 CURVE Nid
+        };
+        NonNull::new(ec_key)
+            .map(Self)
+            .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
+    }
+
     /// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
-    pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+    pub fn from_cose_public_key_slice(cose_key: &[u8]) -> Result<Self> {
         let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
             error!("Failed to deserialize COSE_Key: {e:?}");
             Error::CoseKeyDecodingFailed
         })?;
-        if cose_key.alg != Some(coset::Algorithm::Assigned(ES256_ALGO)) {
-            error!(
-                "Only ES256 algorithm is supported. Algo type in the COSE Key: {:?}",
-                cose_key.alg
-            );
+        Self::from_cose_public_key(&cose_key)
+    }
+
+    /// Constructs an `EcKey` instance from the provided `COSE_Key`.
+    ///
+    /// The lifetime of the returned `EcKey` is not tied to the lifetime of the `cose_key`,
+    /// because the affine coordinates stored in the `cose_key` are copied into the `EcKey`.
+    ///
+    /// Currently, only the EC P-256 and P-384 curves are supported.
+    pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+        if cose_key.kty != KeyType::Assigned(iana::KeyType::EC2) {
+            error!("Only EC2 keys are supported. Key type in the COSE Key: {:?}", cose_key.kty);
             return Err(Error::Unimplemented);
         }
-        let crv = get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
-        if &Value::from(P256_CURVE.to_i64()) != crv {
-            error!("Only EC P-256 curve is supported. Curve type in the COSE Key: {crv:?}");
-            return Err(Error::Unimplemented);
-        }
+        let ec_key =
+            match get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))? {
+                crv if crv == &Value::from(P256_CURVE.to_i64()) => EcKey::new_p256()?,
+                crv if crv == &Value::from(P384_CURVE.to_i64()) => EcKey::new_p384()?,
+                crv => {
+                    error!(
+                        "Only EC P-256 and P-384 curves are supported. \
+                         Curve type in the COSE Key: {crv:?}"
+                    );
+                    return Err(Error::Unimplemented);
+                }
+            };
+        let x = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+        let y = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
 
-        let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
-        let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
-
-        check_p256_affine_coordinate_size(x)?;
-        check_p256_affine_coordinate_size(y)?;
+        let group = ec_key.ec_group()?;
+        group.check_affine_coordinate_size(x)?;
+        group.check_affine_coordinate_size(y)?;
 
         let x = BigNum::from_slice(x)?;
         let y = BigNum::from_slice(y)?;
 
-        let ec_key = EcKey::new_p256()?;
         // SAFETY: All the parameters are checked non-null and initialized.
         // The function only reads the coordinates x and y within their bounds.
         let ret = unsafe {
@@ -193,14 +219,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 +234,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 +263,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 +271,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 }))
         }
     }
 
@@ -291,27 +325,59 @@
     }
 }
 
-fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
-    Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
-        error!("Value not a bstr.");
-        Error::CoseKeyDecodingFailed
-    })?)
+/// Wrapper of an `EC_GROUP` reference.
+struct EcGroup<'a>(&'a EC_GROUP);
+
+impl<'a> EcGroup<'a> {
+    /// Returns the NID that identifies the EC group of the key.
+    fn curve_nid(&self) -> i32 {
+        // SAFETY: It is safe since the inner pointer is valid and points to an initialized
+        // instance of `EC_GROUP`.
+        unsafe { EC_GROUP_get_curve_name(self.as_ref()) }
+    }
+
+    fn coset_curve(&self) -> Result<iana::EllipticCurve> {
+        #[allow(non_upper_case_globals)]
+        match self.curve_nid() {
+            NID_X9_62_prime256v1 => Ok(P256_CURVE),
+            NID_secp384r1 => Ok(P384_CURVE),
+            name => {
+                error!("Unsupported curve NID: {}", name);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    fn affine_coordinate_size(&self) -> Result<usize> {
+        #[allow(non_upper_case_globals)]
+        match self.curve_nid() {
+            NID_X9_62_prime256v1 => Ok(P256_AFFINE_COORDINATE_SIZE),
+            NID_secp384r1 => Ok(P384_AFFINE_COORDINATE_SIZE),
+            name => {
+                error!("Unsupported curve NID: {}", name);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    fn check_affine_coordinate_size(&self, coordinate: &[u8]) -> Result<()> {
+        let expected_len = self.affine_coordinate_size()?;
+        if expected_len == coordinate.len() {
+            Ok(())
+        } else {
+            error!(
+                "The size of the affine coordinate '{}' does not match the expected size '{}'",
+                coordinate.len(),
+                expected_len
+            );
+            Err(Error::CoseKeyDecodingFailed)
+        }
+    }
 }
 
-fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
-    Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
-}
-
-fn check_p256_affine_coordinate_size(coordinate: &[u8]) -> Result<()> {
-    if P256_AFFINE_COORDINATE_SIZE == coordinate.len() {
-        Ok(())
-    } else {
-        error!(
-            "The size of the affine coordinate '{}' does not match the expected size '{}'",
-            coordinate.len(),
-            P256_AFFINE_COORDINATE_SIZE
-        );
-        Err(Error::CoseKeyDecodingFailed)
+impl<'a> AsRef<EC_GROUP> for EcGroup<'a> {
+    fn as_ref(&self) -> &EC_GROUP {
+        self.0
     }
 }
 
@@ -355,6 +421,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 +443,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..86f99a8 100644
--- a/libs/bssl/src/evp.rs
+++ b/libs/bssl/src/evp.rs
@@ -15,25 +15,35 @@
 //! Wrappers of the EVP functions in BoringSSL evp.h.
 
 use crate::cbb::CbbFixed;
+use crate::digest::{Digester, DigesterContext};
 use crate::ec_key::EcKey;
-use crate::util::{check_int_result, to_call_failed_error};
-use alloc::vec::Vec;
-use bssl_avf_error::{ApiName, Result};
-use bssl_ffi::{
-    CBB_flush, CBB_len, EVP_PKEY_free, EVP_PKEY_new, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key,
-    EVP_PKEY,
+use crate::util::{
+    check_int_result, get_label_value, get_label_value_as_bytes, to_call_failed_error,
 };
-use core::ptr::NonNull;
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+    CBB_flush, CBB_len, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_free, EVP_PKEY_new,
+    EVP_PKEY_new_raw_public_key, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key, EVP_PKEY,
+    EVP_PKEY_ED25519, EVP_PKEY_X25519,
+};
+use ciborium::Value;
+use core::ptr::{self, NonNull};
+use coset::{
+    iana::{self, EnumI64},
+    CoseKey, KeyType, Label,
+};
+use log::error;
 
 /// Wrapper of an `EVP_PKEY` object, representing a public or private key.
-pub struct EvpPKey {
+pub struct PKey {
     pkey: NonNull<EVP_PKEY>,
-    /// Since this struct owns the inner key, the inner key remains valid as
+    /// If this struct owns the inner EC key, the inner EC key should remain valid as
     /// long as the pointer to `EVP_PKEY` is valid.
-    _inner_key: EcKey,
+    _inner_ec_key: Option<EcKey>,
 }
 
-impl Drop for 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,23 +58,23 @@
     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> {
         let pkey = new_pkey()?;
-        // SAFETY: The function only sets the inner key of the initialized and
+        // SAFETY: The function only sets the inner EC key of the initialized and
         // non-null `EVP_PKEY` to point to the given `EC_KEY`. It only reads from
         // and writes to the initialized `EVP_PKEY`.
         // Since this struct owns the inner key, the inner key remains valid as
         // long as `EVP_PKEY` is valid.
         let ret = unsafe { EVP_PKEY_set1_EC_KEY(pkey.as_ptr(), key.0.as_ptr()) };
         check_int_result(ret, ApiName::EVP_PKEY_set1_EC_KEY)?;
-        Ok(Self { pkey, _inner_key: key })
+        Ok(Self { pkey, _inner_ec_key: Some(key) })
     }
 }
 
-impl EvpPKey {
+impl PKey {
     /// Returns a DER-encoded SubjectPublicKeyInfo structure as specified
     /// in RFC 5280 s4.1.2.7:
     ///
@@ -87,4 +97,126 @@
         let len = unsafe { CBB_len(cbb.as_ref()) };
         Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec())
     }
+
+    /// This function takes a raw public key data slice and creates a `PKey` instance wrapping
+    /// a freshly allocated `EVP_PKEY` object from it.
+    ///
+    /// The lifetime of the returned instance is not tied to the lifetime of the raw public
+    /// key slice because the raw data is copied into the `EVP_PKEY` object.
+    ///
+    /// Currently the only supported raw formats are X25519 and Ed25519, where the formats
+    /// are specified in RFC 7748 and RFC 8032 respectively.
+    pub fn new_raw_public_key(raw_public_key: &[u8], type_: PKeyType) -> Result<Self> {
+        let engine = ptr::null_mut(); // Engine is not used.
+        let pkey =
+            // SAFETY: The function only reads from the given raw public key within its bounds.
+            // The returned pointer is checked below.
+            unsafe {
+                EVP_PKEY_new_raw_public_key(
+                    type_.0,
+                    engine,
+                    raw_public_key.as_ptr(),
+                    raw_public_key.len(),
+                )
+            };
+        let pkey =
+            NonNull::new(pkey).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new_raw_public_key))?;
+        Ok(Self { pkey, _inner_ec_key: None })
+    }
+
+    /// Creates a `PKey` from the given `cose_key`.
+    ///
+    /// The lifetime of the returned instance is not tied to the lifetime of the `cose_key` as the
+    /// data of `cose_key` is copied into the `EVP_PKEY` or `EC_KEY` object.
+    pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+        match &cose_key.kty {
+            KeyType::Assigned(iana::KeyType::EC2) => {
+                EcKey::from_cose_public_key(cose_key)?.try_into()
+            }
+            KeyType::Assigned(iana::KeyType::OKP) => {
+                let curve_type =
+                    get_label_value(cose_key, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+                let curve_type = match curve_type {
+                    crv if crv == &Value::from(iana::EllipticCurve::Ed25519.to_i64()) => {
+                        PKeyType::ED25519
+                    }
+                    crv if crv == &Value::from(iana::EllipticCurve::X25519.to_i64()) => {
+                        PKeyType::X25519
+                    }
+                    crv => {
+                        error!("Unsupported curve type in OKP COSE key: {:?}", crv);
+                        return Err(Error::Unimplemented);
+                    }
+                };
+                let x = get_label_value_as_bytes(
+                    cose_key,
+                    Label::Int(iana::OkpKeyParameter::X.to_i64()),
+                )?;
+                Self::new_raw_public_key(x, curve_type)
+            }
+            kty => {
+                error!("Unsupported key type in COSE key: {:?}", kty);
+                Err(Error::Unimplemented)
+            }
+        }
+    }
+
+    /// Verifies the given `signature` of the `message` using the current public key.
+    ///
+    /// The `message` will be hashed using the given `digester` before verification.
+    ///
+    /// For algorithms like Ed25519 that do not use pre-hashed inputs, the `digester` should
+    /// be `None`.
+    pub fn verify(
+        &self,
+        signature: &[u8],
+        message: &[u8],
+        digester: Option<Digester>,
+    ) -> Result<()> {
+        let mut digester_context = DigesterContext::new()?;
+        // The `EVP_PKEY_CTX` is set to null as this function does not collect the context
+        // during the verification.
+        let pkey_context = ptr::null_mut();
+        let engine = ptr::null_mut(); // Use the default engine.
+        let ret =
+            // SAFETY: All the non-null parameters passed to this function have been properly
+            // initialized as required in the BoringSSL spec.
+            unsafe {
+                EVP_DigestVerifyInit(
+                    digester_context.as_mut_ptr(),
+                    pkey_context,
+                    digester.map_or(ptr::null(), |d| d.0),
+                    engine,
+                    self.pkey.as_ptr(),
+                )
+            };
+        check_int_result(ret, ApiName::EVP_DigestVerifyInit)?;
+
+        // SAFETY: The function only reads from the given slices within their bounds.
+        // The `EVP_MD_CTX` is successfully initialized before this call.
+        let ret = unsafe {
+            EVP_DigestVerify(
+                digester_context.as_mut_ptr(),
+                signature.as_ptr(),
+                signature.len(),
+                message.as_ptr(),
+                message.len(),
+            )
+        };
+        check_int_result(ret, ApiName::EVP_DigestVerify)
+    }
+}
+
+/// Type of the keys supported by `PKey`.
+///
+/// It is a wrapper of the `EVP_PKEY_*` macros defined BoringSSL evp.h, which are the
+/// NID values of the corresponding keys.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct PKeyType(i32);
+
+impl PKeyType {
+    /// EVP_PKEY_X25519 / NID_X25519
+    pub const X25519: PKeyType = PKeyType(EVP_PKEY_X25519);
+    /// EVP_PKEY_ED25519 / NID_ED25519
+    pub const ED25519: PKeyType = PKeyType(EVP_PKEY_ED25519);
 }
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index e378386..a420168 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -38,7 +38,7 @@
 pub use cbs::Cbs;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
-pub use evp::EvpPKey;
+pub use evp::{PKey, PKeyType};
 pub use hkdf::hkdf;
 pub use hmac::hmac_sha256;
 pub use rand::rand_bytes;
diff --git a/libs/bssl/src/util.rs b/libs/bssl/src/util.rs
index 880c85b..7473fe1 100644
--- a/libs/bssl/src/util.rs
+++ b/libs/bssl/src/util.rs
@@ -16,6 +16,8 @@
 
 use crate::err::get_error_reason_code;
 use bssl_avf_error::{ApiName, Error, Result};
+use ciborium::Value;
+use coset::{CoseKey, Label};
 use log::error;
 
 pub(crate) fn check_int_result(ret: i32, api_name: ApiName) -> Result<()> {
@@ -35,3 +37,14 @@
 pub(crate) fn to_call_failed_error(api_name: ApiName) -> Error {
     Error::CallFailed(api_name, get_error_reason_code())
 }
+
+pub(crate) fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+    Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
+        error!("Value not a bstr.");
+        Error::CoseKeyDecodingFailed
+    })?)
+}
+
+pub(crate) fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+    Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
+}
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 968af63..9c7eb4f 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, EvpPKey, Result};
+use bssl_avf::{sha256, ApiName, Digester, 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,12 +57,22 @@
 }
 
 #[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();
-    let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+    let deserialized_ec_key = EcKey::from_cose_public_key_slice(&cose_key_data)?;
 
     assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
     Ok(())
@@ -72,10 +82,29 @@
 fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
     let mut ec_key = EcKey::new_p256()?;
     ec_key.generate_key()?;
-    let digest = sha256(MESSAGE1)?;
+    let digester = Digester::sha256();
+    let digest = digester.digest(MESSAGE1)?;
+    assert_eq!(digest, sha256(MESSAGE1)?);
 
     let signature = ec_key.ecdsa_sign(&digest)?;
-    ec_key.ecdsa_verify(&signature, &digest)
+    ec_key.ecdsa_verify(&signature, &digest)?;
+    // Building a `PKey` from a temporary `CoseKey` should work as the lifetime
+    // of the `PKey` is not tied to the lifetime of the `CoseKey`.
+    let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+    pkey.verify(&signature, MESSAGE1, Some(digester))
+}
+
+#[test]
+fn ecdsa_p384_signing_and_verification_succeed() -> Result<()> {
+    let mut ec_key = EcKey::new_p384()?;
+    ec_key.generate_key()?;
+    let digester = Digester::sha384();
+    let digest = digester.digest(MESSAGE1)?;
+
+    let signature = ec_key.ecdsa_sign(&digest)?;
+    ec_key.ecdsa_verify(&signature, &digest)?;
+    let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+    pkey.verify(&signature, MESSAGE1, Some(digester))
 }
 
 #[test]
@@ -90,6 +119,12 @@
     let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
     let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
     assert_eq!(expected_err, err);
+
+    let pkey: PKey = ec_key2.try_into()?;
+    let err = pkey.verify(&signature, MESSAGE1, Some(Digester::sha256())).unwrap_err();
+    let expected_err =
+        Error::CallFailed(ApiName::EVP_DigestVerify, EcdsaError::BadSignature.into());
+    assert_eq!(expected_err, err);
     Ok(())
 }
 
diff --git a/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/src/fdt.rs b/pvmfw/src/fdt.rs
index 4fe2c34..5fbc767 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -201,6 +201,22 @@
     Ok(())
 }
 
+fn read_vendor_public_key_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
+    if let Some(avf_node) = fdt.node(cstr!("/avf"))? {
+        if let Some(vendor_public_key) = avf_node.getprop(cstr!("vendor_public_key"))? {
+            return Ok(Some(vendor_public_key.to_vec()));
+        }
+    }
+    Ok(None)
+}
+
+fn patch_vendor_public_key(fdt: &mut Fdt, vendor_public_key: &[u8]) -> libfdt::Result<()> {
+    let mut root_node = fdt.root_mut()?;
+    let mut avf_node = root_node.add_subnode(cstr!("/avf"))?;
+    avf_node.setprop(cstr!("vendor_public_key"), vendor_public_key)?;
+    Ok(())
+}
+
 #[derive(Debug)]
 struct PciInfo {
     ranges: [PciAddrRange; 2],
@@ -593,6 +609,7 @@
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
+    vendor_public_key: Option<Vec<u8>>,
 }
 
 impl DeviceTreeInfo {
@@ -701,6 +718,18 @@
         None => None,
     };
 
+    // TODO(b/285854379) : A temporary solution lives. This is for enabling
+    // microdroid vendor partition for non-protected VM as well. When passing
+    // DT path containing vendor_public_key via fstab, init stage will check
+    // if vendor_public_key exists in the init stage, regardless the protection.
+    // Adding this temporary solution will prevent fatal in init stage for
+    // protected VM. However, this data is not trustable without validating
+    // with vendor public key value comes from ABL.
+    let vendor_public_key = read_vendor_public_key_from(fdt).map_err(|e| {
+        error!("Failed to read vendor_public_key from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
     Ok(DeviceTreeInfo {
         kernel_range,
         initrd_range,
@@ -711,6 +740,7 @@
         serial_info,
         swiotlb_info,
         device_assignment,
+        vendor_public_key,
     })
 }
 
@@ -768,6 +798,12 @@
             RebootReason::InvalidFdt
         })?;
     }
+    if let Some(vendor_public_key) = &info.vendor_public_key {
+        patch_vendor_public_key(fdt, vendor_public_key).map_err(|e| {
+            error!("Failed to patch vendor_public_key to DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
+    }
 
     fdt.pack().map_err(|e| {
         error!("Failed to pack DT after patching: {e}");
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..029895f 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],
@@ -170,7 +226,8 @@
     parent_certificate: &X509Certificate,
 ) -> Result<()> {
     let cose_mac = CoseMac0::from_slice(maced_public_key)?;
-    let authority_public_key = EcKey::from_cose_public_key(&cose_mac.payload.unwrap()).unwrap();
+    let authority_public_key =
+        EcKey::from_cose_public_key_slice(&cose_mac.payload.unwrap()).unwrap();
     let (remaining, cert) = X509Certificate::from_der(certificate)?;
     assert!(remaining.is_empty());
 
@@ -188,9 +245,9 @@
     let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
     let csr_payload =
         cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
-    let subject_public_key = EcKey::from_cose_public_key(&csr_payload.public_key).unwrap();
+    let subject_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key).unwrap();
     let expected_spki_data =
-        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 +262,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/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..4e87136 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`.
 
-    let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+    // Verifies the second signature with the public key in the CSR payload.
+    let ec_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key)?;
     cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
         ecdsa_verify(&ec_public_key, signature, message)
     })?;
-    let subject_public_key_info = 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;