Skip to content

Commit 8cad64c

Browse files
committed
Add DPE key verification tests.
1 parent c4f548a commit 8cad64c

File tree

2 files changed

+222
-5
lines changed

2 files changed

+222
-5
lines changed

test/src/derive.rs

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

test/tests/caliptra_integration_tests/smoke_test.rs

+92-4
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) {
@@ -550,6 +555,52 @@ 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+
let expected_dpe_alias_key = DpeAliasKey::derive(
588+
&PcrRtCurrentInput {
589+
runtime_digest: image.manifest.runtime.digest,
590+
manifest: image.manifest,
591+
},
592+
&expected_rt_alias_key,
593+
&measurement,
594+
&tci_type,
595+
&cmd.label,
596+
);
597+
598+
assert!(expected_dpe_alias_key
599+
.derive_public_key()
600+
.public_eq(&dpe_cert.public_key().unwrap()));
601+
602+
println!("dpe cert: {dpe_cert_txt}");
603+
553604
// Hitlessly update to the no-uart runtime firmware
554605

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

0 commit comments

Comments
 (0)