Skip to content

Commit 5f31f01

Browse files
committed
Add DPE key verification tests.
1 parent c4f548a commit 5f31f01

File tree

2 files changed

+237
-12
lines changed

2 files changed

+237
-12
lines changed

test/src/derive.rs

+131-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
/// DO NOT REFACTOR THIS FILE TO RE-USE CODE FROM OTHER PARTS OF CALIPTRA
77
use caliptra_hw_model_types::SecurityState;
88
use caliptra_image_types::ImageManifest;
9+
use caliptra_runtime::TciMeasurement;
10+
use dpe::tci::TciNodeData;
911
use openssl::{
1012
pkey::{PKey, Public},
11-
sha::{sha256, sha384},
13+
sha::{sha256, sha384, Sha384},
1214
};
1315
use zerocopy::{transmute, AsBytes};
1416

@@ -498,6 +500,134 @@ impl RtAliasKey {
498500
}
499501
}
500502

503+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
504+
pub struct DpeAliasKey {
505+
pub cdi: [u32; 12],
506+
507+
// The DPE alias private key as stored in the key-vault
508+
pub priv_key: [u32; 12],
509+
}
510+
impl DpeAliasKey {
511+
pub const PAUSER_COUNT: usize = 5;
512+
513+
pub fn derive(
514+
pcr_rt_current: &PcrRtCurrentInput,
515+
rt_key: &RtAliasKey,
516+
measurement: &[u8; 48],
517+
tci_type: &[u8; 4],
518+
label: &[u8],
519+
mbox_valid_pauser: &[u32; Self::PAUSER_COUNT],
520+
mbox_pauser_lock: &[bool; Self::PAUSER_COUNT],
521+
) -> Self {
522+
// Get all of the TCIs
523+
let tcis = [
524+
Self::get_rt_journey_tci(pcr_rt_current),
525+
Self::get_valid_pauser_tci(mbox_valid_pauser, mbox_pauser_lock),
526+
Self::get_measurement_tci(measurement, tci_type),
527+
];
528+
529+
// Derive the CDI's measurement. Note they are added to the hash in the reverse order
530+
// because DPE starts from the leaf and goes to the root.
531+
let mut hash = Sha384::new();
532+
for tci in tcis.iter().rev() {
533+
hash.update(tci.as_bytes());
534+
}
535+
let measurement = hash.finish();
536+
537+
// Derive the CDI's context
538+
let mut hash = Sha384::new();
539+
hash.update(&measurement);
540+
hash.update(b"DPE");
541+
let context = hash.finish();
542+
543+
// Derive the CDI
544+
let mut cdi: [u32; 12] = transmute!(hmac384_kdf(
545+
swap_word_bytes(&rt_key.cdi).as_bytes(),
546+
b"derive_cdi",
547+
Some(&context),
548+
));
549+
swap_word_bytes_inplace(&mut cdi);
550+
551+
// Derive the seed
552+
let mut priv_key_seed: [u32; 12] = transmute!(hmac384_kdf(
553+
swap_word_bytes(&cdi).as_bytes(),
554+
label,
555+
Some(b"ECC")
556+
));
557+
swap_word_bytes_inplace(&mut priv_key_seed);
558+
559+
// Derive the private key
560+
let mut priv_key: [u32; 12] = transmute!(hmac384_drbg_keygen(
561+
swap_word_bytes(&priv_key_seed).as_bytes(),
562+
swap_word_bytes(&ECDSA_KEYGEN_NONCE).as_bytes()
563+
));
564+
swap_word_bytes_inplace(&mut priv_key);
565+
Self { priv_key, cdi }
566+
}
567+
568+
pub fn derive_public_key(&self) -> PKey<Public> {
569+
derive_ecdsa_key(
570+
swap_word_bytes(&self.priv_key)
571+
.as_bytes()
572+
.try_into()
573+
.unwrap(),
574+
)
575+
}
576+
577+
fn get_rt_journey_tci(pcr_rt_current: &PcrRtCurrentInput) -> TciNodeData {
578+
let current = swap_word_bytes(&PcrRtCurrent::derive(pcr_rt_current).0);
579+
let cumulative = Self::extend_from_zeroes(current.as_bytes());
580+
TciNodeData {
581+
tci_type: u32::from_be_bytes(*b"RTJM"),
582+
tci_cumulative: TciMeasurement(cumulative),
583+
tci_current: TciMeasurement(current.as_bytes().try_into().unwrap()),
584+
locality: u32::MAX,
585+
}
586+
}
587+
588+
fn get_valid_pauser_tci(
589+
mbox_valid_pauser: &[u32; Self::PAUSER_COUNT],
590+
mbox_pauser_lock: &[bool; Self::PAUSER_COUNT],
591+
) -> TciNodeData {
592+
// Hash all of the locked PAUSERs
593+
let mut hash = Sha384::new();
594+
for (lock, valid_pauser) in mbox_pauser_lock.iter().zip(mbox_valid_pauser) {
595+
if *lock {
596+
hash.update(valid_pauser.as_bytes());
597+
}
598+
}
599+
let valid_pauser_hash_bytes = hash.finish();
600+
601+
// Swap the endianness
602+
let mut valid_pauser_hash_words = [0u32; 12];
603+
valid_pauser_hash_words
604+
.as_bytes_mut()
605+
.copy_from_slice(&valid_pauser_hash_bytes);
606+
swap_word_bytes_inplace(&mut valid_pauser_hash_words);
607+
608+
let cumulative = Self::extend_from_zeroes(valid_pauser_hash_words.as_bytes());
609+
TciNodeData {
610+
tci_type: u32::from_be_bytes(*b"MBVP"),
611+
tci_cumulative: TciMeasurement(cumulative),
612+
tci_current: TciMeasurement(valid_pauser_hash_words.as_bytes().try_into().unwrap()),
613+
locality: 1,
614+
}
615+
}
616+
617+
fn get_measurement_tci(measurement: &[u8; 48], tci_type: &[u8; 4]) -> TciNodeData {
618+
TciNodeData {
619+
tci_type: u32::from_be_bytes(*tci_type),
620+
tci_cumulative: TciMeasurement(Self::extend_from_zeroes(measurement)),
621+
tci_current: TciMeasurement(*measurement),
622+
locality: 1,
623+
}
624+
}
625+
626+
fn extend_from_zeroes(data: &[u8]) -> [u8; 48] {
627+
sha384(&[&[0u8; 48], data].concat())
628+
}
629+
}
630+
501631
#[test]
502632
fn test_derive_fmc_alias_key() {
503633
let fmc_alias_key = FmcAliasKey::derive(

test/tests/caliptra_integration_tests/smoke_test.rs

+106-11
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@
33
use caliptra_builder::firmware::{APP_WITH_UART, FMC_WITH_UART};
44
use caliptra_builder::{firmware, ImageOptions};
55
use caliptra_common::mailbox_api::{
6-
GetFmcAliasCertReq, GetLdevCertReq, GetRtAliasCertReq, ResponseVarSize,
6+
CommandId, GetFmcAliasCertReq, GetLdevCertReq, GetRtAliasCertReq, InvokeDpeReq, InvokeDpeResp,
7+
MailboxReq, MailboxReqHeader, ResponseVarSize, StashMeasurementReq,
78
};
89
use caliptra_common::RomBootStatus;
910
use caliptra_drivers::CaliptraError;
10-
use caliptra_hw_model::{BootParams, HwModel, InitParams, SecurityState};
11+
use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, SecurityState};
1112
use caliptra_hw_model_types::{DeviceLifecycle, Fuses, RandomEtrngResponses, RandomNibbles};
12-
use caliptra_test::derive::{PcrRtCurrentInput, RtAliasKey};
13+
use caliptra_test::derive::{DpeAliasKey, PcrRtCurrentInput, RtAliasKey};
1314
use caliptra_test::{derive, redact_cert, run_test, RedactOpts, UnwrapSingle};
1415
use caliptra_test::{
1516
derive::{DoeInput, DoeOutput, FmcAliasKey, IDevId, LDevId, Pcr0, Pcr0Input},
1617
swap_word_bytes, swap_word_bytes_inplace,
1718
x509::{DiceFwid, DiceTcbInfo},
1819
};
20+
use dpe::commands::{CertifyKeyCmd, Command};
21+
use dpe::commands::{CertifyKeyFlags, CommandHdr};
22+
use dpe::context::ContextHandle;
23+
use dpe::response::CertifyKeyResp;
1924
use openssl::nid::Nid;
2025
use openssl::sha::{sha384, Sha384};
2126
use rand::rngs::StdRng;
2227
use rand::SeedableRng;
2328
use regex::Regex;
2429
use std::mem;
25-
use zerocopy::AsBytes;
30+
use zerocopy::{AsBytes, FromBytes};
2631

2732
#[track_caller]
2833
fn assert_output_contains(haystack: &str, needle: &str) {
@@ -429,13 +434,13 @@ fn smoke_test() {
429434
"Manifest digest is {:02x?}",
430435
image.manifest.runtime.digest.as_bytes()
431436
);
432-
let expected_rt_alias_key = RtAliasKey::derive(
433-
&PcrRtCurrentInput {
434-
runtime_digest: image.manifest.runtime.digest,
435-
manifest: image.manifest,
436-
},
437-
&expected_fmc_alias_key,
438-
);
437+
438+
let pcr_rt_input = PcrRtCurrentInput {
439+
runtime_digest: image.manifest.runtime.digest,
440+
manifest: image.manifest,
441+
};
442+
443+
let expected_rt_alias_key = RtAliasKey::derive(&pcr_rt_input, &expected_fmc_alias_key);
439444

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

558+
let measurement: [u8; 48] = [0xdeadbeef_u32; 12].as_bytes().try_into().unwrap();
559+
let tci_type = [0xABu8; 4];
560+
let stash_req = StashMeasurementReq {
561+
measurement,
562+
hdr: MailboxReqHeader { chksum: 0 },
563+
metadata: tci_type,
564+
context: [0xCD; 48],
565+
svn: 0xEF01,
566+
};
567+
hw.mailbox_execute_req(stash_req).unwrap();
568+
569+
let mut cmd = CertifyKeyCmd {
570+
handle: ContextHandle::default(),
571+
label: [
572+
48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27,
573+
26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
574+
3, 2, 1,
575+
],
576+
flags: CertifyKeyFlags::empty(),
577+
format: CertifyKeyCmd::FORMAT_X509,
578+
};
579+
580+
let cert_key_response = execute_certify_key_cmd(&mut hw, &mut cmd);
581+
let dpe_cert = openssl::x509::X509::from_der(
582+
&cert_key_response.cert[..cert_key_response.cert_size as usize],
583+
)
584+
.unwrap();
585+
let dpe_cert_txt = String::from_utf8(rt_alias_cert.to_text().unwrap()).unwrap();
586+
587+
// Get the MBOX PAUSER settings
588+
let mbox_valid_pauser: [u32; DpeAliasKey::PAUSER_COUNT] =
589+
hw.soc_ifc().cptra_mbox_valid_pauser().read();
590+
let mut mbox_pauser_lock: [bool; DpeAliasKey::PAUSER_COUNT] = Default::default();
591+
for (i, lock) in mbox_pauser_lock.iter_mut().enumerate() {
592+
*lock = hw.soc_ifc().cptra_mbox_pauser_lock().at(i).read().lock();
593+
}
594+
595+
let expected_dpe_alias_key = DpeAliasKey::derive(
596+
&pcr_rt_input,
597+
&expected_rt_alias_key,
598+
&measurement,
599+
&tci_type,
600+
&cmd.label,
601+
&mbox_valid_pauser,
602+
&mbox_pauser_lock,
603+
);
604+
605+
assert!(expected_dpe_alias_key
606+
.derive_public_key()
607+
.public_eq(&dpe_cert.public_key().unwrap()));
608+
609+
println!("dpe cert: {dpe_cert_txt}");
610+
553611
// Hitlessly update to the no-uart runtime firmware
554612

555613
let image2 = caliptra_builder::build_and_sign_image(
@@ -857,3 +915,40 @@ fn test_fmc_wdt_timeout() {
857915
// error_internal_intr_r must be 0b01000000 since the error_wdt_timer1_timeout_sts bit must be set
858916
assert_eq!(error_internal_intr_r, 0b01000000);
859917
}
918+
919+
fn execute_certify_key_cmd(model: &mut DefaultHwModel, cmd: &mut CertifyKeyCmd) -> CertifyKeyResp {
920+
// Put the header and data into a unified buffer
921+
let mut cmd_data: [u8; 512] = [0u8; InvokeDpeReq::DATA_MAX_SIZE];
922+
let dpe_cmd_id = Command::CERTIFY_KEY;
923+
let cmd_hdr = CommandHdr::new_for_test(dpe_cmd_id);
924+
let cmd_hdr_buf = cmd_hdr.as_bytes();
925+
cmd_data[..cmd_hdr_buf.len()].copy_from_slice(cmd_hdr_buf);
926+
let cmd_buf = cmd.as_bytes();
927+
cmd_data[cmd_hdr_buf.len()..cmd_hdr_buf.len() + cmd_buf.len()].copy_from_slice(cmd_buf);
928+
929+
let mut mbox_cmd = MailboxReq::InvokeDpeCommand(InvokeDpeReq {
930+
hdr: MailboxReqHeader { chksum: 0 },
931+
data: cmd_data,
932+
data_size: (cmd_hdr_buf.len() + cmd_buf.len()) as u32,
933+
});
934+
mbox_cmd.populate_chksum().unwrap();
935+
936+
let resp = model.mailbox_execute(
937+
u32::from(CommandId::INVOKE_DPE),
938+
mbox_cmd.as_bytes().unwrap(),
939+
);
940+
let resp = resp.unwrap().expect("We should have received a response");
941+
942+
assert!(resp.len() <= std::mem::size_of::<InvokeDpeResp>());
943+
let mut resp_hdr = InvokeDpeResp::default();
944+
resp_hdr.as_bytes_mut()[..resp.len()].copy_from_slice(&resp);
945+
946+
assert!(caliptra_common::checksum::verify_checksum(
947+
resp_hdr.hdr.chksum,
948+
0x0,
949+
&resp[core::mem::size_of_val(&resp_hdr.hdr.chksum)..],
950+
));
951+
952+
let resp_bytes = &resp_hdr.data[..resp_hdr.data_size as usize];
953+
CertifyKeyResp::read_from(resp_bytes).unwrap()
954+
}

0 commit comments

Comments
 (0)