pvmfw: Move bcc under dice::chain

Prepare for heavily changing the file by first moving/renaming it.

Note that libpvmfw.dice.test now also includes dice::chain, as a result.

Test: m libpvmfw.dice.test pvmfw_bin
Change-Id: I9edb0fc8aa54a50c756de2ac3f9c82ce83f5e51a
diff --git a/guest/pvmfw/src/dice/chain.rs b/guest/pvmfw/src/dice/chain.rs
new file mode 100644
index 0000000..0f5b058
--- /dev/null
+++ b/guest/pvmfw/src/dice/chain.rs
@@ -0,0 +1,271 @@
+// 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.
+
+//! Code to inspect/manipulate the BCC (DICE Chain) we receive from our loader (the hypervisor).
+
+// TODO(b/279910232): Unify this, somehow, with the similar but different code in hwtrust.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::value::Value;
+use core::fmt;
+use core::mem::size_of;
+use coset::{iana, Algorithm, CborSerializable, CoseKey};
+use diced_open_dice::{BccHandover, Cdi, DiceArtifacts, DiceMode};
+use log::trace;
+
+type Result<T> = core::result::Result<T, BccError>;
+
+pub enum BccError {
+    CborDecodeError,
+    CborEncodeError,
+    CosetError(coset::CoseError),
+    DiceError(diced_open_dice::DiceError),
+    MalformedBcc(&'static str),
+    MissingBcc,
+}
+
+impl From<coset::CoseError> for BccError {
+    fn from(e: coset::CoseError) -> Self {
+        Self::CosetError(e)
+    }
+}
+
+impl fmt::Display for BccError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::CborDecodeError => write!(f, "Error parsing BCC CBOR"),
+            Self::CborEncodeError => write!(f, "Error encoding BCC CBOR"),
+            Self::CosetError(e) => write!(f, "Encountered an error with coset: {e}"),
+            Self::DiceError(e) => write!(f, "Dice error: {e:?}"),
+            Self::MalformedBcc(s) => {
+                write!(f, "BCC does not have the expected CBOR structure: {s}")
+            }
+            Self::MissingBcc => write!(f, "Missing BCC"),
+        }
+    }
+}
+
+/// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
+/// from the received BCC.
+#[cfg_attr(test, allow(dead_code))]
+pub fn truncate(bcc_handover: BccHandover) -> Result<Vec<u8>> {
+    // Note: The strings here are deliberately different from those used in a normal DICE handover
+    // because we want this to not be equivalent to any valid DICE derivation.
+    let cdi_seal = taint_cdi(bcc_handover.cdi_seal(), "TaintCdiSeal")?;
+    let cdi_attest = taint_cdi(bcc_handover.cdi_attest(), "TaintCdiAttest")?;
+
+    // BccHandover = {
+    //   1 : bstr .size 32,     ; CDI_Attest
+    //   2 : bstr .size 32,     ; CDI_Seal
+    //   ? 3 : Bcc,             ; Certificate chain
+    // }
+    let bcc_handover: Vec<(Value, Value)> =
+        vec![(1.into(), cdi_attest.as_slice().into()), (2.into(), cdi_seal.as_slice().into())];
+    cbor_util::serialize(&bcc_handover).map_err(|_| BccError::CborEncodeError)
+}
+
+#[cfg_attr(test, allow(dead_code))]
+fn taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi> {
+    // An arbitrary value generated randomly.
+    const SALT: [u8; 64] = [
+        0xdc, 0x0d, 0xe7, 0x40, 0x47, 0x9d, 0x71, 0xb8, 0x69, 0xd0, 0x71, 0x85, 0x27, 0x47, 0xf5,
+        0x65, 0x7f, 0x16, 0xfa, 0x59, 0x23, 0x19, 0x6a, 0x6b, 0x77, 0x41, 0x01, 0x45, 0x90, 0x3b,
+        0xfa, 0x68, 0xad, 0xe5, 0x26, 0x31, 0x5b, 0x40, 0x85, 0x71, 0x97, 0x12, 0xbd, 0x0b, 0x38,
+        0x5c, 0x98, 0xf3, 0x0e, 0xe1, 0x7c, 0x82, 0x23, 0xa4, 0x38, 0x38, 0x85, 0x84, 0x85, 0x0d,
+        0x02, 0x90, 0x60, 0xd3,
+    ];
+    let mut result = [0u8; size_of::<Cdi>()];
+    diced_open_dice::kdf(cdi.as_slice(), &SALT, info.as_bytes(), result.as_mut_slice())
+        .map_err(BccError::DiceError)?;
+    Ok(result)
+}
+
+/// Represents a (partially) decoded BCC DICE chain.
+pub struct Bcc {
+    is_debug_mode: bool,
+    leaf_subject_pubkey: PublicKey,
+}
+
+impl Bcc {
+    pub fn new(received_bcc: Option<&[u8]>) -> Result<Bcc> {
+        let received_bcc = received_bcc.unwrap_or(&[]);
+        if received_bcc.is_empty() {
+            return Err(BccError::MissingBcc);
+        }
+
+        // We don't attempt to fully validate the BCC (e.g. we don't check the signatures) - we
+        // have to trust our loader. But if it's invalid CBOR or otherwise clearly ill-formed,
+        // something is very wrong, so we fail.
+        let bcc_cbor =
+            cbor_util::deserialize(received_bcc).map_err(|_| BccError::CborDecodeError)?;
+
+        // Bcc = [
+        //   PubKeyEd25519 / PubKeyECDSA256, // DK_pub
+        //   + BccEntry,                     // Root -> leaf (KM_pub)
+        // ]
+        let bcc = match bcc_cbor {
+            Value::Array(v) if v.len() >= 2 => v,
+            _ => return Err(BccError::MalformedBcc("Invalid top level value")),
+        };
+        // Decode all the DICE payloads to make sure they are well-formed.
+        let payloads = bcc
+            .into_iter()
+            .skip(1)
+            .map(|v| BccEntry::new(v).payload())
+            .collect::<Result<Vec<_>>>()?;
+
+        let is_debug_mode = is_any_payload_debug_mode(&payloads)?;
+        // Safe to unwrap because we checked the length above.
+        let leaf_subject_pubkey = payloads.last().unwrap().subject_public_key()?;
+        Ok(Self { is_debug_mode, leaf_subject_pubkey })
+    }
+
+    /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
+    /// secure).
+    pub fn is_debug_mode(&self) -> bool {
+        self.is_debug_mode
+    }
+
+    pub fn leaf_subject_pubkey(&self) -> &PublicKey {
+        &self.leaf_subject_pubkey
+    }
+}
+
+fn is_any_payload_debug_mode(payloads: &[BccPayload]) -> Result<bool> {
+    // Check if any payload in the chain is marked as Debug mode, which means the device is not
+    // secure. (Normal means it is a secure boot, for that stage at least; we ignore recovery
+    // & not configured /invalid values, since it's not clear what they would mean in this
+    // context.)
+    for payload in payloads {
+        if payload.is_debug_mode()? {
+            return Ok(true);
+        }
+    }
+    Ok(false)
+}
+
+#[repr(transparent)]
+struct BccEntry(Value);
+
+#[repr(transparent)]
+struct BccPayload(Value);
+
+#[derive(Debug, Clone)]
+pub struct PublicKey {
+    /// The COSE key algorithm for the public key, representing the value of the `alg`
+    /// field in the COSE key format of the public key. See RFC 8152, section 7 for details.
+    pub cose_alg: iana::Algorithm,
+}
+
+impl BccEntry {
+    pub fn new(entry: Value) -> Self {
+        Self(entry)
+    }
+
+    pub fn payload(&self) -> Result<BccPayload> {
+        // BccEntry = [                                  // COSE_Sign1 (untagged)
+        //     protected : bstr .cbor {
+        //         1 : AlgorithmEdDSA / AlgorithmES256,  // Algorithm
+        //     },
+        //     unprotected: {},
+        //     payload: bstr .cbor BccPayload,
+        //     signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
+        //                     // ECDSA(SigningKey, bstr .cbor BccEntryInput)
+        //     // See RFC 8032 for details of how to encode the signature value for Ed25519.
+        // ]
+        let payload =
+            self.payload_bytes().ok_or(BccError::MalformedBcc("Invalid payload in BccEntry"))?;
+        let payload = cbor_util::deserialize(payload).map_err(|_| BccError::CborDecodeError)?;
+        trace!("Bcc payload: {payload:?}");
+        Ok(BccPayload(payload))
+    }
+
+    fn payload_bytes(&self) -> Option<&Vec<u8>> {
+        let entry = self.0.as_array()?;
+        if entry.len() != 4 {
+            return None;
+        };
+        entry[2].as_bytes()
+    }
+}
+
+const KEY_MODE: i32 = -4670551;
+const MODE_DEBUG: u8 = DiceMode::kDiceModeDebug as u8;
+const SUBJECT_PUBLIC_KEY: i32 = -4670552;
+
+impl BccPayload {
+    pub fn is_debug_mode(&self) -> Result<bool> {
+        // BccPayload = {                     // CWT
+        // ...
+        //     ? -4670551 : bstr,             // Mode
+        // ...
+        // }
+
+        let Some(value) = self.value_from_key(KEY_MODE) else { return Ok(false) };
+
+        // 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 Unknown, according to the Open
+        // Profile for DICE spec.
+        let mode = if let Some(bytes) = value.as_bytes() {
+            if bytes.len() != 1 {
+                return Err(BccError::MalformedBcc("Invalid mode bstr"));
+            }
+            bytes[0].into()
+        } else {
+            value.as_integer().ok_or(BccError::MalformedBcc("Invalid type for mode"))?
+        };
+        Ok(mode == MODE_DEBUG.into())
+    }
+
+    fn subject_public_key(&self) -> Result<PublicKey> {
+        // BccPayload = {                             ; CWT [RFC8392]
+        // ...
+        //   -4670552 : bstr .cbor PubKeyEd25519 /
+        //              bstr .cbor PubKeyECDSA256 /
+        //              bstr .cbor PubKeyECDSA384,    ; Subject Public Key
+        // ...
+        // }
+        self.value_from_key(SUBJECT_PUBLIC_KEY)
+            .ok_or(BccError::MalformedBcc("Subject public key missing"))?
+            .as_bytes()
+            .ok_or(BccError::MalformedBcc("Subject public key is not a byte string"))
+            .and_then(|v| PublicKey::from_slice(v))
+    }
+
+    fn value_from_key(&self, key: i32) -> Option<&Value> {
+        // BccPayload is just a map; we only use integral keys, but in general it's legitimate
+        // for other things to be present, or for the key we care about not to be present.
+        // Ciborium represents the map as a Vec, preserving order (and allowing duplicate keys,
+        // which we ignore) but preventing fast lookup.
+        let payload = self.0.as_map()?;
+        for (k, v) in payload {
+            if k.as_integer() == Some(key.into()) {
+                return Some(v);
+            }
+        }
+        None
+    }
+}
+
+impl PublicKey {
+    fn from_slice(slice: &[u8]) -> Result<Self> {
+        let key = CoseKey::from_slice(slice)?;
+        let Some(Algorithm::Assigned(cose_alg)) = key.alg else {
+            return Err(BccError::MalformedBcc("Invalid algorithm in public key"));
+        };
+        Ok(Self { cose_alg })
+    }
+}
diff --git a/guest/pvmfw/src/dice/mod.rs b/guest/pvmfw/src/dice/mod.rs
new file mode 100644
index 0000000..94348a5
--- /dev/null
+++ b/guest/pvmfw/src/dice/mod.rs
@@ -0,0 +1,456 @@
+// 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.
+
+//! Support for DICE derivation and BCC generation.
+extern crate alloc;
+
+pub(crate) mod chain;
+
+use alloc::format;
+use alloc::string::String;
+use alloc::vec::Vec;
+pub use chain::Bcc;
+use ciborium::cbor;
+use ciborium::Value;
+use core::mem::size_of;
+use diced_open_dice::{
+    bcc_handover_main_flow, hash, Config, DiceContext, DiceMode, Hash, InputValues, HIDDEN_SIZE,
+};
+use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
+use zerocopy::Immutable;
+use zerocopy::IntoBytes;
+use zerocopy::KnownLayout;
+
+// pVM firmware (like other VM components) is expected to populate some fields in DICE
+// Configuration Descriptor. See dice_for_avf_guest.cddl
+const COMPONENT_NAME_KEY: i64 = -70002;
+const SECURITY_VERSION_KEY: i64 = -70005;
+const RKP_VM_MARKER_KEY: i64 = -70006;
+const INSTANCE_HASH_KEY: i64 = -71003;
+
+#[derive(Debug)]
+pub enum Error {
+    /// Error in CBOR operations
+    #[allow(dead_code)]
+    CborError(ciborium::value::Error),
+    /// Error in DICE operations
+    #[allow(dead_code)]
+    DiceError(diced_open_dice::DiceError),
+}
+
+impl From<ciborium::value::Error> for Error {
+    fn from(e: ciborium::value::Error) -> Self {
+        Self::CborError(e)
+    }
+}
+
+impl From<diced_open_dice::DiceError> for Error {
+    fn from(e: diced_open_dice::DiceError) -> Self {
+        Self::DiceError(e)
+    }
+}
+
+// DICE in pvmfw result type.
+type Result<T> = core::result::Result<T, Error>;
+
+fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
+    match debug_level {
+        DebugLevel::None => DiceMode::kDiceModeNormal,
+        DebugLevel::Full => DiceMode::kDiceModeDebug,
+    }
+}
+
+fn to_dice_hash(verified_boot_data: &VerifiedBootData) -> Result<Hash> {
+    let mut digests = [0u8; size_of::<Digest>() * 2];
+    digests[..size_of::<Digest>()].copy_from_slice(&verified_boot_data.kernel_digest);
+    if let Some(initrd_digest) = verified_boot_data.initrd_digest {
+        digests[size_of::<Digest>()..].copy_from_slice(&initrd_digest);
+    }
+    Ok(hash(&digests)?)
+}
+
+#[derive(Clone)]
+pub struct PartialInputs {
+    pub code_hash: Hash,
+    pub auth_hash: Hash,
+    pub mode: DiceMode,
+    pub security_version: u64,
+    pub rkp_vm_marker: bool,
+    component_name: String,
+}
+
+impl PartialInputs {
+    pub fn new(data: &VerifiedBootData) -> Result<Self> {
+        let code_hash = to_dice_hash(data)?;
+        let auth_hash = hash(data.public_key)?;
+        let mode = to_dice_mode(data.debug_level);
+        let component_name = data.name.clone().unwrap_or(String::from("vm_entry"));
+        // We use rollback_index from vbmeta as the security_version field in dice certificate.
+        let security_version = data.rollback_index;
+        let rkp_vm_marker = data.has_capability(Capability::RemoteAttest)
+            || data.has_capability(Capability::TrustySecurityVm);
+
+        Ok(Self { code_hash, auth_hash, mode, security_version, rkp_vm_marker, component_name })
+    }
+
+    pub fn write_next_bcc(
+        self,
+        current_bcc_handover: &[u8],
+        salt: &[u8; HIDDEN_SIZE],
+        instance_hash: Option<Hash>,
+        deferred_rollback_protection: bool,
+        next_bcc: &mut [u8],
+        context: DiceContext,
+    ) -> Result<()> {
+        let config = self
+            .generate_config_descriptor(instance_hash)
+            .map_err(|_| diced_open_dice::DiceError::InvalidInput)?;
+
+        let dice_inputs = InputValues::new(
+            self.code_hash,
+            Config::Descriptor(&config),
+            self.auth_hash,
+            self.mode,
+            self.make_hidden(salt, deferred_rollback_protection)?,
+        );
+        let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc, context)?;
+        Ok(())
+    }
+
+    fn make_hidden(
+        &self,
+        salt: &[u8; HIDDEN_SIZE],
+        deferred_rollback_protection: bool,
+    ) -> diced_open_dice::Result<[u8; HIDDEN_SIZE]> {
+        // We want to make sure we get a different sealing CDI for:
+        // - VMs with different salt values
+        // - An RKP VM and any other VM (regardless of salt)
+        // - depending on whether rollback protection has been deferred to payload. This ensures the
+        //   adversary cannot leak the secrets by using old images & setting
+        //   `deferred_rollback_protection` to true.
+        // The hidden input for DICE affects the sealing CDI (but the values in the config
+        // descriptor do not).
+        // Since the hidden input has to be a fixed size, create it as a hash of the values we
+        // want included.
+        #[derive(Immutable, IntoBytes, KnownLayout)]
+        #[repr(C, packed)]
+        struct HiddenInput {
+            rkp_vm_marker: bool,
+            salt: [u8; HIDDEN_SIZE],
+            deferred_rollback_protection: bool,
+        }
+        hash(
+            HiddenInput {
+                rkp_vm_marker: self.rkp_vm_marker,
+                salt: *salt,
+                deferred_rollback_protection,
+            }
+            .as_bytes(),
+        )
+    }
+
+    fn generate_config_descriptor(&self, instance_hash: Option<Hash>) -> Result<Vec<u8>> {
+        let mut config = Vec::with_capacity(4);
+        config.push((cbor!(COMPONENT_NAME_KEY)?, cbor!(self.component_name.as_str())?));
+        config.push((cbor!(SECURITY_VERSION_KEY)?, cbor!(self.security_version)?));
+        if self.rkp_vm_marker {
+            config.push((cbor!(RKP_VM_MARKER_KEY)?, Value::Null))
+        }
+        if let Some(instance_hash) = instance_hash {
+            config.push((cbor!(INSTANCE_HASH_KEY)?, Value::from(instance_hash.as_slice())));
+        }
+        let config = Value::Map(config);
+        Ok(cbor_util::serialize(&config).map_err(|e| {
+            ciborium::value::Error::Custom(format!("Error in serialization: {e:?}"))
+        })?)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        Hash, PartialInputs, COMPONENT_NAME_KEY, INSTANCE_HASH_KEY, RKP_VM_MARKER_KEY,
+        SECURITY_VERSION_KEY,
+    };
+    use ciborium::Value;
+    use diced_open_dice::DiceArtifacts;
+    use diced_open_dice::DiceContext;
+    use diced_open_dice::DiceMode;
+    use diced_open_dice::KeyAlgorithm;
+    use diced_open_dice::HIDDEN_SIZE;
+    use diced_sample_inputs::make_sample_bcc_and_cdis;
+    use hwtrust::{dice, session::Session};
+    use pvmfw_avb::Capability;
+    use pvmfw_avb::DebugLevel;
+    use pvmfw_avb::Digest;
+    use pvmfw_avb::VerifiedBootData;
+    use std::collections::HashMap;
+    use std::mem::size_of;
+    use std::vec;
+
+    const COMPONENT_VERSION_KEY: i64 = -70003;
+    const RESETTABLE_KEY: i64 = -70004;
+    const BASE_VB_DATA: VerifiedBootData = VerifiedBootData {
+        debug_level: DebugLevel::None,
+        kernel_digest: [1u8; size_of::<Digest>()],
+        initrd_digest: Some([2u8; size_of::<Digest>()]),
+        public_key: b"public key",
+        name: None,
+        capabilities: vec![],
+        rollback_index: 42,
+        page_size: None,
+    };
+    const HASH: Hash = *b"sixtyfourbyteslongsentencearerarebutletsgiveitatrycantbethathard";
+
+    #[test]
+    fn base_data_conversion() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeNormal);
+        assert_eq!(inputs.security_version, 42);
+        assert!(!inputs.rkp_vm_marker);
+
+        // TODO(b/313608219): Consider checks for code_hash and possibly auth_hash.
+    }
+
+    #[test]
+    fn debuggable_conversion() {
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert_eq!(inputs.mode, DiceMode::kDiceModeDebug);
+    }
+
+    #[test]
+    fn rkp_vm_conversion() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+
+        assert!(inputs.rkp_vm_marker);
+    }
+
+    #[test]
+    fn base_config_descriptor() {
+        let vb_data = BASE_VB_DATA;
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs, None);
+
+        assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+        assert_eq!(config_map.get(&COMPONENT_VERSION_KEY), None);
+        assert_eq!(config_map.get(&RESETTABLE_KEY), None);
+        assert_eq!(config_map.get(&SECURITY_VERSION_KEY).unwrap().as_integer().unwrap(), 42.into());
+        assert_eq!(config_map.get(&RKP_VM_MARKER_KEY), None);
+    }
+
+    #[test]
+    fn rkp_vm_config_descriptor_has_rkp_vm_marker_and_component_name() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs, Some(HASH));
+
+        assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+        assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+    }
+
+    #[test]
+    fn security_vm_config_descriptor_has_rkp_vm_marker() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::TrustySecurityVm], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs, Some(HASH));
+
+        assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+    }
+
+    #[test]
+    fn config_descriptor_with_instance_hash() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs, Some(HASH));
+        assert_eq!(*config_map.get(&INSTANCE_HASH_KEY).unwrap(), Value::from(HASH.as_slice()));
+    }
+
+    #[test]
+    fn config_descriptor_without_instance_hash() {
+        let vb_data =
+            VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let config_map = decode_config_descriptor(&inputs, None);
+        assert!(!config_map.contains_key(&INSTANCE_HASH_KEY));
+    }
+
+    fn decode_config_descriptor(
+        inputs: &PartialInputs,
+        instance_hash: Option<Hash>,
+    ) -> HashMap<i64, Value> {
+        let config_descriptor = inputs.generate_config_descriptor(instance_hash).unwrap();
+
+        let cbor_map =
+            cbor_util::deserialize::<Value>(&config_descriptor).unwrap().into_map().unwrap();
+
+        cbor_map
+            .into_iter()
+            .map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
+            .collect()
+    }
+
+    #[test]
+    fn changing_deferred_rpb_changes_secrets() {
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let mut buffer_without_defer = [0; 4096];
+        let mut buffer_with_defer = [0; 4096];
+        let mut buffer_without_defer_retry = [0; 4096];
+        let context = DiceContext {
+            authority_algorithm: KeyAlgorithm::Ed25519,
+            subject_algorithm: KeyAlgorithm::Ed25519,
+        };
+
+        let sample_dice_input: &[u8] = &[
+            0xa3, // CDI attest
+            0x01, 0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CDI seal
+            0x02, 0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DICE chain
+            0x03, 0x82, 0xa6, 0x01, 0x02, 0x03, 0x27, 0x04, 0x02, 0x20, 0x01, 0x21, 0x40, 0x22,
+            0x40, 0x84, 0x40, 0xa0, 0x40, 0x40,
+            // 8-bytes of trailing data that aren't part of the DICE chain.
+            0x84, 0x41, 0x55, 0xa0, 0x42, 0x11, 0x22, 0x40,
+        ];
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                sample_dice_input,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                false,
+                &mut buffer_without_defer,
+                context.clone(),
+            )
+            .unwrap();
+        let bcc_handover1 = diced_open_dice::bcc_handover_parse(&buffer_without_defer).unwrap();
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                sample_dice_input,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer_with_defer,
+                context.clone(),
+            )
+            .unwrap();
+        let bcc_handover2 = diced_open_dice::bcc_handover_parse(&buffer_with_defer).unwrap();
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                sample_dice_input,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                false,
+                &mut buffer_without_defer_retry,
+                context.clone(),
+            )
+            .unwrap();
+        let bcc_handover3 =
+            diced_open_dice::bcc_handover_parse(&buffer_without_defer_retry).unwrap();
+
+        assert_ne!(bcc_handover1.cdi_seal(), bcc_handover2.cdi_seal());
+        assert_eq!(bcc_handover1.cdi_seal(), bcc_handover3.cdi_seal());
+    }
+
+    #[test]
+    fn dice_derivation_with_different_algorithms_is_valid() {
+        let dice_artifacts = make_sample_bcc_and_cdis().unwrap();
+        let bcc_handover0_bytes = to_bcc_handover(&dice_artifacts);
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let mut buffer = [0; 4096];
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover0_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::Ed25519,
+                    subject_algorithm: KeyAlgorithm::EcdsaP256,
+                },
+            )
+            .expect("Failed to derive Ed25519 -> EcdsaP256 BCC");
+        let bcc_handover1 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+        let bcc_handover1_bytes = to_bcc_handover(&bcc_handover1);
+        buffer.fill(0);
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover1_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::EcdsaP256,
+                    subject_algorithm: KeyAlgorithm::EcdsaP384,
+                },
+            )
+            .expect("Failed to derive EcdsaP256 -> EcdsaP384 BCC");
+        let bcc_handover2 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+        let bcc_handover2_bytes = to_bcc_handover(&bcc_handover2);
+        buffer.fill(0);
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover2_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::EcdsaP384,
+                    subject_algorithm: KeyAlgorithm::Ed25519,
+                },
+            )
+            .expect("Failed to derive EcdsaP384 -> Ed25519 BCC");
+        let bcc_handover3 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+
+        let mut session = Session::default();
+        session.set_allow_any_mode(true);
+        let _chain = dice::Chain::from_cbor(&session, bcc_handover3.bcc().unwrap()).unwrap();
+    }
+
+    fn to_bcc_handover(dice_artifacts: &dyn DiceArtifacts) -> Vec<u8> {
+        let dice_chain = cbor_util::deserialize::<Value>(dice_artifacts.bcc().unwrap()).unwrap();
+        let bcc_handover = Value::Map(vec![
+            (Value::Integer(1.into()), Value::Bytes(dice_artifacts.cdi_attest().to_vec())),
+            (Value::Integer(2.into()), Value::Bytes(dice_artifacts.cdi_seal().to_vec())),
+            (Value::Integer(3.into()), dice_chain),
+        ]);
+        cbor_util::serialize(&bcc_handover).unwrap()
+    }
+}