Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DPE key derivation verification tests (#1552). #1645

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 137 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,140 @@ 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());
// Both allow_ca and allow_x509 are currently true for all contexts. This may change to
// be more dynamic in the future.
let allow_ca = true;
let allow_x509 = true;
hash.update(allow_ca.as_bytes());
hash.update(allow_x509.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()
}
Loading