use crate::{
error::PqRascvError,
measurement::{Measurements, PcrBank, RoT},
};
use sha3::{Digest, Sha3_256};
const DICE_ATTEST_LABEL: &[u8] = b"DICE-attest";
pub struct DiceRoT<'a> {
cdi: [u8; 32],
firmware: &'a [u8],
ai_model: Option<&'a [u8]>,
event_counter: u64,
}
impl<'a> DiceRoT<'a> {
#[must_use]
pub fn new(
cdi: [u8; 32],
firmware: &'a [u8],
ai_model: Option<&'a [u8]>,
event_counter: u64,
) -> Self {
Self { cdi, firmware, ai_model, event_counter }
}
}
impl<'a> RoT for DiceRoT<'a> {
fn measure(&self) -> Result<Measurements, PqRascvError> {
let firmware_hash: [u8; 32] = {
let mut h = Sha3_256::new();
h.update(self.firmware);
h.finalize().into()
};
let ai_model_hash: [u8; 32] = match self.ai_model {
Some(model) => {
let mut h = Sha3_256::new();
h.update(model);
h.finalize().into()
}
None => [0u8; 32],
};
let cdi_attestation: [u8; 32] = {
let mut h = Sha3_256::new();
h.update(self.cdi);
h.update(DICE_ATTEST_LABEL);
h.update(firmware_hash);
h.finalize().into()
};
let mut pcrs = PcrBank::default();
pcrs.0[0] = cdi_attestation;
Ok(Measurements {
pcrs,
firmware_hash,
ai_model_hash,
event_counter: self.event_counter,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
static FW_A: &[u8] = b"firmware-v1.0.0";
static FW_B: &[u8] = b"firmware-v1.0.1";
const CDI: [u8; 32] = [0x42u8; 32];
#[test]
fn measure_is_deterministic() {
let rot = DiceRoT::new(CDI, FW_A, None, 0);
assert_eq!(rot.measure().unwrap(), rot.measure().unwrap());
}
#[test]
fn firmware_change_changes_hash_and_pcr0() {
let m_a = DiceRoT::new(CDI, FW_A, None, 0).measure().unwrap();
let m_b = DiceRoT::new(CDI, FW_B, None, 0).measure().unwrap();
assert_ne!(m_a.firmware_hash, m_b.firmware_hash,
"different firmware must produce different firmware_hash");
assert_ne!(m_a.pcrs.0[0], m_b.pcrs.0[0],
"different firmware must produce different CDI attestation in PCR 0");
}
#[test]
fn cdi_change_changes_pcr0_but_not_firmware_hash() {
let cdi_alt = [0xffu8; 32];
let m_a = DiceRoT::new(CDI, FW_A, None, 0).measure().unwrap();
let m_b = DiceRoT::new(cdi_alt, FW_A, None, 0).measure().unwrap();
assert_eq!(m_a.firmware_hash, m_b.firmware_hash,
"same firmware must produce the same firmware_hash regardless of CDI");
assert_ne!(m_a.pcrs.0[0], m_b.pcrs.0[0],
"different CDI must produce different CDI attestation in PCR 0");
}
#[test]
fn pcr0_is_not_zero() {
let m = DiceRoT::new(CDI, FW_A, None, 0).measure().unwrap();
assert_ne!(m.pcrs.0[0], [0u8; 32]);
}
#[test]
fn pcr1_through_7_are_zero() {
let m = DiceRoT::new(CDI, FW_A, None, 0).measure().unwrap();
for i in 1..8 {
assert_eq!(m.pcrs.0[i], [0u8; 32], "PCR {i} should be zero for single-layer DICE");
}
}
#[test]
fn ai_model_hash_zero_when_absent() {
let m = DiceRoT::new(CDI, FW_A, None, 0).measure().unwrap();
assert_eq!(m.ai_model_hash, [0u8; 32]);
}
#[test]
fn ai_model_hash_set_when_present() {
let m = DiceRoT::new(CDI, FW_A, Some(b"model-weights"), 0).measure().unwrap();
assert_ne!(m.ai_model_hash, [0u8; 32]);
}
#[test]
fn event_counter_propagated() {
let m = DiceRoT::new(CDI, FW_A, None, 99).measure().unwrap();
assert_eq!(m.event_counter, 99);
}
}