Skip to content

Commit

Permalink
Add DPE key verification tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
zhalvorsen committed Aug 22, 2024
1 parent c4f548a commit 5f31f01
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 12 deletions.
132 changes: 131 additions & 1 deletion test/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
/// DO NOT REFACTOR THIS FILE TO RE-USE CODE FROM OTHER PARTS OF CALIPTRA
use caliptra_hw_model_types::SecurityState;
use caliptra_image_types::ImageManifest;
use caliptra_runtime::TciMeasurement;
use dpe::tci::TciNodeData;
use openssl::{
pkey::{PKey, Public},
sha::{sha256, sha384},
sha::{sha256, sha384, Sha384},
};
use zerocopy::{transmute, AsBytes};

Expand Down Expand Up @@ -498,6 +500,134 @@ impl RtAliasKey {
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DpeAliasKey {
pub cdi: [u32; 12],

// The DPE alias private key as stored in the key-vault
pub priv_key: [u32; 12],
}
impl DpeAliasKey {
pub const PAUSER_COUNT: usize = 5;

pub fn derive(
pcr_rt_current: &PcrRtCurrentInput,
rt_key: &RtAliasKey,
measurement: &[u8; 48],
tci_type: &[u8; 4],
label: &[u8],
mbox_valid_pauser: &[u32; Self::PAUSER_COUNT],
mbox_pauser_lock: &[bool; Self::PAUSER_COUNT],
) -> Self {
// Get all of the TCIs
let tcis = [
Self::get_rt_journey_tci(pcr_rt_current),
Self::get_valid_pauser_tci(mbox_valid_pauser, mbox_pauser_lock),
Self::get_measurement_tci(measurement, tci_type),
];

// Derive the CDI's measurement. Note they are added to the hash in the reverse order
// because DPE starts from the leaf and goes to the root.
let mut hash = Sha384::new();
for tci in tcis.iter().rev() {
hash.update(tci.as_bytes());
}
let measurement = hash.finish();

// Derive the CDI's context
let mut hash = Sha384::new();
hash.update(&measurement);
hash.update(b"DPE");
let context = hash.finish();

// Derive the CDI
let mut cdi: [u32; 12] = transmute!(hmac384_kdf(
swap_word_bytes(&rt_key.cdi).as_bytes(),
b"derive_cdi",
Some(&context),
));
swap_word_bytes_inplace(&mut cdi);

// Derive the seed
let mut priv_key_seed: [u32; 12] = transmute!(hmac384_kdf(
swap_word_bytes(&cdi).as_bytes(),
label,
Some(b"ECC")
));
swap_word_bytes_inplace(&mut priv_key_seed);

// Derive the private key
let mut priv_key: [u32; 12] = transmute!(hmac384_drbg_keygen(
swap_word_bytes(&priv_key_seed).as_bytes(),
swap_word_bytes(&ECDSA_KEYGEN_NONCE).as_bytes()
));
swap_word_bytes_inplace(&mut priv_key);
Self { priv_key, cdi }
}

pub fn derive_public_key(&self) -> PKey<Public> {
derive_ecdsa_key(
swap_word_bytes(&self.priv_key)
.as_bytes()
.try_into()
.unwrap(),
)
}

fn get_rt_journey_tci(pcr_rt_current: &PcrRtCurrentInput) -> TciNodeData {
let current = swap_word_bytes(&PcrRtCurrent::derive(pcr_rt_current).0);
let cumulative = Self::extend_from_zeroes(current.as_bytes());
TciNodeData {
tci_type: u32::from_be_bytes(*b"RTJM"),
tci_cumulative: TciMeasurement(cumulative),
tci_current: TciMeasurement(current.as_bytes().try_into().unwrap()),
locality: u32::MAX,
}
}

fn get_valid_pauser_tci(
mbox_valid_pauser: &[u32; Self::PAUSER_COUNT],
mbox_pauser_lock: &[bool; Self::PAUSER_COUNT],
) -> TciNodeData {
// Hash all of the locked PAUSERs
let mut hash = Sha384::new();
for (lock, valid_pauser) in mbox_pauser_lock.iter().zip(mbox_valid_pauser) {
if *lock {
hash.update(valid_pauser.as_bytes());
}
}
let valid_pauser_hash_bytes = hash.finish();

// Swap the endianness
let mut valid_pauser_hash_words = [0u32; 12];
valid_pauser_hash_words
.as_bytes_mut()
.copy_from_slice(&valid_pauser_hash_bytes);
swap_word_bytes_inplace(&mut valid_pauser_hash_words);

let cumulative = Self::extend_from_zeroes(valid_pauser_hash_words.as_bytes());
TciNodeData {
tci_type: u32::from_be_bytes(*b"MBVP"),
tci_cumulative: TciMeasurement(cumulative),
tci_current: TciMeasurement(valid_pauser_hash_words.as_bytes().try_into().unwrap()),
locality: 1,
}
}

fn get_measurement_tci(measurement: &[u8; 48], tci_type: &[u8; 4]) -> TciNodeData {
TciNodeData {
tci_type: u32::from_be_bytes(*tci_type),
tci_cumulative: TciMeasurement(Self::extend_from_zeroes(measurement)),
tci_current: TciMeasurement(*measurement),
locality: 1,
}
}

fn extend_from_zeroes(data: &[u8]) -> [u8; 48] {
sha384(&[&[0u8; 48], data].concat())
}
}

#[test]
fn test_derive_fmc_alias_key() {
let fmc_alias_key = FmcAliasKey::derive(
Expand Down
117 changes: 106 additions & 11 deletions test/tests/caliptra_integration_tests/smoke_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@
use caliptra_builder::firmware::{APP_WITH_UART, FMC_WITH_UART};
use caliptra_builder::{firmware, ImageOptions};
use caliptra_common::mailbox_api::{
GetFmcAliasCertReq, GetLdevCertReq, GetRtAliasCertReq, ResponseVarSize,
CommandId, GetFmcAliasCertReq, GetLdevCertReq, GetRtAliasCertReq, InvokeDpeReq, InvokeDpeResp,
MailboxReq, MailboxReqHeader, ResponseVarSize, StashMeasurementReq,
};
use caliptra_common::RomBootStatus;
use caliptra_drivers::CaliptraError;
use caliptra_hw_model::{BootParams, HwModel, InitParams, SecurityState};
use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, SecurityState};
use caliptra_hw_model_types::{DeviceLifecycle, Fuses, RandomEtrngResponses, RandomNibbles};
use caliptra_test::derive::{PcrRtCurrentInput, RtAliasKey};
use caliptra_test::derive::{DpeAliasKey, PcrRtCurrentInput, RtAliasKey};
use caliptra_test::{derive, redact_cert, run_test, RedactOpts, UnwrapSingle};
use caliptra_test::{
derive::{DoeInput, DoeOutput, FmcAliasKey, IDevId, LDevId, Pcr0, Pcr0Input},
swap_word_bytes, swap_word_bytes_inplace,
x509::{DiceFwid, DiceTcbInfo},
};
use dpe::commands::{CertifyKeyCmd, Command};
use dpe::commands::{CertifyKeyFlags, CommandHdr};
use dpe::context::ContextHandle;
use dpe::response::CertifyKeyResp;
use openssl::nid::Nid;
use openssl::sha::{sha384, Sha384};
use rand::rngs::StdRng;
use rand::SeedableRng;
use regex::Regex;
use std::mem;
use zerocopy::AsBytes;
use zerocopy::{AsBytes, FromBytes};

#[track_caller]
fn assert_output_contains(haystack: &str, needle: &str) {
Expand Down Expand Up @@ -429,13 +434,13 @@ fn smoke_test() {
"Manifest digest is {:02x?}",
image.manifest.runtime.digest.as_bytes()
);
let expected_rt_alias_key = RtAliasKey::derive(
&PcrRtCurrentInput {
runtime_digest: image.manifest.runtime.digest,
manifest: image.manifest,
},
&expected_fmc_alias_key,
);

let pcr_rt_input = PcrRtCurrentInput {
runtime_digest: image.manifest.runtime.digest,
manifest: image.manifest,
};

let expected_rt_alias_key = RtAliasKey::derive(&pcr_rt_input, &expected_fmc_alias_key);

// Check that the rt-alias key has the rt measurements input above mixed into it
// If a firmware change causes this assertion to fail, it is likely that the
Expand Down Expand Up @@ -550,6 +555,59 @@ fn smoke_test() {
.read()
.mbox_ecc_unc());

let measurement: [u8; 48] = [0xdeadbeef_u32; 12].as_bytes().try_into().unwrap();
let tci_type = [0xABu8; 4];
let stash_req = StashMeasurementReq {
measurement,
hdr: MailboxReqHeader { chksum: 0 },
metadata: tci_type,
context: [0xCD; 48],
svn: 0xEF01,
};
hw.mailbox_execute_req(stash_req).unwrap();

let mut cmd = CertifyKeyCmd {
handle: ContextHandle::default(),
label: [
48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27,
26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
3, 2, 1,
],
flags: CertifyKeyFlags::empty(),
format: CertifyKeyCmd::FORMAT_X509,
};

let cert_key_response = execute_certify_key_cmd(&mut hw, &mut cmd);
let dpe_cert = openssl::x509::X509::from_der(
&cert_key_response.cert[..cert_key_response.cert_size as usize],
)
.unwrap();
let dpe_cert_txt = String::from_utf8(rt_alias_cert.to_text().unwrap()).unwrap();

// Get the MBOX PAUSER settings
let mbox_valid_pauser: [u32; DpeAliasKey::PAUSER_COUNT] =
hw.soc_ifc().cptra_mbox_valid_pauser().read();
let mut mbox_pauser_lock: [bool; DpeAliasKey::PAUSER_COUNT] = Default::default();
for (i, lock) in mbox_pauser_lock.iter_mut().enumerate() {
*lock = hw.soc_ifc().cptra_mbox_pauser_lock().at(i).read().lock();
}

let expected_dpe_alias_key = DpeAliasKey::derive(
&pcr_rt_input,
&expected_rt_alias_key,
&measurement,
&tci_type,
&cmd.label,
&mbox_valid_pauser,
&mbox_pauser_lock,
);

assert!(expected_dpe_alias_key
.derive_public_key()
.public_eq(&dpe_cert.public_key().unwrap()));

println!("dpe cert: {dpe_cert_txt}");

// Hitlessly update to the no-uart runtime firmware

let image2 = caliptra_builder::build_and_sign_image(
Expand Down Expand Up @@ -857,3 +915,40 @@ fn test_fmc_wdt_timeout() {
// error_internal_intr_r must be 0b01000000 since the error_wdt_timer1_timeout_sts bit must be set
assert_eq!(error_internal_intr_r, 0b01000000);
}

fn execute_certify_key_cmd(model: &mut DefaultHwModel, cmd: &mut CertifyKeyCmd) -> CertifyKeyResp {
// Put the header and data into a unified buffer
let mut cmd_data: [u8; 512] = [0u8; InvokeDpeReq::DATA_MAX_SIZE];
let dpe_cmd_id = Command::CERTIFY_KEY;
let cmd_hdr = CommandHdr::new_for_test(dpe_cmd_id);
let cmd_hdr_buf = cmd_hdr.as_bytes();
cmd_data[..cmd_hdr_buf.len()].copy_from_slice(cmd_hdr_buf);
let cmd_buf = cmd.as_bytes();
cmd_data[cmd_hdr_buf.len()..cmd_hdr_buf.len() + cmd_buf.len()].copy_from_slice(cmd_buf);

let mut mbox_cmd = MailboxReq::InvokeDpeCommand(InvokeDpeReq {
hdr: MailboxReqHeader { chksum: 0 },
data: cmd_data,
data_size: (cmd_hdr_buf.len() + cmd_buf.len()) as u32,
});
mbox_cmd.populate_chksum().unwrap();

let resp = model.mailbox_execute(
u32::from(CommandId::INVOKE_DPE),
mbox_cmd.as_bytes().unwrap(),
);
let resp = resp.unwrap().expect("We should have received a response");

assert!(resp.len() <= std::mem::size_of::<InvokeDpeResp>());
let mut resp_hdr = InvokeDpeResp::default();
resp_hdr.as_bytes_mut()[..resp.len()].copy_from_slice(&resp);

assert!(caliptra_common::checksum::verify_checksum(
resp_hdr.hdr.chksum,
0x0,
&resp[core::mem::size_of_val(&resp_hdr.hdr.chksum)..],
));

let resp_bytes = &resp_hdr.data[..resp_hdr.data_size as usize];
CertifyKeyResp::read_from(resp_bytes).unwrap()
}

0 comments on commit 5f31f01

Please sign in to comment.