Merge "pvmfw: Use new VM DTBO format" into main
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 8481edf..cb3b2aa 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -5,7 +5,10 @@
 rust_defaults {
     name: "microdroid_manager_defaults",
     crate_name: "microdroid_manager",
-    defaults: ["avf_build_flags_rust"],
+    defaults: [
+        "avf_build_flags_rust",
+        "secretkeeper_use_latest_hal_aidl_rust",
+    ],
     srcs: ["src/main.rs"],
     edition: "2021",
     prefer_rlib: true,
@@ -43,6 +46,8 @@
         "libprotobuf",
         "librpcbinder_rs",
         "librustutils",
+        "libsecretkeeper_client",
+        "libsecretkeeper_comm_nostd",
         "libscopeguard",
         "libserde",
         "libserde_cbor",
@@ -51,6 +56,7 @@
         "libuuid",
         "libvsock",
         "librand",
+        "libzeroize",
     ],
     init_rc: ["microdroid_manager.rc"],
     multilib: {
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 0cf7013..a8b88aa 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -107,7 +107,7 @@
     apks.chain(apexes).collect()
 }
 
-// Returns a configuration descriptor of the given payload. See vm_config.cddl for a definition
+// Returns a configuration descriptor of the given payload. See vm_config.cddl for the definition
 // of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 9e167a4..c94a937 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -105,7 +105,6 @@
             MicrodroidError::PayloadInvalidConfig(msg) => {
                 (ErrorCode::PAYLOAD_INVALID_CONFIG, msg.to_string())
             }
-
             // Connection failure won't be reported to VS; return the default value
             MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
                 (ErrorCode::UNKNOWN, msg.to_string())
@@ -282,7 +281,8 @@
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
     let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
-    let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
+    let vm_secret =
+        VmSecret::new(dice_artifacts, service).context("Failed to create VM secrets")?;
 
     if cfg!(dice_changes) {
         // Now that the DICE derivation is done, it's ok to allow payload code to run.
diff --git a/microdroid_manager/src/vm_config.cddl b/microdroid_manager/src/vm_config.cddl
index 052262d..8508e8f 100644
--- a/microdroid_manager/src/vm_config.cddl
+++ b/microdroid_manager/src/vm_config.cddl
@@ -11,6 +11,10 @@
 
 ; The configuration descriptor node for a Microdroid VM, with extensions to describe the contents
 ; of the VM payload.
+; The subcomponents describe the APKs and then the APEXes that are part of the VM. The main APK
+; is first, followed by any extra APKs in the order they are specified in the VM config.
+; The APEXes are listed in the order specified when the VM is created, which is normally alphabetic
+; order by name.
 VmConfigDescriptor = {
     -70002 : "Microdroid payload",      ; Component name
     (? -71000: tstr //                  ; Path to the payload config file
@@ -23,9 +27,30 @@
 }
 
 ; Describes a unit of code (e.g. an APK or an APEX) present inside the VM.
+;
+; For an APK, the fields are as follows:
+; - Component name: The string "apk:" followed by the package name.
+; - Security version: The long version code from the APK manifest
+;   (https://developer.android.com/reference/android/content/pm/PackageInfo#getLongVersionCode()).
+; - Code hash: This is the root hash of a Merkle tree computed over all bytes of the APK, as used
+;   in the APK Signature Scheme v4 (https://source.android.com/docs/security/features/apksigning/v4)
+;   with empty salt and using SHA-256 as the hash algorithm.
+; - Authority hash: The SHA-512 hash of the DER representation of the X.509 certificate for the
+;   public key used to sign the APK.
+;
+; For an APEX, they are as follows:
+; - Component name: The string "apex:" followed by the APEX name as specified in the APEX Manifest
+;   (see https://source.android.com/docs/core/ota/apex).
+; - Security version: The version number from the APEX Manifest.
+; - Code hash: The root hash of the apex_payload.img file within the APEX, taken from the first
+;   hashtree descriptor in the VBMeta image
+;   (see https://android.googlesource.com/platform/external/avb/+/master/README.md).
+; - Authority hash: The SHA-512 hash of the public key used to sign the file system image in the
+;   APEX (as stored in the apex_pubkey file). The format is as described for AvbRSAPublicKeyHeader
+;   in https://cs.android.com/android/platform/superproject/main/+/main:external/avb/libavb/avb_crypto.h.
 SubcomponentDescriptor = {
   1: tstr,                              ; Component name
   2: uint,                              ; Security version
-  ? 3: bstr,                            ; Code hash
+  3: bstr,                              ; Code hash
   4: bstr,                              ; Authority hash
 }
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index d84c2e2..df5d318 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -14,18 +14,28 @@
 
 //! Class for encapsulating & managing represent VM secrets.
 
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
+use secretkeeper_comm::data_types::request::Request;
+use binder::{Strong};
+use coset::CborSerializable;
 use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
 use keystore2_crypto::ZVec;
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
 use openssl::sha;
+use secretkeeper_client::SkSession;
+use secretkeeper_comm::data_types::{Id, ID_SIZE, Secret, SECRET_SIZE};
+use secretkeeper_comm::data_types::response::Response;
+use secretkeeper_comm::data_types::packet::{ResponsePacket, ResponseType};
+use secretkeeper_comm::data_types::request_response_impl::{
+    StoreSecretRequest, GetSecretResponse, GetSecretRequest};
+use secretkeeper_comm::data_types::error::SecretkeeperError;
+use zeroize::Zeroizing;
 
 const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
 
-// Size of the secret stored in Secretkeeper.
-const SK_SECRET_SIZE: usize = 64;
-
 // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
 const SALT_ENCRYPTED_STORE: &[u8] = &[
     0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D, 0x6F,
@@ -36,6 +46,24 @@
     0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
 ];
 
+// TODO(b/291213394): Remove this once policy is generated from dice_chain
+const HYPOTHETICAL_DICE_POLICY: [u8; 43] = [
+    0x83, 0x01, 0x81, 0x83, 0x01, 0x80, 0xA1, 0x01, 0x00, 0x82, 0x83, 0x01, 0x81, 0x01, 0x73, 0x74,
+    0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, 0x5F, 0x64, 0x69, 0x63, 0x65, 0x5F, 0x70, 0x6F, 0x6C, 0x69,
+    0x63, 0x79, 0x83, 0x02, 0x82, 0x03, 0x18, 0x64, 0x19, 0xE9, 0x75,
+];
+// TODO(b/291213394): Differentiate the Id of nPVM based on 'salt'
+const ID_NP_VM: [u8; ID_SIZE] = [
+    0xF1, 0xB2, 0xED, 0x3B, 0xD1, 0xBD, 0xF0, 0x7D, 0xE1, 0xF0, 0x01, 0xFC, 0x61, 0x71, 0xD3, 0x42,
+    0xE5, 0x8A, 0xAF, 0x33, 0x6C, 0x11, 0xDC, 0xC8, 0x6F, 0xAE, 0x12, 0x5C, 0x26, 0x44, 0x6B, 0x86,
+    0xCC, 0x24, 0xFD, 0xBF, 0x91, 0x4A, 0x54, 0x84, 0xF9, 0x01, 0x59, 0x25, 0x70, 0x89, 0x38, 0x8D,
+    0x5E, 0xE6, 0x91, 0xDF, 0x68, 0x60, 0x69, 0x26, 0xBE, 0xFE, 0x79, 0x58, 0xF7, 0xEA, 0x81, 0x7D,
+];
+const SKP_SECRET_NP_VM: [u8; SECRET_SIZE] = [
+    0xA9, 0x89, 0x97, 0xFE, 0xAE, 0x97, 0x55, 0x4B, 0x32, 0x35, 0xF0, 0xE8, 0x93, 0xDA, 0xEA, 0x24,
+    0x06, 0xAC, 0x36, 0x8B, 0x3C, 0x95, 0x50, 0x16, 0x67, 0x71, 0x65, 0x26, 0xEB, 0xD0, 0xC3, 0x98,
+];
+
 pub enum VmSecret {
     // V2 secrets are derived from 2 independently secured secrets:
     //      1. Secretkeeper protected secrets (skp secret).
@@ -54,15 +82,47 @@
     V1 { dice: OwnedDiceArtifacts },
 }
 
+fn get_id() -> [u8; ID_SIZE] {
+    if super::is_strict_boot() {
+        todo!("Id for protected VM is not implemented");
+    } else {
+        ID_NP_VM
+    }
+}
+
 impl VmSecret {
-    pub fn new(dice_artifacts: OwnedDiceArtifacts) -> Result<VmSecret> {
-        if is_sk_supported() {
-            // TODO(b/291213394): Change this to real Sk protected secret.
-            let fake_skp_secret = ZVec::new(SK_SECRET_SIZE)?;
-            return Ok(Self::V2 { dice: dice_artifacts, skp_secret: fake_skp_secret });
+    pub fn new(
+        dice_artifacts: OwnedDiceArtifacts,
+        vm_service: &Strong<dyn IVirtualMachineService>,
+    ) -> Result<VmSecret> {
+        ensure!(dice_artifacts.bcc().is_some(), "Dice chain missing");
+
+        if let Some(sk_service) = is_sk_supported(vm_service)? {
+            let id = get_id();
+            let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
+            if super::is_strict_boot() {
+                if super::is_new_instance() {
+                    *skp_secret = rand::random();
+                    store_secret(sk_service.clone(), id, skp_secret.clone(), &dice_artifacts)?;
+                } else {
+                    // Subsequent run of the pVM -> get the secret stored in Secretkeeper.
+                    *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
+                }
+            } else {
+                // TODO(b/291213394): Non protected VM don't need to use Secretkeeper, remove this
+                // once we have sufficient testing on protected VM.
+                store_secret(sk_service.clone(), id, SKP_SECRET_NP_VM.into(), &dice_artifacts)?;
+                *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
+            }
+            return Ok(Self::V2 {
+                dice: dice_artifacts,
+                skp_secret: ZVec::try_from(skp_secret.to_vec())?,
+            });
         }
+        //  Use V1 secrets if Secretkeeper is not supported.
         Ok(Self::V1 { dice: dice_artifacts })
     }
+
     pub fn dice(&self) -> &OwnedDiceArtifacts {
         match self {
             Self::V2 { dice, .. } => dice,
@@ -94,13 +154,87 @@
     }
 }
 
-// Does the hardware support Secretkeeper.
-fn is_sk_supported() -> bool {
-    if cfg!(llpvm_changes) {
-        return false;
+fn store_secret(
+    secretkeeper: binder::Strong<dyn ISecretkeeper>,
+    id: [u8; ID_SIZE],
+    secret: Zeroizing<[u8; SECRET_SIZE]>,
+    _dice_chain: &OwnedDiceArtifacts,
+) -> Result<()> {
+    // Start a new secretkeeper session!
+    let session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
+    let store_request = StoreSecretRequest {
+        id: Id(id),
+        secret: Secret(*secret),
+        // TODO(b/291233371): Construct policy out of dice_chain.
+        sealing_policy: HYPOTHETICAL_DICE_POLICY.to_vec(),
     };
-    // TODO(b/292209416): This value should be extracted from device tree.
-    // Note: this does not affect the security of pVM. pvmfw & microdroid_manager continue to block
-    // upgraded images. Setting this true is equivalent to including constant salt in vm secrets.
-    true
+    log::info!("Secretkeeper operation: {:?}", store_request);
+
+    let store_request = store_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+    let store_response = session.secret_management_request(&store_request).map_err(anyhow_err)?;
+    let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
+    let response_type = store_response.response_type().map_err(anyhow_err)?;
+    ensure!(
+        response_type == ResponseType::Success,
+        "Secretkeeper store failed with error: {:?}",
+        *SecretkeeperError::deserialize_from_packet(store_response).map_err(anyhow_err)?
+    );
+    Ok(())
+}
+
+fn get_secret(
+    secretkeeper: binder::Strong<dyn ISecretkeeper>,
+    id: [u8; ID_SIZE],
+    _dice_chain: &OwnedDiceArtifacts,
+) -> Result<[u8; SECRET_SIZE]> {
+    // Start a new secretkeeper session!
+    let session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
+    let get_request = GetSecretRequest {
+        id: Id(id),
+        // TODO(b/291233371): Construct policy out of dice_chain.
+        updated_sealing_policy: None,
+    };
+    log::info!("Secretkeeper operation: {:?}", get_request);
+
+    let get_request = get_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+    let get_response = session.secret_management_request(&get_request).map_err(anyhow_err)?;
+    let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
+    let response_type = get_response.response_type().map_err(anyhow_err)?;
+    ensure!(
+        response_type == ResponseType::Success,
+        "Secretkeeper get failed with error: {:?}",
+        *SecretkeeperError::deserialize_from_packet(get_response).map_err(anyhow_err)?
+    );
+    let get_response =
+        *GetSecretResponse::deserialize_from_packet(get_response).map_err(anyhow_err)?;
+    Ok(get_response.secret.0)
+}
+
+#[inline]
+fn anyhow_err<E: core::fmt::Debug>(err: E) -> anyhow::Error {
+    anyhow!("{:?}", err)
+}
+
+// Get the secretkeeper connection if supported. Host can be consulted whether the device supports
+// secretkeeper but that should be used with caution for protected VM.
+fn is_sk_supported(
+    host: &Strong<dyn IVirtualMachineService>,
+) -> Result<Option<Strong<dyn ISecretkeeper>>> {
+    let sk = if cfg!(llpvm_changes) {
+        if super::is_strict_boot() {
+            // TODO: For protected VM check for Secretkeeper authentication data in device tree.
+            None
+        } else {
+            // For non-protected VM, believe what host claims.
+            host.getSecretkeeper()
+                // TODO rename this error!
+                .map_err(|e| {
+                    super::MicrodroidError::FailedToConnectToVirtualizationService(e.to_string())
+                })?
+        }
+    } else {
+        // LLPVM flag is disabled
+        None
+    };
+    Ok(sk)
 }
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 2fe4ec9..3f78a88 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -19,11 +19,10 @@
 use core::num::NonZeroUsize;
 use core::ops::Range;
 use core::result;
-use core::slice;
 use log::{info, warn};
 use static_assertions::const_assert_eq;
 use vmbase::util::RangeExt;
-use zerocopy::{FromBytes, FromZeroes, LayoutVerified};
+use zerocopy::{FromBytes, FromZeroes};
 
 /// Configuration data header.
 #[repr(C, packed)]
@@ -129,12 +128,14 @@
 
 impl Entry {
     const COUNT: usize = Self::_VARIANT_COUNT as usize;
+
+    const ALL_ENTRIES: [Entry; Self::COUNT] = [Self::Bcc, Self::DebugPolicy, Self::VmDtbo];
 }
 
 #[derive(Default)]
 pub struct Entries<'a> {
     pub bcc: &'a mut [u8],
-    pub debug_policy: Option<&'a mut [u8]>,
+    pub debug_policy: Option<&'a [u8]>,
     pub vm_dtbo: Option<&'a mut [u8]>,
 }
 
@@ -203,7 +204,7 @@
         }
 
         let (header, rest) =
-            LayoutVerified::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
+            zerocopy::Ref::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
         let header = header.into_ref();
 
         if header.magic != Header::MAGIC {
@@ -230,7 +231,7 @@
         };
 
         let (header_entries, body) =
-            LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
+            zerocopy::Ref::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
                 .ok_or(Error::BufferTooSmall)?;
 
         // Validate that we won't get an invalid alignment in the following due to padding to u64.
@@ -240,7 +241,7 @@
         let limits = header.body_lowest_bound()?..total_size;
         let mut ranges: [Option<NonEmptyRange>; Entry::COUNT] = [None; Entry::COUNT];
         let mut last_end = 0;
-        for entry in [Entry::Bcc, Entry::DebugPolicy, Entry::VmDtbo] {
+        for entry in Entry::ALL_ENTRIES {
             let Some(header_entry) = header_entries.get(entry as usize) else { continue };
             let entry_offset = header_entry.offset.try_into().unwrap();
             let entry_size = header_entry.size.try_into().unwrap();
@@ -266,36 +267,31 @@
         Ok(Self { body, ranges })
     }
 
-    /// Get slice containing the platform BCC.
-    pub fn get_entries(&mut self) -> Entries<'_> {
-        // This assumes that the blobs are in-order w.r.t. the entries.
-        let bcc_range = self.get_entry_range(Entry::Bcc);
-        let dp_range = self.get_entry_range(Entry::DebugPolicy);
-        let vm_dtbo_range = self.get_entry_range(Entry::VmDtbo);
-        // TODO(b/291191157): Provision device assignment with this.
-        if let Some(vm_dtbo_range) = vm_dtbo_range {
-            info!("Found VM DTBO at {:?}", vm_dtbo_range);
+    /// Locate the various config entries.
+    pub fn get_entries(self) -> Entries<'a> {
+        // We require the blobs to be in the same order as the `Entry` enum (and this is checked
+        // in `new` above)
+        // So we can just work through the body range and split off the parts we are interested in.
+        let mut offset = 0;
+        let mut body = self.body;
+
+        let mut entries: [Option<&mut [u8]>; Entry::COUNT] = Default::default();
+        for (i, range) in self.ranges.iter().enumerate() {
+            if let Some(range) = range {
+                body = &mut body[range.start - offset..];
+                let (chunk, rest) = body.split_at_mut(range.len());
+                offset = range.end();
+                body = rest;
+                entries[i] = Some(chunk);
+            }
         }
+        let [bcc, debug_policy, vm_dtbo] = entries;
 
-        // SAFETY: When instantiated, ranges are validated to be in the body range without
-        // overlapping.
-        let (bcc, debug_policy, vm_dtbo) = unsafe {
-            let ptr = self.body.as_mut_ptr() as usize;
-            (
-                Self::from_raw_range_mut(ptr, bcc_range.unwrap()),
-                dp_range.map(|dp_range| Self::from_raw_range_mut(ptr, dp_range)),
-                vm_dtbo_range.map(|vm_dtbo_range| Self::from_raw_range_mut(ptr, vm_dtbo_range)),
-            )
-        };
+        // The platform BCC has always been required.
+        let bcc = bcc.unwrap();
+
+        // We have no reason to mutate so drop the `mut`.
+        let debug_policy = debug_policy.map(|x| &*x);
         Entries { bcc, debug_policy, vm_dtbo }
     }
-
-    fn get_entry_range(&self, entry: Entry) -> Option<NonEmptyRange> {
-        self.ranges[entry as usize]
-    }
-
-    unsafe fn from_raw_range_mut(ptr: usize, range: NonEmptyRange) -> &'a mut [u8] {
-        // SAFETY: The caller must ensure that the range is valid from ptr.
-        unsafe { slice::from_raw_parts_mut((ptr + range.start) as *mut u8, range.len()) }
-    }
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index f4078a3..03f2f62 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -207,7 +207,7 @@
     // then remapped by `init_page_table()`.
     let appended_data = unsafe { get_appended_data_slice() };
 
-    let mut appended = AppendedPayload::new(appended_data).ok_or_else(|| {
+    let appended = AppendedPayload::new(appended_data).ok_or_else(|| {
         error!("No valid configuration found");
         RebootReason::InvalidConfig
     })?;
@@ -438,7 +438,7 @@
         }
     }
 
-    fn get_entries(&mut self) -> config::Entries<'_> {
+    fn get_entries(self) -> config::Entries<'a> {
         match self {
             Self::Config(cfg) => cfg.get_entries(),
             Self::LegacyBcc(bcc) => config::Entries { bcc, ..Default::default() },
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 2cd1061..2a6819b 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -200,19 +200,27 @@
     Ok(())
 }
 
-fn read_vendor_public_key_from(fdt: &Fdt) -> libfdt::Result<Option<Vec<u8>>> {
+fn read_vendor_hashtree_descriptor_root_digest_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()));
+        if let Some(vendor_hashtree_descriptor_root_digest) =
+            avf_node.getprop(cstr!("vendor_hashtree_descriptor_root_digest"))?
+        {
+            return Ok(Some(vendor_hashtree_descriptor_root_digest.to_vec()));
         }
     }
     Ok(None)
 }
 
-fn patch_vendor_public_key(fdt: &mut Fdt, vendor_public_key: &[u8]) -> libfdt::Result<()> {
+fn patch_vendor_hashtree_descriptor_root_digest(
+    fdt: &mut Fdt,
+    vendor_hashtree_descriptor_root_digest: &[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)?;
+    avf_node.setprop(
+        cstr!("vendor_hashtree_descriptor_root_digest"),
+        vendor_hashtree_descriptor_root_digest,
+    )?;
     Ok(())
 }
 
@@ -608,7 +616,7 @@
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
-    vendor_public_key: Option<Vec<u8>>,
+    vendor_hashtree_descriptor_root_digest: Option<Vec<u8>>,
 }
 
 impl DeviceTreeInfo {
@@ -738,15 +746,17 @@
 
     // 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
-    })?;
+    // DT path containing vendor_hashtree_descriptor_root_digest via fstab, init
+    // stage will check if vendor_hashtree_descriptor_root_digest 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 root digest of vendor hashtree
+    // descriptor comes from ABL.
+    let vendor_hashtree_descriptor_root_digest =
+        read_vendor_hashtree_descriptor_root_digest_from(fdt).map_err(|e| {
+            error!("Failed to read vendor_hashtree_descriptor_root_digest from DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
 
     Ok(DeviceTreeInfo {
         kernel_range,
@@ -758,7 +768,7 @@
         serial_info,
         swiotlb_info,
         device_assignment,
-        vendor_public_key,
+        vendor_hashtree_descriptor_root_digest,
     })
 }
 
@@ -811,9 +821,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}");
+    if let Some(vendor_hashtree_descriptor_root_digest) =
+        &info.vendor_hashtree_descriptor_root_digest
+    {
+        patch_vendor_hashtree_descriptor_root_digest(fdt, vendor_hashtree_descriptor_root_digest)
+            .map_err(|e| {
+            error!("Failed to patch vendor_hashtree_descriptor_root_digest to DT: {e}");
             RebootReason::InvalidFdt
         })?;
     }
@@ -827,7 +840,7 @@
     bcc: &[u8],
     new_instance: bool,
     strict_boot: bool,
-    debug_policy: Option<&mut [u8]>,
+    debug_policy: Option<&[u8]>,
     debuggable: bool,
     kaslr_seed: u64,
 ) -> libfdt::Result<()> {
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 1d55a84..09bb899 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -63,7 +63,7 @@
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
     current_bcc_handover: &[u8],
-    mut debug_policy: Option<&mut [u8]>,
+    mut debug_policy: Option<&[u8]>,
 ) -> Result<Range<usize>, RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", fdt.as_ptr());
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 33897b2..60c94fc 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -5,7 +5,11 @@
 rust_defaults {
     name: "virtualizationmanager_defaults",
     crate_name: "virtualizationmanager",
-    defaults: ["avf_build_flags_rust"],
+    defaults: [
+        "avf_build_flags_rust",
+        "secretkeeper_use_latest_hal_aidl_rust",
+        "authgraph_use_latest_hal_aidl_rust",
+    ],
     edition: "2021",
     // Only build on targets which crosvm builds on.
     enabled: false,
@@ -34,6 +38,7 @@
         "libclap",
         "libcommand_fds",
         "libdisk",
+        "libhex",
         "libhypervisor_props",
         "liblazy_static",
         "liblibc",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 7f98fe8..8c2099f 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -52,6 +52,14 @@
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
         BnVirtualMachineService, IVirtualMachineService,
 };
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{BnSecretkeeper, ISecretkeeper};
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::SecretId::SecretId;
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+    Arc::Arc as AuthgraphArc, IAuthGraphKeyExchange::IAuthGraphKeyExchange,
+    IAuthGraphKeyExchange::BnAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
+    Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
+    SessionInitiationInfo::SessionInitiationInfo,
+};
 use anyhow::{anyhow, bail, Context, Result};
 use apkverify::{HashAlgorithm, V4Signature};
 use avflog::LogResult;
@@ -102,9 +110,13 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
+// TODO(b/291213394): Use 'default' instance for secretkeeper instead of 'nonsecure'
+const SECRETKEEPER_IDENTIFIER: &str =
+    "android.hardware.security.secretkeeper.ISecretkeeper/nonsecure";
+
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
-/// Roughly estimated sufficient size for storing vendor public key into DTBO.
+/// Rough size for storing root digest of vendor hash descriptor into DTBO.
 const EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE: usize = 10000;
 
 /// crosvm requires all partitions to be a multiple of 4KiB.
@@ -369,13 +381,17 @@
             check_gdb_allowed(config)?;
         }
 
-        let vendor_public_key = extract_vendor_public_key(config)
-            .context("Failed to extract vendor public key")
-            .or_service_specific_exception(-1)?;
-        let dtbo_vendor = if let Some(vendor_public_key) = vendor_public_key {
+        let vendor_hashtree_descriptor_root_digest =
+            extract_vendor_hashtree_descriptor_root_digest(config)
+                .context("Failed to extract root digest of vendor")
+                .or_service_specific_exception(-1)?;
+        let dtbo_vendor = if let Some(vendor_hashtree_descriptor_root_digest) =
+            vendor_hashtree_descriptor_root_digest
+        {
+            let root_digest_hex = hex::encode(vendor_hashtree_descriptor_root_digest);
             let dtbo_for_vendor_image = temporary_directory.join("dtbo_vendor");
-            create_dtbo_for_vendor_image(&vendor_public_key, &dtbo_for_vendor_image)
-                .context("Failed to write vendor_public_key")
+            create_dtbo_for_vendor_image(root_digest_hex.as_bytes(), &dtbo_for_vendor_image)
+                .context("Failed to write root digest of vendor")
                 .or_service_specific_exception(-1)?;
             let file = File::open(dtbo_for_vendor_image)
                 .context("Failed to open dtbo_vendor")
@@ -559,7 +575,9 @@
     }
 }
 
-fn extract_vendor_public_key(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
+fn extract_vendor_hashtree_descriptor_root_digest(
+    config: &VirtualMachineConfig,
+) -> Result<Option<Vec<u8>>> {
     let VirtualMachineConfig::AppConfig(config) = config else {
         return Ok(None);
     };
@@ -574,15 +592,19 @@
     let size = file.metadata().context("Failed to get metadata from microdroid-vendor.img")?.len();
     let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
         .context("Failed to get vbmeta from microdroid-vendor.img")?;
-    let vendor_public_key = vbmeta
-        .public_key()
-        .ok_or(anyhow!("No public key is extracted from microdroid-vendor.img"))?
-        .to_vec();
 
-    Ok(Some(vendor_public_key))
+    for descriptor in vbmeta.descriptors()?.iter() {
+        if let vbmeta::Descriptor::Hashtree(_) = descriptor {
+            return Ok(Some(descriptor.to_hashtree()?.root_digest().to_vec()));
+        }
+    }
+    Err(anyhow!("No root digest is extracted from microdroid-vendor.img"))
 }
 
-fn create_dtbo_for_vendor_image(vendor_public_key: &[u8], dtbo: &PathBuf) -> Result<()> {
+fn create_dtbo_for_vendor_image(
+    vendor_hashtree_descriptor_root_digest: &[u8],
+    dtbo: &PathBuf,
+) -> Result<()> {
     if dtbo.exists() {
         return Err(anyhow!("DTBO file already exists"));
     }
@@ -610,10 +632,16 @@
     let mut avf_node = overlay_node
         .add_subnode(avf_node_name.as_c_str())
         .map_err(|e| anyhow!("Failed to create avf node: {:?}", e))?;
-    let vendor_public_key_name = CString::new("vendor_public_key")?;
+    let vendor_hashtree_descriptor_root_digest_name =
+        CString::new("vendor_hashtree_descriptor_root_digest")?;
     avf_node
-        .setprop(vendor_public_key_name.as_c_str(), vendor_public_key)
-        .map_err(|e| anyhow!("Failed to set avf/vendor_public_key: {:?}", e))?;
+        .setprop(
+            vendor_hashtree_descriptor_root_digest_name.as_c_str(),
+            vendor_hashtree_descriptor_root_digest,
+        )
+        .map_err(|e| {
+            anyhow!("Failed to set avf/vendor_hashtree_descriptor_root_digest: {:?}", e)
+        })?;
 
     fdt.pack().map_err(|e| anyhow!("Failed to pack fdt: {:?}", e))?;
     let mut file = File::create(dtbo)?;
@@ -1370,6 +1398,20 @@
         }
     }
 
+    fn getSecretkeeper(&self) -> binder::Result<Option<Strong<dyn ISecretkeeper>>> {
+        let sk = match binder::get_interface(SECRETKEEPER_IDENTIFIER) {
+            Ok(sk) => {
+                Some(BnSecretkeeper::new_binder(SecretkeeperProxy(sk), BinderFeatures::default()))
+            }
+            Err(StatusCode::NAME_NOT_FOUND) => None,
+            Err(e) => {
+                error!("unexpected error while fetching connection to Secretkeeper {:?}", e);
+                return Err(e.into());
+            }
+        };
+        Ok(sk)
+    }
+
     fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
         GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
     }
@@ -1502,13 +1544,14 @@
 
     #[test]
     fn test_create_dtbo_for_vendor_image() -> Result<()> {
-        let vendor_public_key = String::from("foo");
-        let vendor_public_key = vendor_public_key.as_bytes();
+        let vendor_hashtree_descriptor_root_digest = String::from("foo");
+        let vendor_hashtree_descriptor_root_digest =
+            vendor_hashtree_descriptor_root_digest.as_bytes();
 
         let tmp_dir = tempfile::TempDir::new()?;
         let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
 
-        create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+        create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
 
         let data = std::fs::read(dtbo_path)?;
         let fdt = Fdt::from_slice(&data).unwrap();
@@ -1529,9 +1572,11 @@
         let Some(avf_node) = avf_node else {
             bail!("avf_node shouldn't be None.");
         };
-        let vendor_public_key_name = CString::new("vendor_public_key")?;
-        let key_from_dtbo = avf_node.getprop(vendor_public_key_name.as_c_str()).unwrap();
-        assert_eq!(key_from_dtbo, Some(vendor_public_key));
+        let vendor_hashtree_descriptor_root_digest_name =
+            CString::new("vendor_hashtree_descriptor_root_digest")?;
+        let digest_from_dtbo =
+            avf_node.getprop(vendor_hashtree_descriptor_root_digest_name.as_c_str()).unwrap();
+        assert_eq!(digest_from_dtbo, Some(vendor_hashtree_descriptor_root_digest));
 
         tmp_dir.close()?;
         Ok(())
@@ -1539,18 +1584,84 @@
 
     #[test]
     fn test_create_dtbo_for_vendor_image_throws_error_if_already_exists() -> Result<()> {
-        let vendor_public_key = String::from("foo");
-        let vendor_public_key = vendor_public_key.as_bytes();
+        let vendor_hashtree_descriptor_root_digest = String::from("foo");
+        let vendor_hashtree_descriptor_root_digest =
+            vendor_hashtree_descriptor_root_digest.as_bytes();
 
         let tmp_dir = tempfile::TempDir::new()?;
         let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
 
-        create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+        create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path)?;
 
-        let ret_second_trial = create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path);
+        let ret_second_trial =
+            create_dtbo_for_vendor_image(vendor_hashtree_descriptor_root_digest, &dtbo_path);
         assert!(ret_second_trial.is_err(), "should fail");
 
         tmp_dir.close()?;
         Ok(())
     }
 }
+
+struct SecretkeeperProxy(Strong<dyn ISecretkeeper>);
+
+impl Interface for SecretkeeperProxy {}
+
+impl ISecretkeeper for SecretkeeperProxy {
+    fn processSecretManagementRequest(&self, req: &[u8]) -> binder::Result<Vec<u8>> {
+        // Pass the request to the channel, and read the response.
+        self.0.processSecretManagementRequest(req)
+    }
+
+    fn getAuthGraphKe(&self) -> binder::Result<Strong<dyn IAuthGraphKeyExchange>> {
+        let ag = AuthGraphKeyExchangeProxy(self.0.getAuthGraphKe()?);
+        Ok(BnAuthGraphKeyExchange::new_binder(ag, BinderFeatures::default()))
+    }
+
+    fn deleteIds(&self, ids: &[SecretId]) -> binder::Result<()> {
+        self.0.deleteIds(ids)
+    }
+
+    fn deleteAll(&self) -> binder::Result<()> {
+        self.0.deleteAll()
+    }
+}
+
+struct AuthGraphKeyExchangeProxy(Strong<dyn IAuthGraphKeyExchange>);
+
+impl Interface for AuthGraphKeyExchangeProxy {}
+
+impl IAuthGraphKeyExchange for AuthGraphKeyExchangeProxy {
+    fn create(&self) -> binder::Result<SessionInitiationInfo> {
+        self.0.create()
+    }
+
+    fn init(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_nonce: &[u8],
+        peer_version: i32,
+    ) -> binder::Result<KeInitResult> {
+        self.0.init(peer_pub_key, peer_id, peer_nonce, peer_version)
+    }
+
+    fn finish(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_signature: &SessionIdSignature,
+        peer_nonce: &[u8],
+        peer_version: i32,
+        own_key: &Key,
+    ) -> binder::Result<SessionInfo> {
+        self.0.finish(peer_pub_key, peer_id, peer_signature, peer_nonce, peer_version, own_key)
+    }
+
+    fn authenticationComplete(
+        &self,
+        peer_signature: &SessionIdSignature,
+        shared_keys: &[AuthgraphArc; 2],
+    ) -> binder::Result<[AuthgraphArc; 2]> {
+        self.0.authenticationComplete(peer_signature, shared_keys)
+    }
+}
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index c69fe8f..8ca375a 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -57,7 +57,10 @@
 aidl_interface {
     name: "android.system.virtualmachineservice",
     srcs: ["android/system/virtualmachineservice/**/*.aidl"],
-    imports: ["android.system.virtualizationcommon"],
+    imports: [
+        "android.hardware.security.secretkeeper-V1",
+        "android.system.virtualizationcommon",
+    ],
     unstable: true,
     backend: {
         java: {
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 3c60478..cf91302 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -15,6 +15,7 @@
  */
 package android.system.virtualmachineservice;
 
+import android.hardware.security.secretkeeper.ISecretkeeper;
 import android.system.virtualizationcommon.Certificate;
 import android.system.virtualizationcommon.ErrorCode;
 
@@ -54,4 +55,11 @@
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
     Certificate[] requestAttestation(in byte[] csr);
+
+    /**
+     * Request connection to Secretkeeper. This is used by pVM to store Anti-Rollback protected
+     * secrets. Note that the return value is nullable to reflect that Secretkeeper HAL may not be
+     * present.
+     */
+    @nullable ISecretkeeper getSecretkeeper();
 }