From 5f31f01f09c18739145ea6d256c75baf8b228647 Mon Sep 17 00:00:00 2001 From: Zach Halvorsen Date: Sat, 17 Aug 2024 00:27:30 +0000 Subject: [PATCH] Add DPE key verification tests. --- test/src/derive.rs | 132 +++++++++++++++++- .../caliptra_integration_tests/smoke_test.rs | 117 ++++++++++++++-- 2 files changed, 237 insertions(+), 12 deletions(-) diff --git a/test/src/derive.rs b/test/src/derive.rs index 17dcb2587a..f363879260 100644 --- a/test/src/derive.rs +++ b/test/src/derive.rs @@ -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}; @@ -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 { + 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( diff --git a/test/tests/caliptra_integration_tests/smoke_test.rs b/test/tests/caliptra_integration_tests/smoke_test.rs index 3760416549..b4ed18a30f 100644 --- a/test/tests/caliptra_integration_tests/smoke_test.rs +++ b/test/tests/caliptra_integration_tests/smoke_test.rs @@ -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) { @@ -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 @@ -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( @@ -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::()); + 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() +}