Migrate DICE access to IVmPayloadService

Take control of the microdroid interface to DICE so we can eventually
expose only what we need to with a simple interface.

Merge the DICE logic into microdroid_manager, removing a process,
removing IDiceMaintenance, and replacing IDiceNode with
IVmPayloadService for access to the DICE values. The current interface
is just meant to be enough to migrate current payload but it will need
to be refined.

The new interface is exposed in the vm_payload library and clients are
updated to use the new library functions.

Bug: 243133253
Bug: 243514248
Test: atest MicrodroidTests
Test: atest ComposHostTestCases
Change-Id: I2d659576ca3ccdb8f8ffd9d22ff0d1b96e85a3b8
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index fc165c8..268d3a2 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -28,4 +28,4 @@
 # Virtual console for logcat
 /dev/hvc2                 0666   system     system
 
-/dev/open-dice0           0660   diced      diced
+/dev/open-dice0           0660   root       root
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index 36480da..6dba760 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -20,10 +20,34 @@
 extern "C" {
 #endif
 
-/// Notifies the host that the payload is ready.
-/// Returns true if the notification succeeds else false.
+/**
+ * Notifies the host that the payload is ready.
+ * Returns true if the notification succeeds else false.
+ */
 bool notify_payload_ready();
 
+/**
+ * Get the VM's attestation chain.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the contained privacy breaking identifiers to the payload
+ * TODO: keep the DICE chain as an internal detail for as long as possible
+ */
+size_t get_dice_attestation_chain(void *data, size_t size);
+
+/**
+ * Get the VM's attestation CDI.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the raw CDI, only derived values
+ */
+size_t get_dice_attestation_cdi(void *data, size_t size);
+
+/**
+ * Get the VM's sealing CDI.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the raw CDI, only derived values
+ */
+size_t get_dice_sealing_cdi(void *data, size_t size);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index 394578a..e3da227 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -16,4 +16,7 @@
 
 mod vm_service;
 
-pub use vm_service::notify_payload_ready;
+pub use vm_service::{
+    get_dice_attestation_cdi, get_dice_attestation_chain, get_dice_sealing_cdi,
+    notify_payload_ready,
+};
diff --git a/microdroid/vm_payload/src/vm_service.rs b/microdroid/vm_payload/src/vm_service.rs
index 7414827..18d8222 100644
--- a/microdroid/vm_payload/src/vm_service.rs
+++ b/microdroid/vm_payload/src/vm_service.rs
@@ -28,7 +28,7 @@
         android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
     );
     if let Err(e) = try_notify_payload_ready() {
-        error!("Failed to notify ready: {}", e);
+        error!("{:?}", e);
         false
     } else {
         info!("Notified host payload ready successfully");
@@ -42,6 +42,90 @@
     get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
 }
 
+/// Get the VM's attestation chain.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_chain(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_attestation_chain() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(chain) => {
+            if size < chain.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(chain.as_ptr(), data, chain.len());
+                chain.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
+}
+
+/// Get the VM's attestation CDI.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_cdi(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_attestation_cdi() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(cdi) => {
+            if size < cdi.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, cdi.len());
+                cdi.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
+}
+
+/// Get the VM's sealing CDI.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_sealing_cdi(data: *mut u8, size: usize) -> usize {
+    match try_get_dice_sealing_cdi() {
+        Err(e) => {
+            error!("{:?}", e);
+            0
+        }
+        Ok(cdi) => {
+            if size < cdi.len() {
+                0
+            } else {
+                std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, cdi.len());
+                cdi.len()
+            }
+        }
+    }
+}
+
+fn try_get_dice_sealing_cdi() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceSealingCdi().context("Cannot get sealing CDI")
+}
+
 fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
     wait_for_interface(VM_PAYLOAD_SERVICE_NAME)
         .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_NAME))
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index b281d5c..4b06b3e 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,8 +9,6 @@
     edition: "2021",
     prefer_rlib: true,
     rustlibs: [
-        "android.hardware.security.dice-V1-rust",
-        "android.security.dice-rust",
         "android.system.virtualizationcommon-rust",
         "android.system.virtualizationservice-rust",
         "android.system.virtualmachineservice-rust",
@@ -20,6 +18,9 @@
         "libapkverify",
         "libbinder_rs",
         "libbyteorder",
+        "libdiced",
+        "libdiced_open_dice_cbor",
+        "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
         "libitertools",
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 82cbf43..9f56957 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -26,4 +26,31 @@
 
     /** Notifies that the payload is ready to serve. */
     void notifyPayloadReady();
+
+    /**
+     * Gets the DICE attestation chain for the VM.
+     *
+     * STOPSHIP:
+     * TODO: don't expose this to untrusted payloads as it contains privacy breaking identifiers.
+     */
+    byte[] getDiceAttestationChain();
+
+    /**
+     * Gets the DICE attestation CDI for the VM.
+     *
+     * STOPSHIP:
+     * TODO: A better API would handle key derivation on behalf of the payload so they can't forget
+     * to do it themselves. It also means the payload doesn't get the raw CDI so there's less chance
+     * of it leaking.
+     */
+    byte[] getDiceAttestationCdi();
+
+    /**
+     * Gets the DICE sealing CDI for the VM.
+     *
+     * TODO: A better API would handle key derivation on behalf of the payload so they can't forget
+     * to do it themselves. It also means the payload doesn't get the raw CDI so there's less chance
+     * of it leaking.
+     */
+    byte[] getDiceSealingCdi();
 }
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
new file mode 100644
index 0000000..3881db3
--- /dev/null
+++ b/microdroid_manager/src/dice.rs
@@ -0,0 +1,177 @@
+// Copyright 2022, 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.
+
+//! Logic for handling the DICE values and boot operations.
+
+use anyhow::{bail, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use diced_open_dice_cbor::{
+    Config, ContextImpl, InputValuesOwned, Mode, OpenDiceCborContext, CDI_SIZE, HASH_SIZE,
+    HIDDEN_SIZE,
+};
+use keystore2_crypto::ZVec;
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use std::fs;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
+
+/// Artifacts that are kept in the process address space after the artifacts from the driver have
+/// been consumed.
+pub struct DiceContext {
+    pub cdi_attest: [u8; CDI_SIZE],
+    pub cdi_seal: [u8; CDI_SIZE],
+    pub bcc: Vec<u8>,
+}
+
+/// Artifacts that are mapped into the process address space from the driver.
+pub enum DiceDriver<'a> {
+    Real {
+        driver_path: PathBuf,
+        mmap_addr: *mut c_void,
+        mmap_size: usize,
+        cdi_attest: &'a [u8; CDI_SIZE],
+        cdi_seal: &'a [u8; CDI_SIZE],
+        bcc: &'a [u8],
+    },
+    Fake(DiceContext),
+}
+
+impl DiceDriver<'_> {
+    pub fn new(driver_path: &Path) -> Result<Self> {
+        if driver_path.exists() {
+            log::info!("Using DICE values from driver");
+        } else if super::is_strict_boot() {
+            bail!("Strict boot requires DICE value from driver but none were found");
+        } else {
+            log::warn!("Using sample DICE values");
+            let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+                .expect("Failed to create sample dice artifacts.");
+            return Ok(Self::Fake(DiceContext {
+                cdi_attest: cdi_attest[..].try_into().unwrap(),
+                cdi_seal: cdi_seal[..].try_into().unwrap(),
+                bcc,
+            }));
+        };
+
+        let mut file = fs::File::open(driver_path)
+            .map_err(|error| Error::new(error).context("Opening driver"))?;
+        let mmap_size =
+            file.read_u64::<NativeEndian>()
+                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+        // It's safe to map the driver as the service will only create a single
+        // mapping per process.
+        let mmap_addr = unsafe {
+            let fd = file.as_raw_fd();
+            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+        };
+        if mmap_addr == MAP_FAILED {
+            bail!("Failed to mmap {:?}", driver_path);
+        }
+        // The slice is created for the region of memory that was just
+        // successfully mapped into the process address space so it will be
+        // accessible and not referenced from anywhere else.
+        let mmap_buf =
+            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+        // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
+        // encoded CBOR.
+        //
+        // BccHandover = {
+        //   1 : bstr .size 32,     ; CDI_Attest
+        //   2 : bstr .size 32,     ; CDI_Seal
+        //   3 : Bcc,               ; Certificate chain
+        // }
+        if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
+            || mmap_buf[36..39] != [0x02, 0x58, 0x20]
+            || mmap_buf[71] != 0x03
+        {
+            bail!("BccHandover format mismatch");
+        }
+        Ok(Self::Real {
+            driver_path: driver_path.to_path_buf(),
+            mmap_addr,
+            mmap_size,
+            cdi_attest: mmap_buf[4..36].try_into().unwrap(),
+            cdi_seal: mmap_buf[39..71].try_into().unwrap(),
+            bcc: &mmap_buf[72..],
+        })
+    }
+
+    pub fn get_sealing_key(&self, identifier: &[u8]) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data, rather than using the CDI
+        // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+        // input key material is already cryptographically strong.
+        let cdi_seal = match self {
+            Self::Real { cdi_seal, .. } => cdi_seal,
+            Self::Fake(fake) => &fake.cdi_seal,
+        };
+        let salt = &[];
+        let mut key = ZVec::new(32)?;
+        hkdf(&mut key, Md::sha256(), cdi_seal, salt, identifier)?;
+        Ok(key)
+    }
+
+    pub fn derive(
+        self,
+        code_hash: [u8; HASH_SIZE],
+        config_desc: &[u8],
+        authority_hash: [u8; HASH_SIZE],
+        debug: bool,
+        hidden: [u8; HIDDEN_SIZE],
+    ) -> Result<DiceContext> {
+        let input_values = InputValuesOwned::new(
+            code_hash,
+            Config::Descriptor(config_desc),
+            authority_hash,
+            None,
+            if debug { Mode::Debug } else { Mode::Normal },
+            hidden,
+        );
+        let (cdi_attest, cdi_seal, bcc) = match &self {
+            Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
+            Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
+        };
+        let (cdi_attest, cdi_seal, bcc) = OpenDiceCborContext::new()
+            .bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
+            .context("DICE derive from driver")?;
+        if let Self::Real { driver_path, .. } = &self {
+            // Writing to the device wipes the artifacts. The string is ignored by the driver but
+            // included for documentation.
+            fs::write(driver_path, "wipe")
+                .map_err(|err| Error::new(err).context("Wiping driver"))?;
+        }
+        Ok(DiceContext {
+            cdi_attest: cdi_attest[..].try_into().unwrap(),
+            cdi_seal: cdi_seal[..].try_into().unwrap(),
+            bcc,
+        })
+    }
+}
+
+impl Drop for DiceDriver<'_> {
+    fn drop(&mut self) {
+        if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
+            // All references to the mapped region have the same lifetime as self. Since self is
+            // being dropped, so are all the references to the mapped region meaning its safe to
+            // unmap.
+            let ret = unsafe { munmap(mmap_addr, mmap_size) };
+            if ret != 0 {
+                log::warn!("Failed to munmap ({})", ret);
+            }
+        }
+    }
+}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 358d88b..96e9360 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,15 +33,11 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
+use crate::dice::DiceDriver;
 use crate::ioutil;
 
-use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
 use anyhow::{anyhow, bail, Context, Result};
-use binder::wait_for_interface;
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
-use keystore2_crypto::ZVec;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
 use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
 use serde::{Deserialize, Serialize};
 use std::fs::{File, OpenOptions};
@@ -51,6 +47,9 @@
 /// Path to the instance disk inside the VM
 const INSTANCE_IMAGE_PATH: &str = "/dev/block/by-name/vm-instance";
 
+/// Identifier for the key used to seal the instance data.
+const INSTANCE_KEY_IDENTIFIER: &[u8] = b"microdroid_manager_key";
+
 /// Magic string in the instance disk header
 const DISK_HEADER_MAGIC: &str = "Android-VM-instance";
 
@@ -114,7 +113,7 @@
     /// Reads the identity data that was written by microdroid manager. The returned data is
     /// plaintext, although it is stored encrypted. In case when the partition for microdroid
     /// manager doesn't exist, which can happen if it's the first boot, `Ok(None)` is returned.
-    pub fn read_microdroid_data(&mut self) -> Result<Option<MicrodroidData>> {
+    pub fn read_microdroid_data(&mut self, dice: &DiceDriver) -> Result<Option<MicrodroidData>> {
         let (header, offset) = self.locate_microdroid_header()?;
         if header.is_none() {
             return Ok(None);
@@ -143,7 +142,7 @@
         self.file.read_exact(&mut header)?;
 
         // Decrypt and authenticate the data (along with the header).
-        let key = get_key()?;
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
         let plaintext =
             decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
 
@@ -153,7 +152,11 @@
 
     /// Writes identity data to the partition for microdroid manager. The partition is appended
     /// if it doesn't exist. The data is stored encrypted.
-    pub fn write_microdroid_data(&mut self, microdroid_data: &MicrodroidData) -> Result<()> {
+    pub fn write_microdroid_data(
+        &mut self,
+        microdroid_data: &MicrodroidData,
+        dice: &DiceDriver,
+    ) -> Result<()> {
         let (header, offset) = self.locate_microdroid_header()?;
 
         let data = serde_cbor::to_vec(microdroid_data)?;
@@ -185,7 +188,7 @@
         self.file.write_all(nonce.as_ref())?;
 
         // Then encrypt and sign the data.
-        let key = get_key()?;
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
         let mut tag = [0; AES_256_GCM_TAG_LENGTH];
         let ciphertext =
             encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
@@ -268,23 +271,6 @@
     Ok(ret)
 }
 
-/// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
-/// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> Result<ZVec> {
-    // Sealing CDI from the previous stage.
-    let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
-        .context("IDiceNode service not found")?;
-    let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
-    // Deterministically derive another key to use for encrypting the data, rather than using the
-    // CDI directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
-    // input key material is already cryptographically strong.
-    let salt = &[];
-    let info = b"microdroid_manager_key".as_ref();
-    let mut key = ZVec::new(32)?;
-    hkdf(&mut key, Md::sha256(), &bcc_handover.cdiSeal, salt, info)?;
-    Ok(key)
-}
-
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub struct MicrodroidData {
     pub salt: Vec<u8>, // Should be [u8; 64] but that isn't serializable.
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 1349ede..49dcacb 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -14,24 +14,22 @@
 
 //! Microdroid Manager
 
+mod dice;
 mod instance;
 mod ioutil;
 mod payload;
 mod vm_payload_service;
 
+use crate::dice::{DiceContext, DiceDriver};
 use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
 use crate::vm_payload_service::register_vm_payload_service;
-use android_hardware_security_dice::aidl::android::hardware::security::dice::{
-    Config::Config, InputValues::InputValues, Mode::Mode,
-};
-use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
     VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
 };
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify, V4Signature};
-use binder::{ProcessState, wait_for_interface, Strong};
+use binder::{ProcessState, Strong};
 use diced_utils::cbor::{encode_header, encode_number};
 use glob::glob;
 use itertools::sorted;
@@ -194,9 +192,10 @@
 }
 
 fn dice_derivation(
+    dice: DiceDriver,
     verified_data: &MicrodroidData,
     payload_metadata: &PayloadMetadata,
-) -> Result<()> {
+) -> Result<DiceContext> {
     // Calculate compound digests of code and authorities
     let mut code_hash_ctx = Sha512::new();
     let mut authority_hash_ctx = Sha512::new();
@@ -247,20 +246,8 @@
     let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
 
     // Send the details to diced
-    let diced =
-        wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
-            .context("IDiceMaintenance service not found")?;
-    diced
-        .demoteSelf(&[InputValues {
-            codeHash: code_hash,
-            config: Config { desc: config_desc },
-            authorityHash: authority_hash,
-            authorityDescriptor: None,
-            mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
-            hidden: verified_data.salt.clone().try_into().unwrap(),
-        }])
-        .context("IDiceMaintenance::demoteSelf failed")?;
-    Ok(())
+    let hidden = verified_data.salt.clone().try_into().unwrap();
+    dice.derive(code_hash, &config_desc, authority_hash, app_debuggable, hidden)
 }
 
 fn encode_tstr(tstr: &str, buffer: &mut Vec<u8>) -> Result<()> {
@@ -290,9 +277,11 @@
 
 fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
+    let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
-    let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
+    let saved_data =
+        instance.read_microdroid_data(&dice).context("Failed to read identity data")?;
 
     if is_strict_boot() {
         // Provisioning must happen on the first boot and never again.
@@ -333,7 +322,9 @@
         saved_data
     } else {
         info!("Saving verified data.");
-        instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
+        instance
+            .write_microdroid_data(&verified_data, &dice)
+            .context("Failed to write identity data")?;
         verified_data
     };
 
@@ -343,7 +334,7 @@
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    dice_derivation(&verified_data, &payload_metadata)?;
+    let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
 
     // Before reading a file from the APK, start zipfuse
     let noexec = false;
@@ -388,7 +379,7 @@
     }
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
-    register_vm_payload_service(service.clone())?;
+    register_vm_payload_service(service.clone(), dice)?;
     ProcessState::start_thread_pool();
     exec_task(task, service)
 }
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 0050a4c..70117c0 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,6 +14,7 @@
 
 //! Implementation of the AIDL interface `IVmPayloadService`.
 
+use crate::dice::DiceContext;
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -23,29 +24,43 @@
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
     virtual_machine_service: Strong<dyn IVirtualMachineService>,
+    dice: DiceContext,
 }
 
 impl IVmPayloadService for VmPayloadService {
     fn notifyPayloadReady(&self) -> binder::Result<()> {
         self.virtual_machine_service.notifyPayloadReady()
     }
+
+    fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.bcc.clone())
+    }
+
+    fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.cdi_attest.to_vec())
+    }
+
+    fn getDiceSealingCdi(&self) -> binder::Result<Vec<u8>> {
+        Ok(self.dice.cdi_seal.to_vec())
+    }
 }
 
 impl Interface for VmPayloadService {}
 
 impl VmPayloadService {
     /// Creates a new `VmPayloadService` instance from the `IVirtualMachineService` reference.
-    fn new(vm_service: Strong<dyn IVirtualMachineService>) -> Self {
-        Self { virtual_machine_service: vm_service }
+    fn new(vm_service: Strong<dyn IVirtualMachineService>, dice: DiceContext) -> Self {
+        Self { virtual_machine_service: vm_service, dice }
     }
 }
 
 /// Registers the `IVmPayloadService` service.
 pub(crate) fn register_vm_payload_service(
     vm_service: Strong<dyn IVirtualMachineService>,
+    dice: DiceContext,
 ) -> Result<()> {
     let vm_payload_binder = BnVmPayloadService::new_binder(
-        VmPayloadService::new(vm_service),
+        VmPayloadService::new(vm_service, dice),
         BinderFeatures::default(),
     );
     add_service(VM_PAYLOAD_SERVICE_NAME, vm_payload_binder.as_binder())