From f7d2543b6a04d7191eb22fa714c5c59d25952b75 Mon Sep 17 00:00:00 2001 From: Carl Lundin Date: Tue, 14 Jan 2025 14:58:46 -0800 Subject: [PATCH] Started work on sign with exported runtime command. --- api/src/mailbox.rs | 9 +- runtime/src/dpe_crypto.rs | 53 ++++++++++- runtime/src/drivers.rs | 5 +- runtime/src/invoke_dpe.rs | 7 +- runtime/src/lib.rs | 3 + runtime/src/sign_with_exported.rs | 91 +++++++++++++++++++ .../tests/runtime_integration_tests/common.rs | 17 ++-- .../tests/runtime_integration_tests/main.rs | 1 + .../test_sign_with_export.rs | 71 +++++++++++++++ test/tests/fips_test_suite/common.rs | 6 -- 10 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 runtime/src/sign_with_exported.rs create mode 100644 runtime/tests/runtime_integration_tests/test_sign_with_export.rs diff --git a/api/src/mailbox.rs b/api/src/mailbox.rs index f659bb65fd..0832bfaf53 100644 --- a/api/src/mailbox.rs +++ b/api/src/mailbox.rs @@ -258,7 +258,7 @@ pub enum MailboxReq { CertifyKeyExtended(CertifyKeyExtendedReq), SetAuthManifest(SetAuthManifestReq), AuthorizeAndStash(AuthorizeAndStashReq), - SignWithExported(SignWithExportedResp), + SignWithExported(SignWithExportedReq), } impl MailboxReq { @@ -709,7 +709,7 @@ pub struct InvokeDpeResp { pub data: [u8; InvokeDpeResp::DATA_MAX_SIZE], // variable length } impl InvokeDpeResp { - pub const DATA_MAX_SIZE: usize = 2200; + pub const DATA_MAX_SIZE: usize = 7000; // Temporarily avoid DPE buffer overrun. } impl ResponseVarSize for InvokeDpeResp {} @@ -1039,9 +1039,8 @@ impl Default for SignWithExportedReq { } impl SignWithExportedReq { - pub const EXPORTED_CDI_MAX_SIZE: usize = 512; - pub const MAX_DIGEST_SIZE: usize = 64; // TODO(clundin): Is this a reasonable max size? This accommodates - // SHA-512 but DPE only supports SHA-384. + pub const EXPORTED_CDI_MAX_SIZE: usize = 32; + pub const MAX_DIGEST_SIZE: usize = 48; // TODO(clundin): Is this a reasonable max size? DPE only supports up to SHA-384. } impl Request for SignWithExportedReq { diff --git a/runtime/src/dpe_crypto.rs b/runtime/src/dpe_crypto.rs index 008a8b1026..ccbaba8657 100644 --- a/runtime/src/dpe_crypto.rs +++ b/runtime/src/dpe_crypto.rs @@ -14,6 +14,7 @@ Abstract: use core::cmp::min; +use arrayvec::ArrayVec; use caliptra_cfi_derive_git::cfi_impl_fn; use caliptra_cfi_lib_git::{cfi_assert, cfi_assert_eq, cfi_launder}; use caliptra_common::keyids::{ @@ -25,10 +26,16 @@ use caliptra_drivers::{ KeyVault, KeyWriteArgs, Sha384, Sha384DigestOp, Trng, }; use crypto::{AlgLen, Crypto, CryptoBuf, CryptoError, Digest, EcdsaPub, EcdsaSig, Hasher}; -use dpe::x509::MeasurementData; +use dpe::{x509::MeasurementData, MAX_EXPORTED_CDI_SIZE}; use zerocopy::IntoBytes; use zeroize::Zeroize; +// Currently only can export CDI once, but in the future we may want to support multiple exported +// CDI handles at the cost of using more KeyVault slots. +pub const EXPORTED_HANDLES_NUM: usize = 1; +pub type ExportedCdiHandle = [u8; MAX_EXPORTED_CDI_SIZE]; +pub type ExportedCdiHandles = ArrayVec<(KeyId, ExportedCdiHandle), EXPORTED_HANDLES_NUM>; + pub struct DpeCrypto<'a> { sha384: &'a mut Sha384, trng: &'a mut Trng, @@ -38,6 +45,7 @@ pub struct DpeCrypto<'a> { rt_pub_key: &'a mut Ecc384PubKey, key_id_rt_cdi: KeyId, key_id_rt_priv_key: KeyId, + exported_cdi_slots: Option<&'a mut ExportedCdiHandles>, } impl<'a> DpeCrypto<'a> { @@ -61,9 +69,14 @@ impl<'a> DpeCrypto<'a> { rt_pub_key, key_id_rt_cdi, key_id_rt_priv_key, + exported_cdi_slots: None, } } + pub fn with_exported_cdi_slots(&mut self, slots: &'a mut ExportedCdiHandles) { + self.exported_cdi_slots = Some(slots); + } + fn derive_cdi_inner( &mut self, algs: AlgLen, @@ -227,9 +240,41 @@ impl<'a> Crypto for DpeCrypto<'a> { self.derive_cdi_inner(algs, measurement, info, KEY_ID_DPE_CDI) } - #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] - fn get_exported_cdi(&mut self) -> Result { - Ok(KEY_ID_EXPORTED_DPE_CDI) + //TODO(clundin): Wrap this with CFI? + fn get_exported_cdi_handle( + &mut self, + cdi: &Self::Cdi, + ) -> Result<[u8; MAX_EXPORTED_CDI_SIZE], CryptoError> { + let mut exported_cdi_handle = [0; MAX_EXPORTED_CDI_SIZE]; + self.rand_bytes(&mut exported_cdi_handle)?; + + // TODO(clundin): Add a real error. + let Some(ref mut export_cdi_slots) = self.exported_cdi_slots else { Err(CryptoError::ExportedCdiHandleLimitExceeded)? }; + if export_cdi_slots.is_full() { + return Err(CryptoError::ExportedCdiHandleLimitExceeded); + } + + for slot in export_cdi_slots.iter() { + if slot.0 == *cdi { + return Err(CryptoError::ExportedCdiHandleLimitExceeded); + } + } + export_cdi_slots.push((cdi.clone(), exported_cdi_handle)); + Ok(exported_cdi_handle) + } + + //TODO(clundin): Wrap this with CFI? + fn get_cdi_from_exported_handle( + &mut self, + exported_cdi_handle: &[u8; MAX_EXPORTED_CDI_SIZE], + ) -> Option { + let Some(ref export_cdi_slots) = self.exported_cdi_slots else { return None }; + for (cdi, handle) in export_cdi_slots.iter() { + if handle == exported_cdi_handle { + return Some(cdi.clone()); + } + } + None } #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] diff --git a/runtime/src/drivers.rs b/runtime/src/drivers.rs index 107aa4fc21..2b3b02ed86 100644 --- a/runtime/src/drivers.rs +++ b/runtime/src/drivers.rs @@ -23,6 +23,7 @@ use crate::{ PL1_DPE_ACTIVE_CONTEXT_THRESHOLD, }; +use crate::dpe_crypto::ExportedCdiHandles; use arrayvec::ArrayVec; use caliptra_cfi_derive_git::{cfi_impl_fn, cfi_mod_fn}; use caliptra_cfi_lib_git::{cfi_assert, cfi_assert_eq, cfi_assert_eq_12_words, cfi_launder}; @@ -58,7 +59,7 @@ use dpe::{ }; use core::cmp::Ordering::{Equal, Greater}; -use crypto::{AlgLen, Crypto, CryptoBuf, Hasher}; +use crypto::{AlgLen, Crypto, CryptoBuf, Hasher, MAX_EXPORTED_CDI_SIZE}; use zerocopy::IntoBytes; #[derive(PartialEq, Clone)] @@ -108,6 +109,7 @@ pub struct Drivers { pub is_shutdown: bool, pub dmtf_device_info: Option>, + pub exported_cdi_slots: ExportedCdiHandles, } impl Drivers { @@ -146,6 +148,7 @@ impl Drivers { cert_chain: ArrayVec::new(), is_shutdown: false, dmtf_device_info: None, + exported_cdi_slots: ArrayVec::new(), }) } diff --git a/runtime/src/invoke_dpe.rs b/runtime/src/invoke_dpe.rs index fe1cf4206b..3215073a47 100644 --- a/runtime/src/invoke_dpe.rs +++ b/runtime/src/invoke_dpe.rs @@ -113,6 +113,12 @@ impl InvokeDpeCmd { { return Err(CaliptraError::RUNTIME_INCORRECT_PAUSER_PRIVILEGE_LEVEL); } + // TODO(clundin): Document this better; TL;DR if the export CDI flag is set we + // need to pass a persistent back end for it. + // Also this should only be needed when the export CDI flag is set, but it's + // okay to always do it. + env.crypto + .with_exported_cdi_slots(&mut drivers.exported_cdi_slots); cmd.execute(dpe, &mut env, locality) } Command::CertifyKey(cmd) => { @@ -131,7 +137,6 @@ impl InvokeDpeCmd { destroy_ctx_resp } Command::Sign(cmd) => cmd.execute(dpe, &mut env, locality), - Command::SignWithExported(cmd) => cmd.execute(dpe, &mut env, locality), Command::RotateCtx(cmd) => cmd.execute(dpe, &mut env, locality), Command::GetCertificateChain(cmd) => cmd.execute(dpe, &mut env, locality), }; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a7aa642a89..374d71aae4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -30,6 +30,7 @@ mod invoke_dpe; mod pcr; mod populate_idev; mod set_auth_manifest; +mod sign_with_exported; mod stash_measurement; mod subject_alt_name; mod update; @@ -46,6 +47,7 @@ use mailbox::Mailbox; use crate::capabilities::CapabilitiesCmd; pub use crate::certify_key_extended::CertifyKeyExtendedCmd; pub use crate::hmac::Hmac; +use crate::sign_with_exported::SignWithExportedCmd; pub use crate::subject_alt_name::AddSubjectAltNameCmd; pub use authorize_and_stash::{IMAGE_AUTHORIZED, IMAGE_HASH_MISMATCH, IMAGE_NOT_AUTHORIZED}; pub use caliptra_common::fips::FipsVersionCmd; @@ -229,6 +231,7 @@ fn handle_command(drivers: &mut Drivers) -> CaliptraResult { CommandId::SET_AUTH_MANIFEST => SetAuthManifestCmd::execute(drivers, cmd_bytes), CommandId::AUTHORIZE_AND_STASH => AuthorizeAndStashCmd::execute(drivers, cmd_bytes), CommandId::GET_IDEV_CSR => GetIdevCsrCmd::execute(drivers, cmd_bytes), + CommandId::SIGN_WITH_EXPORTED => SignWithExportedCmd::execute(drivers, cmd_bytes), _ => Err(CaliptraError::RUNTIME_UNIMPLEMENTED_COMMAND), }?; diff --git a/runtime/src/sign_with_exported.rs b/runtime/src/sign_with_exported.rs new file mode 100644 index 0000000000..082c939592 --- /dev/null +++ b/runtime/src/sign_with_exported.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache-2.0 license + +use crate::{dpe_crypto::DpeCrypto, Drivers}; + +use caliptra_cfi_derive_git::cfi_impl_fn; +use caliptra_cfi_lib_git::{cfi_assert, cfi_assert_eq, cfi_launder}; + +use caliptra_common::mailbox_api::{ + MailboxResp, MailboxRespHeader, SignWithExportedReq, SignWithExportedResp, +}; +use caliptra_error::{CaliptraError, CaliptraResult}; + +use crypto::{Crypto, Digest, EcdsaSig}; +use dpe::{DPE_PROFILE, MAX_EXPORTED_CDI_SIZE}; +use zerocopy::{FromBytes, IntoBytes}; + +pub struct SignWithExportedCmd; +impl SignWithExportedCmd { + /// SignWithExported signs a `digest` using an ECDSA keypair derived from a exported_cdi + /// handle and the CDI stored in DPE. + /// + /// # Arguments + /// + /// * `env` - DPE environment containing Crypto and Platform implementations + /// * `digest` - The data to be signed + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn ecdsa_sign( + env: &mut DpeCrypto, + digest: &Digest, + exported_cdi_handle: &[u8; MAX_EXPORTED_CDI_SIZE], + ) -> CaliptraResult { + let algs = DPE_PROFILE.alg_len(); + // TODO(clundin): Add unique error codes. + let cdi = env.get_cdi_from_exported_handle(exported_cdi_handle); + // TODO(clundin): Actually handle this. + let cdi = cdi.unwrap(); + + let key_label = b"Exported ECC"; + let key_pair = env.derive_key_pair_exported(algs, &cdi, key_label, b"Exported Handle"); + + if cfi_launder(key_pair.is_ok()) { + #[cfg(not(feature = "no-cfi"))] + cfi_assert!(key_pair.is_ok()); + } else { + #[cfg(not(feature = "no-cfi"))] + cfi_assert!(key_pair.is_err()); + } + let (priv_key, pub_key) = + key_pair.map_err(|_| CaliptraError::RUNTIME_INITIALIZE_DPE_FAILED)?; + + let sig = env + .ecdsa_sign_with_derived(algs, digest, &priv_key, &pub_key) + .map_err(|_| CaliptraError::RUNTIME_INITIALIZE_DPE_FAILED)?; + + Ok(sig) + } + + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + #[inline(never)] + pub(crate) fn execute(drivers: &mut Drivers, cmd_args: &[u8]) -> CaliptraResult { + let cmd = SignWithExportedReq::ref_from_bytes(cmd_args) + .map_err(|_| CaliptraError::RUNTIME_MAILBOX_INVALID_PARAMS)?; + let key_id_rt_cdi = Drivers::get_key_id_rt_cdi(drivers)?; + let key_id_rt_priv_key = Drivers::get_key_id_rt_priv_key(drivers)?; + let mut crypto = DpeCrypto::new( + &mut drivers.sha384, + &mut drivers.trng, + &mut drivers.ecc384, + &mut drivers.hmac384, + &mut drivers.key_vault, + &mut drivers.persistent_data.get_mut().fht.rt_dice_pub_key, + key_id_rt_cdi, + key_id_rt_priv_key, + ); + crypto.with_exported_cdi_slots(&mut drivers.exported_cdi_slots); + + // TODO(clundin): Update error code. + let digest = + Digest::new(&cmd.digest).map_err(|_| CaliptraError::RUNTIME_INVALID_CHECKSUM)?; + // TODO(clundin): Can we / should we make these assumptions for the signature type? + // I think we need to expose more metadata. + let EcdsaSig { r, s } = Self::ecdsa_sign(&mut crypto, &digest, &cmd.exported_cdi)?; + + let mut resp = SignWithExportedResp::default(); + resp.signature[..r.bytes().len()].copy_from_slice(r.bytes()); + resp.signature[r.bytes().len()..s.bytes().len() + r.bytes().len()] + .copy_from_slice(s.bytes()); + resp.signature_size = (s.bytes().len() + r.bytes().len()) as u32; + Ok(MailboxResp::SignWithExported(resp)) + } +} diff --git a/runtime/tests/runtime_integration_tests/common.rs b/runtime/tests/runtime_integration_tests/common.rs index 20d59d1140..5dcfd980c5 100644 --- a/runtime/tests/runtime_integration_tests/common.rs +++ b/runtime/tests/runtime_integration_tests/common.rs @@ -22,8 +22,8 @@ use caliptra_hw_model::{ use dpe::{ commands::{Command, CommandHdr}, response::{ - CertifyKeyResp, DeriveContextResp, GetCertificateChainResp, GetProfileResp, NewHandleResp, - Response, ResponseHdr, SignResp, SignWithExportedResp, + CertifyKeyResp, DeriveContextExportedCdiResp, DeriveContextResp, GetCertificateChainResp, + GetProfileResp, NewHandleResp, Response, ResponseHdr, SignResp, }, }; use openssl::{ @@ -161,7 +161,6 @@ fn get_cmd_id(dpe_cmd: &mut Command) -> u32 { Command::DeriveContext(_) => Command::DERIVE_CONTEXT, Command::CertifyKey(_) => Command::CERTIFY_KEY, Command::Sign(_) => Command::SIGN, - Command::SignWithExported(_) => Command::SIGN_WITH_EXPORTED, Command::RotateCtx(_) => Command::ROTATE_CONTEXT_HANDLE, Command::DestroyCtx(_) => Command::DESTROY_CONTEXT, Command::GetCertificateChain(_) => Command::GET_CERTIFICATE_CHAIN, @@ -178,7 +177,6 @@ fn as_bytes<'a>(dpe_cmd: &'a mut Command) -> &'a [u8] { Command::InitCtx(cmd) => cmd.as_bytes(), Command::RotateCtx(cmd) => cmd.as_bytes(), Command::Sign(cmd) => cmd.as_bytes(), - Command::SignWithExported(cmd) => cmd.as_bytes(), } } @@ -187,9 +185,11 @@ fn parse_dpe_response(dpe_cmd: &mut Command, resp_bytes: &[u8]) -> Response { Command::CertifyKey(_) => { Response::CertifyKey(CertifyKeyResp::read_from_bytes(resp_bytes).unwrap()) } - Command::DeriveContext(_) => { - Response::DeriveContext(DeriveContextResp::read_from_bytes(resp_bytes).unwrap()) - } + Command::DeriveContext(_) => Response::DeriveContextExportedCdi( + // TODO(clundin): We need to infer the return type by the flags passed into the cmd. + // This type is based off the EXPORT_CDI flag. + DeriveContextExportedCdiResp::read_from_bytes(resp_bytes).unwrap(), + ), Command::GetCertificateChain(_) => Response::GetCertificateChain( GetCertificateChainResp::read_from_bytes(resp_bytes).unwrap(), ), @@ -206,9 +206,6 @@ fn parse_dpe_response(dpe_cmd: &mut Command, resp_bytes: &[u8]) -> Response { Response::RotateCtx(NewHandleResp::read_from_bytes(resp_bytes).unwrap()) } Command::Sign(_) => Response::Sign(SignResp::read_from_bytes(resp_bytes).unwrap()), - Command::SignWithExported(_) => { - Response::SignWithExported(SignWithExportedResp::read_from_bytes(resp_bytes).unwrap()) - } } } diff --git a/runtime/tests/runtime_integration_tests/main.rs b/runtime/tests/runtime_integration_tests/main.rs index 417946cda3..03e9ffbd34 100644 --- a/runtime/tests/runtime_integration_tests/main.rs +++ b/runtime/tests/runtime_integration_tests/main.rs @@ -18,6 +18,7 @@ mod test_pauser_privilege_levels; mod test_pcr; mod test_populate_idev; mod test_set_auth_manifest; +mod test_sign_with_export; mod test_stash_measurement; mod test_tagging; mod test_update_reset; diff --git a/runtime/tests/runtime_integration_tests/test_sign_with_export.rs b/runtime/tests/runtime_integration_tests/test_sign_with_export.rs new file mode 100644 index 0000000000..c1f868b8b3 --- /dev/null +++ b/runtime/tests/runtime_integration_tests/test_sign_with_export.rs @@ -0,0 +1,71 @@ +// Licensed under the Apache-2.0 license + +use caliptra_api::SocManager; +use caliptra_common::mailbox_api::{ + CommandId, MailboxReq, MailboxReqHeader, SignWithExportedReq, SignWithExportedResp, +}; +use caliptra_hw_model::HwModel; +use caliptra_runtime::RtBootStatus; +use dpe::{ + commands::{Command, DeriveContextCmd, DeriveContextFlags}, + context::ContextHandle, + response::Response, + DPE_PROFILE, +}; +use openssl::{bn::BigNum, ecdsa::EcdsaSig, x509::X509}; +use zerocopy::{FromBytes, IntoBytes}; + +use crate::common::{execute_dpe_cmd, run_rt_test, DpeResult, RuntimeTestArgs, TEST_DIGEST}; + +#[test] +fn test_sign_with_exported_cdi() { + let mut model = run_rt_test(RuntimeTestArgs::default()); + model.step_until(|m| { + m.soc_ifc().cptra_boot_status().read() == u32::from(RtBootStatus::RtReadyForCommands) + }); + + let get_cert_chain_cmd = DeriveContextCmd { + handle: ContextHandle::default(), + data: [0; DPE_PROFILE.get_tci_size()], + flags: DeriveContextFlags::EXPORT_CDI | DeriveContextFlags::CREATE_CERTIFICATE, + tci_type: 0, + target_locality: 0, + }; + let resp = execute_dpe_cmd( + &mut model, + &mut Command::DeriveContext(&get_cert_chain_cmd), + DpeResult::Success, + ); + + let resp = match resp { + Some(Response::DeriveContextExportedCdi(resp)) => resp, + _ => panic!("expected derive context resp!"), + }; + + let mut cmd = MailboxReq::SignWithExported(SignWithExportedReq { + hdr: MailboxReqHeader { chksum: 0 }, + exported_cdi: resp.exported_cdi, + digest: TEST_DIGEST, + }); + cmd.populate_chksum().unwrap(); + + let result = model.mailbox_execute( + CommandId::SIGN_WITH_EXPORTED.into(), + cmd.as_bytes().unwrap(), + ); + + let response = result.unwrap().unwrap(); + let response = SignWithExportedResp::ref_from_bytes(response.as_bytes()).unwrap(); + let r = &response.signature[..DPE_PROFILE.get_ecc_int_size() as usize]; + let s = &response.signature[r.len()..response.signature_size as usize]; + let sig = EcdsaSig::from_private_components( + BigNum::from_slice(r).unwrap(), + BigNum::from_slice(s).unwrap(), + ) + .unwrap(); + + let x509 = + X509::from_der(&resp.new_certificate[..resp.certificate_size.try_into().unwrap()]).unwrap(); + let ec_pub_key = x509.public_key().unwrap().ec_key().unwrap(); + assert!(sig.verify(&TEST_DIGEST, &ec_pub_key).unwrap()); +} diff --git a/test/tests/fips_test_suite/common.rs b/test/tests/fips_test_suite/common.rs index cc560544a0..9261c29e9e 100755 --- a/test/tests/fips_test_suite/common.rs +++ b/test/tests/fips_test_suite/common.rs @@ -7,7 +7,6 @@ use caliptra_common::mailbox_api::*; use caliptra_drivers::FipsTestHook; use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, ModelError, ShaAccMode}; use caliptra_test::swap_word_bytes_inplace; -use dpe::response::SignWithExportedResp; use dpe::{ commands::*, response::{ @@ -293,7 +292,6 @@ fn get_cmd_id(dpe_cmd: &mut Command) -> u32 { Command::DeriveContext(_) => Command::DERIVE_CONTEXT, Command::CertifyKey(_) => Command::CERTIFY_KEY, Command::Sign(_) => Command::SIGN, - Command::SignWithExported(_) => Command::SIGN_WITH_EXPORTED, Command::RotateCtx(_) => Command::ROTATE_CONTEXT_HANDLE, Command::DestroyCtx(_) => Command::DESTROY_CONTEXT, Command::GetCertificateChain(_) => Command::GET_CERTIFICATE_CHAIN, @@ -309,7 +307,6 @@ pub fn as_bytes<'a>(dpe_cmd: &'a mut Command) -> &'a [u8] { Command::InitCtx(cmd) => cmd.as_bytes(), Command::RotateCtx(cmd) => cmd.as_bytes(), Command::Sign(cmd) => cmd.as_bytes(), - Command::SignWithExported(cmd) => cmd.as_bytes(), } } @@ -337,9 +334,6 @@ pub fn parse_dpe_response(dpe_cmd: &mut Command, resp_bytes: &[u8]) -> Response Response::RotateCtx(NewHandleResp::read_from_bytes(resp_bytes).unwrap()) } Command::Sign(_) => Response::Sign(SignResp::read_from_bytes(resp_bytes).unwrap()), - Command::SignWithExported(_) => { - Response::SignWithExported(SignWithExportedResp::read_from_bytes(resp_bytes).unwrap()) - } } }