Skip to content

Commit 2dbfa83

Browse files
committed
Add DPE key verification tests.
1 parent c4f548a commit 2dbfa83

File tree

2 files changed

+243
-12
lines changed

2 files changed

+243
-12
lines changed

test/src/derive.rs

+137-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,140 @@ 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+
// Both allow_ca and allow_x509 are currently true for all contexts. This may change to
535+
// be more dynamic in the future.
536+
let allow_ca = true;
537+
let allow_x509 = true;
538+
hash.update(allow_ca.as_bytes());
539+
hash.update(allow_x509.as_bytes());
540+
}
541+
let measurement = hash.finish();
542+
543+
// Derive the CDI's context
544+
let mut hash = Sha384::new();
545+
hash.update(&measurement);
546+
hash.update(b"DPE");
547+
let context = hash.finish();
548+
549+
// Derive the CDI
550+
let mut cdi: [u32; 12] = transmute!(hmac384_kdf(
551+
swap_word_bytes(&rt_key.cdi).as_bytes(),
552+
b"derive_cdi",
553+
Some(&context),
554+
));
555+
swap_word_bytes_inplace(&mut cdi);
556+
557+
// Derive the seed
558+
let mut priv_key_seed: [u32; 12] = transmute!(hmac384_kdf(
559+
swap_word_bytes(&cdi).as_bytes(),
560+
label,
561+
Some(b"ECC")
562+
));
563+
swap_word_bytes_inplace(&mut priv_key_seed);
564+
565+
// Derive the private key
566+
let mut priv_key: [u32; 12] = transmute!(hmac384_drbg_keygen(
567+
swap_word_bytes(&priv_key_seed).as_bytes(),
568+
swap_word_bytes(&ECDSA_KEYGEN_NONCE).as_bytes()
569+
));
570+
swap_word_bytes_inplace(&mut priv_key);
571+
Self { priv_key, cdi }
572+
}
573+
574+
pub fn derive_public_key(&self) -> PKey<Public> {
575+
derive_ecdsa_key(
576+
swap_word_bytes(&self.priv_key)
577+
.as_bytes()
578+
.try_into()
579+
.unwrap(),
580+
)
581+
}
582+
583+
fn get_rt_journey_tci(pcr_rt_current: &PcrRtCurrentInput) -> TciNodeData {
584+
let current = swap_word_bytes(&PcrRtCurrent::derive(pcr_rt_current).0);
585+
let cumulative = Self::extend_from_zeroes(current.as_bytes());
586+
TciNodeData {
587+
tci_type: u32::from_be_bytes(*b"RTJM"),
588+
tci_cumulative: TciMeasurement(cumulative),
589+
tci_current: TciMeasurement(current.as_bytes().try_into().unwrap()),
590+
locality: u32::MAX,
591+
}
592+
}
593+
594+
fn get_valid_pauser_tci(
595+
mbox_valid_pauser: &[u32; Self::PAUSER_COUNT],
596+
mbox_pauser_lock: &[bool; Self::PAUSER_COUNT],
597+
) -> TciNodeData {
598+
// Hash all of the locked PAUSERs
599+
let mut hash = Sha384::new();
600+
for (lock, valid_pauser) in mbox_pauser_lock.iter().zip(mbox_valid_pauser) {
601+
if *lock {
602+
hash.update(valid_pauser.as_bytes());
603+
}
604+
}
605+
let valid_pauser_hash_bytes = hash.finish();
606+
607+
// Swap the endianness
608+
let mut valid_pauser_hash_words = [0u32; 12];
609+
valid_pauser_hash_words
610+
.as_bytes_mut()
611+
.copy_from_slice(&valid_pauser_hash_bytes);
612+
swap_word_bytes_inplace(&mut valid_pauser_hash_words);
613+
614+
let cumulative = Self::extend_from_zeroes(valid_pauser_hash_words.as_bytes());
615+
TciNodeData {
616+
tci_type: u32::from_be_bytes(*b"MBVP"),
617+
tci_cumulative: TciMeasurement(cumulative),
618+
tci_current: TciMeasurement(valid_pauser_hash_words.as_bytes().try_into().unwrap()),
619+
locality: 1,
620+
}
621+
}
622+
623+
fn get_measurement_tci(measurement: &[u8; 48], tci_type: &[u8; 4]) -> TciNodeData {
624+
TciNodeData {
625+
tci_type: u32::from_be_bytes(*tci_type),
626+
tci_cumulative: TciMeasurement(Self::extend_from_zeroes(measurement)),
627+
tci_current: TciMeasurement(*measurement),
628+
locality: 1,
629+
}
630+
}
631+
632+
fn extend_from_zeroes(data: &[u8]) -> [u8; 48] {
633+
sha384(&[&[0u8; 48], data].concat())
634+
}
635+
}
636+
501637
#[test]
502638
fn test_derive_fmc_alias_key() {
503639
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)