diff --git a/Cargo.lock b/Cargo.lock index 87edca4772..a15aa393a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,7 @@ dependencies = [ "openssl", "ufmt", "zerocopy", + "zeroize", ] [[package]] diff --git a/api/src/mailbox.rs b/api/src/mailbox.rs index 9db5ff59f0..e1ad6a6a9f 100644 --- a/api/src/mailbox.rs +++ b/api/src/mailbox.rs @@ -54,6 +54,9 @@ impl CommandId { // The get IDevID CSR command. pub const GET_IDEV_CSR: Self = Self(0x4944_4352); // "IDCR" + + // The get FMC Alias CSR command. + pub const GET_FMC_ALIAS_CSR: Self = Self(0x464D_4352); // "FMCR" } impl From for CommandId { @@ -155,6 +158,7 @@ pub enum MailboxResp { CertifyKeyExtended(CertifyKeyExtendedResp), AuthorizeAndStash(AuthorizeAndStashResp), GetIdevCsr(GetIdevCsrResp), + GetFmcAliasCsr(GetFmcAliasCsrResp), } impl MailboxResp { @@ -176,6 +180,7 @@ impl MailboxResp { MailboxResp::CertifyKeyExtended(resp) => Ok(resp.as_bytes()), MailboxResp::AuthorizeAndStash(resp) => Ok(resp.as_bytes()), MailboxResp::GetIdevCsr(resp) => Ok(resp.as_bytes()), + MailboxResp::GetFmcAliasCsr(resp) => Ok(resp.as_bytes()), } } @@ -197,6 +202,7 @@ impl MailboxResp { MailboxResp::CertifyKeyExtended(resp) => Ok(resp.as_bytes_mut()), MailboxResp::AuthorizeAndStash(resp) => Ok(resp.as_bytes_mut()), MailboxResp::GetIdevCsr(resp) => Ok(resp.as_bytes_mut()), + MailboxResp::GetFmcAliasCsr(resp) => Ok(resp.as_bytes_mut()), } } @@ -1017,6 +1023,41 @@ impl Default for GetIdevCsrResp { } } +// GET_IDEVID_CSR +#[repr(C)] +#[derive(Default, Debug, AsBytes, FromBytes, PartialEq, Eq)] +pub struct GetFmcAliasCsrReq { + pub hdr: MailboxReqHeader, +} + +impl Request for GetFmcAliasCsrReq { + const ID: CommandId = CommandId::GET_FMC_ALIAS_CSR; + type Resp = GetFmcAliasCsrResp; +} + +#[repr(C)] +#[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)] +pub struct GetFmcAliasCsrResp { + pub hdr: MailboxRespHeader, + pub data_size: u32, + pub data: [u8; Self::DATA_MAX_SIZE], +} + +impl Default for GetFmcAliasCsrResp { + fn default() -> Self { + Self { + hdr: MailboxRespHeader::default(), + data_size: 0, + data: [0u8; Self::DATA_MAX_SIZE], + } + } +} + +impl GetFmcAliasCsrResp { + pub const DATA_MAX_SIZE: usize = 512; +} +impl ResponseVarSize for GetFmcAliasCsrResp {} + #[repr(u32)] #[derive(Debug, PartialEq, Eq)] pub enum ImageHashSource { diff --git a/common/src/lib.rs b/common/src/lib.rs index ba4e884d24..e3cde63be5 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -38,9 +38,9 @@ pub use fuse::{FuseLogEntry, FuseLogEntryId}; pub use pcr::{PcrLogEntry, PcrLogEntryId, RT_FW_CURRENT_PCR, RT_FW_JOURNEY_PCR}; pub const FMC_ORG: u32 = 0x40000000; -pub const FMC_SIZE: u32 = 20 * 1024; +pub const FMC_SIZE: u32 = 22 * 1024 - 512; pub const RUNTIME_ORG: u32 = FMC_ORG + FMC_SIZE; -pub const RUNTIME_SIZE: u32 = 97 * 1024; +pub const RUNTIME_SIZE: u32 = 95 * 1024 + 512; pub use memory_layout::{DATA_ORG, FHT_ORG, FHT_SIZE, MAN1_ORG}; pub use wdt::{restart_wdt, start_wdt, stop_wdt, WdtTimeout}; diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml index e112f63655..ff90f5fa5c 100644 --- a/drivers/Cargo.toml +++ b/drivers/Cargo.toml @@ -37,6 +37,7 @@ verilator = ["caliptra-hw-model/verilator"] no-cfi = [] "hw-1.0" = ["caliptra-builder/hw-1.0", "caliptra-registers/hw-1.0"] fips-test-hooks = [] +fmc_alias_csr = [] [dev-dependencies] caliptra-api.workspace = true diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index b663bf8087..5988219dc8 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -86,8 +86,11 @@ pub use okref::okmutref; pub use okref::okref; pub use pcr_bank::{PcrBank, PcrId}; pub use pcr_reset::PcrResetCounter; +#[cfg(feature = "fmc")] +pub use persistent::fmc_alias_csr::FmcAliasCsr; #[cfg(feature = "runtime")] pub use persistent::AuthManifestImageMetadataList; + pub use persistent::{ FuseLogArray, IdevIdCsr, PcrLogArray, PersistentData, PersistentDataAccessor, StashMeasurementArray, FUSE_LOG_MAX_COUNT, MAX_CSR_SIZE, MEASUREMENT_MAX_COUNT, diff --git a/drivers/src/memory_layout.rs b/drivers/src/memory_layout.rs index a4314bc336..f387c0a0e5 100644 --- a/drivers/src/memory_layout.rs +++ b/drivers/src/memory_layout.rs @@ -41,7 +41,8 @@ pub const DPE_ORG: u32 = 0x50005400; pub const PCR_RESET_COUNTER_ORG: u32 = 0x50006800; pub const AUTH_MAN_IMAGE_METADATA_LIST_ORG: u32 = 0x50006C00; pub const IDEVID_CSR_ORG: u32 = 0x50008800; -pub const DATA_ORG: u32 = 0x50008C00; +pub const FMC_ALIAS_CSR_ORG: u32 = 0x50008C00; +pub const DATA_ORG: u32 = 0x50009000; pub const STACK_ORG: u32 = 0x5000f800; pub const ROM_STACK_ORG: u32 = 0x5001C000; @@ -74,7 +75,8 @@ pub const DPE_SIZE: u32 = 5 * 1024; pub const PCR_RESET_COUNTER_SIZE: u32 = 1024; pub const AUTH_MAN_IMAGE_METADATA_MAX_SIZE: u32 = 7 * 1024; pub const IDEVID_CSR_SIZE: u32 = 1024; -pub const DATA_SIZE: u32 = 27 * 1024; +pub const FMC_ALIAS_CSR_SIZE: u32 = 1024; +pub const DATA_SIZE: u32 = 26 * 1024; pub const STACK_SIZE: u32 = 64 * 1024; pub const ROM_STACK_SIZE: u32 = 14 * 1024; pub const ESTACK_SIZE: u32 = 1024; @@ -158,7 +160,13 @@ fn mem_layout_test_pcr_reset_counter() { #[test] #[allow(clippy::assertions_on_constants)] fn mem_layout_test_idevid_csr() { - assert_eq!((DATA_ORG - IDEVID_CSR_ORG), IDEVID_CSR_SIZE); + assert_eq!((DATA_ORG - FMC_ALIAS_CSR_ORG), FMC_ALIAS_CSR_SIZE); +} + +#[test] +#[allow(clippy::assertions_on_constants)] +fn mem_layout_test_fmc_alias_csr() { + assert_eq!((FMC_ALIAS_CSR_ORG - IDEVID_CSR_ORG), IDEVID_CSR_SIZE); } #[test] diff --git a/drivers/src/persistent.rs b/drivers/src/persistent.rs index e0bb49de67..9b13d7dd27 100644 --- a/drivers/src/persistent.rs +++ b/drivers/src/persistent.rs @@ -21,6 +21,9 @@ use crate::{ FirmwareHandoffTable, }; +#[cfg(feature = "fmc")] +use crate::FmcAliasCsr; + #[cfg(feature = "runtime")] use crate::pcr_reset::PcrResetCounter; @@ -52,6 +55,70 @@ pub struct IdevIdCsr { csr: [u8; MAX_CSR_SIZE], } +#[cfg(feature = "fmc")] +pub mod fmc_alias_csr { + use super::*; + + const _: () = assert!(size_of::() < memory_layout::FMC_ALIAS_CSR_SIZE as usize); + + #[derive(Clone, FromBytes, AsBytes, Zeroize)] + #[repr(C)] + pub struct FmcAliasCsr { + csr_len: u32, + csr: [u8; MAX_CSR_SIZE], + } + + impl Default for FmcAliasCsr { + fn default() -> Self { + Self { + csr_len: Self::UNPROVISIONED_CSR, + csr: [0; MAX_CSR_SIZE], + } + } + } + + impl FmcAliasCsr { + /// The `csr_len` field is set to this constant when a ROM image supports CSR generation but + /// the CSR generation flag was not enabled. + /// + /// This is used by the runtime to distinguish ROM images that support CSR generation from + /// ones that do not. + /// + /// u32::MAX is too large to be a valid CSR, so we use it to encode this state. + pub const UNPROVISIONED_CSR: u32 = u32::MAX; + + /// Get the CSR buffer + pub fn get(&self) -> Option<&[u8]> { + self.csr.get(..self.csr_len as usize) + } + + /// Create `Self` from a csr slice. `csr_len` MUST be the actual length of the csr. + pub fn new(csr_buf: &[u8], csr_len: usize) -> CaliptraResult { + if csr_len >= MAX_CSR_SIZE { + return Err(CaliptraError::FMC_ALIAS_INVALID_CSR); + } + + let mut _self = Self { + csr_len: csr_len as u32, + csr: [0; MAX_CSR_SIZE], + }; + _self.csr[..csr_len].copy_from_slice(&csr_buf[..csr_len]); + + Ok(_self) + } + + /// Get the length of the CSR in bytes. + pub fn get_csr_len(&self) -> u32 { + self.csr_len + } + + /// Check if the CSR was unprovisioned + pub fn is_unprovisioned(&self) -> bool { + self.csr_len == Self::UNPROVISIONED_CSR + } + } +} + impl Default for IdevIdCsr { fn default() -> Self { Self { @@ -164,6 +231,12 @@ pub struct PersistentData { pub idevid_csr: IdevIdCsr, reserved10: [u8; memory_layout::IDEVID_CSR_SIZE as usize - size_of::()], + + #[cfg(feature = "fmc")] + pub fmc_alias_csr: FmcAliasCsr, + + #[cfg(feature = "fmc")] + reserved11: [u8; memory_layout::FMC_ALIAS_CSR_SIZE as usize - size_of::()], } impl PersistentData { @@ -196,10 +269,29 @@ impl PersistentData { addr_of!((*P).idevid_csr) as u32, memory_layout::IDEVID_CSR_ORG ); + + assert_eq!( + addr_of!((*P).idevid_csr) as u32, + memory_layout::IDEVID_CSR_ORG + ); + + #[cfg(not(feature = "fmc"))] assert_eq!( P.add(1) as u32, memory_layout::IDEVID_CSR_ORG + memory_layout::IDEVID_CSR_SIZE ); + + #[cfg(feature = "fmc")] + assert_eq!( + addr_of!((*P).fmc_alias_csr) as u32, + memory_layout::FMC_ALIAS_CSR_ORG + ); + + #[cfg(feature = "fmc")] + assert_eq!( + P.add(1) as u32, + memory_layout::FMC_ALIAS_CSR_ORG + memory_layout::FMC_ALIAS_CSR_SIZE + ); } } } diff --git a/error/src/lib.rs b/error/src/lib.rs index 750a1611ba..67c2d9d3b5 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -448,6 +448,9 @@ impl CaliptraError { pub const RUNTIME_GET_IDEV_ID_UNSUPPORTED_ROM: CaliptraError = CaliptraError::new_const(0x000E0052); + pub const RUNTIME_GET_FMC_CSR_UNPROVISIONED: CaliptraError = + CaliptraError::new_const(0x000E0053); + /// FMC Errors pub const FMC_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000F0001); pub const FMC_GLOBAL_EXCEPTION: CaliptraError = CaliptraError::new_const(0x000F0002); @@ -464,6 +467,16 @@ impl CaliptraError { pub const FMC_GLOBAL_WDT_EXPIRED: CaliptraError = CaliptraError::new_const(0x000F000D); pub const FMC_UNKNOWN_RESET: CaliptraError = CaliptraError::new_const(0x000F000E); + /// FMC Alias CSR Errors + pub const FMC_ALIAS_CSR_BUILDER_INIT_FAILURE: CaliptraError = + CaliptraError::new_const(0x000F000F); + pub const FMC_ALIAS_CSR_BUILDER_BUILD_FAILURE: CaliptraError = + CaliptraError::new_const(0x000F0010); + pub const FMC_ALIAS_INVALID_CSR: CaliptraError = CaliptraError::new_const(0x000F0011); + pub const FMC_ALIAS_CSR_VERIFICATION_FAILURE: CaliptraError = + CaliptraError::new_const(0x000F0012); + pub const FMC_ALIAS_CSR_OVERFLOW: CaliptraError = CaliptraError::new_const(0x000F0013); + /// TRNG_EXT Errors pub const DRIVER_TRNG_EXT_TIMEOUT: CaliptraError = CaliptraError::new_const(0x00100001); diff --git a/fmc/Cargo.toml b/fmc/Cargo.toml index ce8cbb61f9..7de33fa57e 100644 --- a/fmc/Cargo.toml +++ b/fmc/Cargo.toml @@ -17,6 +17,7 @@ ufmt.workspace = true zerocopy.workspace = true caliptra-cfi-lib = { workspace = true, default-features = false, features = ["cfi", "cfi-counter" ] } caliptra-cfi-derive.workspace = true +zeroize.workspace = true [build-dependencies] @@ -41,3 +42,4 @@ itrng = ["caliptra-hw-model/itrng"] verilator = ["caliptra-hw-model/verilator"] fake-fmc = [] "hw-1.0" = ["caliptra-builder/hw-1.0", "caliptra-cpu/hw-1.0", "caliptra-drivers/hw-1.0", "caliptra-registers/hw-1.0"] +fmc_alias_csr = ["caliptra-drivers/fmc_alias_csr"] \ No newline at end of file diff --git a/fmc/build.sh b/fmc/build.sh index 0feabf5fd8..dce5b31441 100755 --- a/fmc/build.sh +++ b/fmc/build.sh @@ -9,4 +9,5 @@ cargo build \ --target riscv32imc-unknown-none-elf \ --profile=firmware \ --no-default-features \ + --features=fmc_alias_csr \ --bin=caliptra-fmc diff --git a/fmc/src/flow/crypto.rs b/fmc/src/flow/crypto.rs index 23e83edae9..60e6a4ca2b 100644 --- a/fmc/src/flow/crypto.rs +++ b/fmc/src/flow/crypto.rs @@ -5,7 +5,12 @@ File Name: Abstract: Crypto helper routines --*/ +use caliptra_x509::Ecdsa384Signature; + use crate::fmc_env::FmcEnv; +use caliptra_drivers::okmutref; +use zeroize::Zeroize; + use caliptra_cfi_derive::cfi_impl_fn; use caliptra_common::{crypto::Ecc384KeyPair, keyids::KEY_ID_TMP}; use caliptra_drivers::{ @@ -14,6 +19,21 @@ use caliptra_drivers::{ KeyWriteArgs, Sha256Alg, }; +pub trait Ecdsa384SignatureAdapter { + /// Convert to ECDSA Signature + fn to_ecdsa(&self) -> Ecdsa384Signature; +} + +impl Ecdsa384SignatureAdapter for Ecc384Signature { + /// Convert to ECDSA Signatuure + fn to_ecdsa(&self) -> Ecdsa384Signature { + Ecdsa384Signature { + r: (&self.r).into(), + s: (&self.s).into(), + } + } +} + pub enum Crypto {} impl Crypto { @@ -187,4 +207,35 @@ impl Crypto { let digest = okref(&digest)?; env.ecc384.verify(pub_key, digest, sig) } + + /// Sign the data using ECC Private Key. + /// Verify the signature using the ECC Public Key. + /// + /// This routine calculates the digest of the `data`, signs the hash and returns the signature. + /// This routine also verifies the signature using the public key. + /// + /// # Arguments + /// + /// * `env` - FMC Environment + /// * `priv_key` - Key slot to retrieve the private key + /// * `data` - Input data to hash + /// + /// # Returns + /// + /// * `Ecc384Signature` - Signature + #[inline(always)] + pub fn ecdsa384_sign_and_verify( + env: &mut FmcEnv, + priv_key: KeyId, + pub_key: &Ecc384PubKey, + data: &[u8], + ) -> CaliptraResult { + let mut digest = Self::sha384_digest(env, data); + let digest = okmutref(&mut digest)?; + let priv_key_args = KeyReadArgs::new(priv_key); + let priv_key = Ecc384PrivKeyIn::Key(priv_key_args); + let result = env.ecc384.sign(&priv_key, pub_key, digest, &mut env.trng); + digest.0.zeroize(); + result + } } diff --git a/fmc/src/flow/fmc_alias_csr.rs b/fmc/src/flow/fmc_alias_csr.rs new file mode 100644 index 0000000000..386ba45f81 --- /dev/null +++ b/fmc/src/flow/fmc_alias_csr.rs @@ -0,0 +1,123 @@ +// Licensed under the Apache-2.0 license + +use crate::flow::crypto::Crypto; +use crate::flow::dice::DiceOutput; +use crate::flow::x509::X509; +use crate::fmc_env::FmcEnv; +use crate::HandOff; +use caliptra_common::crypto::Ecc384KeyPair; + +use crate::flow::crypto::Ecdsa384SignatureAdapter; + +use zeroize::Zeroize; + +use caliptra_drivers::okmutref; +use caliptra_drivers::FmcAliasCsr; + +use caliptra_x509::FmcAliasCsrTbs; +use caliptra_x509::FmcAliasCsrTbsParams; + +use caliptra_drivers::{CaliptraError, CaliptraResult}; + +use caliptra_x509::Ecdsa384CsrBuilder; + +/// Retrieve DICE Output from HandOff +/// +/// # Arguments +/// +/// * `hand_off` - HandOff +/// +/// # Returns +/// +/// * `DiceInput` - DICE Layer Input +fn dice_output_from_hand_off(env: &mut FmcEnv) -> CaliptraResult { + let auth_pub = HandOff::fmc_pub_key(env); + let subj_sn = X509::subj_sn(env, &auth_pub)?; + let subj_key_id = X509::subj_key_id(env, &auth_pub)?; + // Create initial output + let output = DiceOutput { + cdi: HandOff::fmc_cdi(env), + subj_key_pair: Ecc384KeyPair { + priv_key: HandOff::fmc_priv_key(env), + pub_key: auth_pub, + }, + subj_sn, + subj_key_id, + }; + + Ok(output) +} + +fn write_csr_to_peristent_storage(env: &mut FmcEnv, csr: &FmcAliasCsr) -> CaliptraResult<()> { + let csr_persistent_mem = &mut env.persistent_data.get_mut().fmc_alias_csr; + + *csr_persistent_mem = csr.clone(); + + Ok(()) +} + +#[inline(always)] +pub fn generate_csr(env: &mut FmcEnv) -> CaliptraResult<()> { + dice_output_from_hand_off(env).and_then(|output| make_csr(env, &output)) +} + +/// Generate FMC Alias CSR +/// +/// # Arguments +/// +/// * `env` - FMC Environment +/// * `output` - DICE Output +// Inlined to reduce FMC size +#[inline(always)] +pub fn make_csr(env: &mut FmcEnv, output: &DiceOutput) -> CaliptraResult<()> { + let key_pair = &output.subj_key_pair; + + // CSR `To Be Signed` Parameters + let params = FmcAliasCsrTbsParams { + // Unique Endpoint Identifier + ueid: &X509::ueid(env)?, + + // Subject Name + subject_sn: &output.subj_sn, + + // Public Key + public_key: &key_pair.pub_key.to_der(), + }; + + // Generate the `To Be Signed` portion of the CSR + let tbs = FmcAliasCsrTbs::new(¶ms); + + // Sign the `To Be Signed` portion + let mut sig = + Crypto::ecdsa384_sign_and_verify(env, key_pair.priv_key, &key_pair.pub_key, tbs.tbs()); + let sig = okmutref(&mut sig)?; + + let _pub_x: [u8; 48] = key_pair.pub_key.x.into(); + let _pub_y: [u8; 48] = key_pair.pub_key.y.into(); + + let _sig_r: [u8; 48] = (&sig.r).into(); + let _sig_s: [u8; 48] = (&sig.s).into(); + + // Build the CSR with `To Be Signed` & `Signature` + let mut csr_buf = [0; caliptra_drivers::MAX_CSR_SIZE]; + let result = Ecdsa384CsrBuilder::new(tbs.tbs(), &sig.to_ecdsa()) + .ok_or(CaliptraError::FMC_ALIAS_CSR_BUILDER_INIT_FAILURE); + sig.zeroize(); + + let csr_bldr = result?; + let csr_len = csr_bldr + .build(&mut csr_buf) + .ok_or(CaliptraError::FMC_ALIAS_CSR_BUILDER_BUILD_FAILURE)?; + + if csr_len > csr_buf.len() { + return Err(CaliptraError::FMC_ALIAS_CSR_OVERFLOW); + } + + let fmc_alias_csr = FmcAliasCsr::new(&csr_buf, csr_len)?; + + let result = write_csr_to_peristent_storage(env, &fmc_alias_csr); + + csr_buf.zeroize(); + + result +} diff --git a/fmc/src/flow/mod.rs b/fmc/src/flow/mod.rs index 5e5d44712d..466dc1c518 100644 --- a/fmc/src/flow/mod.rs +++ b/fmc/src/flow/mod.rs @@ -14,11 +14,14 @@ Abstract: mod crypto; pub mod dice; +mod fmc_alias_csr; mod pcr; mod rt_alias; mod tci; mod x509; +use caliptra_drivers::ResetReason; + use crate::flow::rt_alias::RtAliasLayer; use crate::fmc_env::FmcEnv; @@ -30,5 +33,12 @@ use caliptra_drivers::CaliptraResult; /// /// * `env` - FMC Environment pub fn run(env: &mut FmcEnv) -> CaliptraResult<()> { + let reset_reason = env.soc_ifc.reset_reason(); + + if reset_reason == ResetReason::ColdReset { + // Generate the FMC Alias Certificate Signing Request (CSR) + fmc_alias_csr::generate_csr(env)?; + } + RtAliasLayer::run(env) } diff --git a/fmc/src/flow/rt_alias.rs b/fmc/src/flow/rt_alias.rs index 1e51ce9e72..a356375fd8 100644 --- a/fmc/src/flow/rt_alias.rs +++ b/fmc/src/flow/rt_alias.rs @@ -55,7 +55,6 @@ impl RtAliasLayer { // Derive CDI Self::derive_cdi(env, input.cdi, KEY_ID_RT_CDI)?; report_boot_status(FmcBootStatus::RtAliasDeriveCdiComplete as u32); - cprintln!("[alias rt] Derive Key Pair"); cprintln!( "[alias rt] Store priv key in slot 0x{:x}", KEY_ID_RT_PRIV_KEY as u8 @@ -100,18 +99,15 @@ impl RtAliasLayer { #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] #[inline(never)] pub fn run(env: &mut FmcEnv) -> CaliptraResult<()> { - cprintln!("[alias rt] Extend RT PCRs"); Self::extend_pcrs(env)?; cprintln!("[alias rt] Extend RT PCRs Done"); - cprintln!("[alias rt] Lock RT PCRs"); env.pcr_bank .set_pcr_lock(caliptra_common::RT_FW_CURRENT_PCR); env.pcr_bank .set_pcr_lock(caliptra_common::RT_FW_JOURNEY_PCR); cprintln!("[alias rt] Lock RT PCRs Done"); - cprintln!("[alias rt] Populate DV"); Self::populate_dv(env)?; cprintln!("[alias rt] Populate DV Done"); report_boot_status(crate::FmcBootStatus::RtMeasurementComplete as u32); diff --git a/fmc/src/hand_off.rs b/fmc/src/hand_off.rs index 6033fcdc1a..d80ab0734a 100644 --- a/fmc/src/hand_off.rs +++ b/fmc/src/hand_off.rs @@ -45,24 +45,14 @@ impl HandOff { /// Retrieve FMC CDI pub fn fmc_cdi(env: &FmcEnv) -> KeyId { - let ds: DataStore = - Self::fht(env) - .fmc_cdi_kv_hdl - .try_into() - .unwrap_or_else(|e: CaliptraError| { - cprintln!("[fht] Invalid CDI KV handle"); - handle_fatal_error(e.into()) - }); + let ds: DataStore = Self::fht(env) + .fmc_cdi_kv_hdl + .try_into() + .unwrap_or_else(|e: CaliptraError| handle_fatal_error(e.into())); match ds { - KeyVaultSlot(key_id) => { - cprintln!("[fht] Handoff : FMC CDI: {:?}", key_id as u8); - key_id - } - _ => { - cprintln!("[fht] Invalid KeySlot KV Entry"); - handle_fatal_error(CaliptraError::FMC_HANDOFF_INVALID_PARAM.into()) - } + KeyVaultSlot(key_id) => key_id, + _ => handle_fatal_error(CaliptraError::FMC_HANDOFF_INVALID_PARAM.into()), } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 2a70738d43..3669787260 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -11,7 +11,7 @@ caliptra-cfi-lib-git = { workspace = true, default-features = false, features = caliptra-cfi-derive-git.workspace = true caliptra_common = { workspace = true, default-features = false, features = ["runtime"] } caliptra-cpu.workspace = true -caliptra-drivers = { workspace = true, features = ["runtime"] } +caliptra-drivers = { workspace = true, features = ["fmc", "runtime"] } caliptra-error = { workspace = true, default-features = false } caliptra-image-types = { workspace = true, default-features = false } caliptra-auth-man-types = { workspace = true, default-features = false } diff --git a/runtime/src/drivers.rs b/runtime/src/drivers.rs index edc4f81cfb..3b306116aa 100644 --- a/runtime/src/drivers.rs +++ b/runtime/src/drivers.rs @@ -229,7 +229,7 @@ impl Drivers { } match result { Ok(_) => { - cprintln!("Disabled attestation due to DPE validation failure"); + cprintln!("Disabled attest : DPE valid fail"); // store specific validation error in CPTRA_FW_EXTENDED_ERROR_INFO drivers.soc_ifc.set_fw_extended_error(e.get_error_code()); caliptra_drivers::report_fw_error_non_fatal( @@ -259,9 +259,7 @@ impl Drivers { } match result { Ok(_) => { - cprintln!( - "Disabled attestation due to DPE used context limits being breached" - ); + cprintln!("Disable attest DPE used context limit breach"); caliptra_drivers::report_fw_error_non_fatal(e.into()); } Err(e) => { diff --git a/runtime/src/get_fmc_alias_csr.rs b/runtime/src/get_fmc_alias_csr.rs new file mode 100644 index 0000000000..94e9eeb391 --- /dev/null +++ b/runtime/src/get_fmc_alias_csr.rs @@ -0,0 +1,51 @@ +// Licensed under the Apache-2.0 license + +use crate::Drivers; + +use caliptra_cfi_derive_git::cfi_impl_fn; +use caliptra_cfi_lib_git::cfi_launder; + +use caliptra_common::{ + cprintln, + mailbox_api::{GetFmcAliasCsrReq, GetFmcAliasCsrResp, MailboxResp, MailboxRespHeader}, +}; +use caliptra_error::{CaliptraError, CaliptraResult}; + +use caliptra_drivers::{FmcAliasCsr, IdevIdCsr}; + +use zerocopy::{AsBytes, FromBytes}; + +pub struct GetFmcAliasCsrCmd; +impl GetFmcAliasCsrCmd { + // #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + #[inline(never)] + pub(crate) fn execute(drivers: &mut Drivers, cmd_args: &[u8]) -> CaliptraResult { + let csr_persistent_mem = &drivers.persistent_data.get().fmc_alias_csr; + + match csr_persistent_mem.get_csr_len() { + FmcAliasCsr::UNPROVISIONED_CSR => Err(CaliptraError::RUNTIME_GET_FMC_CSR_UNPROVISIONED), + len => { + let mut resp = GetFmcAliasCsrResp { + data_size: len, + ..Default::default() + }; + + let csr = csr_persistent_mem + .get() + .ok_or(CaliptraError::RUNTIME_GET_FMC_CSR_UNPROVISIONED)?; + + // NOTE: This code will not panic. + // + // csr is guranteed to be the same size as `len`, and therefore + // `resp.data_size` by the `FmcAliasCsr::get` API. + // + // A valid `IDevIDCsr` cannot be larger than `MAX_CSR_SIZE`, which is the max + // size of the buffer in `GetIdevCsrResp` + resp.data[..resp.data_size as usize].copy_from_slice(csr); + + Ok(MailboxResp::GetFmcAliasCsr(resp)) + } + _ => Err(CaliptraError::RUNTIME_INTERNAL), + } + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2e4fe0b862..b38924d315 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -22,6 +22,7 @@ mod dpe_crypto; mod dpe_platform; mod drivers; pub mod fips; +mod get_fmc_alias_csr; mod get_idev_csr; pub mod handoff; mod hmac; @@ -58,6 +59,7 @@ pub use fips::FipsShutdownCmd; pub use fips::{fips_self_test_cmd, fips_self_test_cmd::SelfTestStatus}; pub use populate_idev::PopulateIDevIdCertCmd; +pub use get_fmc_alias_csr::GetFmcAliasCsrCmd; pub use get_idev_csr::GetIdevCsrCmd; pub use info::{FwInfoCmd, IDevIdInfoCmd}; pub use invoke_dpe::InvokeDpeCmd; @@ -175,11 +177,7 @@ fn handle_command(drivers: &mut Drivers) -> CaliptraResult { let req_packet = Packet::copy_from_mbox(drivers)?; let cmd_bytes = req_packet.as_bytes()?; - cprintln!( - "[rt] Received command=0x{:x}, len={}", - req_packet.cmd, - req_packet.len - ); + cprintln!("[rt]cmd =0x{:x}, len={}", req_packet.cmd, req_packet.len); // Handle the request and generate the response let mut resp = match CommandId::from(req_packet.cmd) { @@ -229,6 +227,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::GET_FMC_ALIAS_CSR => GetFmcAliasCsrCmd::execute(drivers, cmd_bytes), _ => Err(CaliptraError::RUNTIME_UNIMPLEMENTED_COMMAND), }?; @@ -271,7 +270,7 @@ pub fn handle_mailbox_commands(drivers: &mut Drivers) -> CaliptraResult<()> { } match result { Ok(_) => { - cprintln!("Disabled attestation due to cmd busy during warm reset"); + cprintln!("Disabled attest - cmd busy + warm rst"); caliptra_drivers::report_fw_error_non_fatal( CaliptraError::RUNTIME_CMD_BUSY_DURING_WARM_RESET.into(), ); diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 6c844d30e6..8558a5cf92 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -28,14 +28,7 @@ use core::hint::black_box; #[cfg(feature = "std")] pub fn main() {} -const BANNER: &str = r#" - ____ _ _ _ ____ _____ - / ___|__ _| (_)_ __ | |_ _ __ __ _ | _ \_ _| -| | / _` | | | '_ \| __| '__/ _` | | |_) || | -| |__| (_| | | | |_) | |_| | | (_| | | _ < | | - \____\__,_|_|_| .__/ \__|_| \__,_| |_| \_\|_| - |_| -"#; +const BANNER: &str = r#"Caliptra RT"#; #[no_mangle] #[allow(clippy::empty_loop)] @@ -79,15 +72,15 @@ pub extern "C" fn entry_point() -> ! { } drivers.run_reset_flow().unwrap_or_else(|e| { - cprintln!("[rt] Runtime failed reset flow"); + cprintln!("[rt] failed reset flow"); handle_fatal_error(e.into()); }); if !drivers.persistent_data.get().fht.is_valid() { - cprintln!("[rt] Runtime can't load FHT"); + cprintln!("[rt] can't load FHT"); handle_fatal_error(caliptra_drivers::CaliptraError::RUNTIME_HANDOFF_FHT_NOT_LOADED.into()); } - cprintln!("[rt] Runtime listening for mailbox commands..."); + cprintln!("[rt] listening for commands..."); if let Err(e) = caliptra_runtime::handle_mailbox_commands(&mut drivers) { handle_fatal_error(e.into()); } diff --git a/runtime/tests/runtime_integration_tests/common.rs b/runtime/tests/runtime_integration_tests/common.rs index b3417d804b..cc50ac07d8 100644 --- a/runtime/tests/runtime_integration_tests/common.rs +++ b/runtime/tests/runtime_integration_tests/common.rs @@ -1,5 +1,6 @@ // Licensed under the Apache-2.0 license +use caliptra_api::mailbox::Request; use caliptra_api::SocManager; use caliptra_builder::{ firmware::{APP_WITH_UART, APP_WITH_UART_FPGA, FMC_WITH_UART}, @@ -268,6 +269,20 @@ pub fn assert_error( } } +pub fn get_certs(model: &mut DefaultHwModel) -> R::Resp { + let payload = MailboxReqHeader { + chksum: caliptra_common::checksum::calc_checksum(u32::from(R::ID), &[]), + }; + let resp_data = model + .mailbox_execute(u32::from(R::ID), payload.as_bytes()) + .unwrap() + .unwrap(); + assert!(resp_data.len() <= std::mem::size_of::<::Resp>()); + let mut resp = R::Resp::new_zeroed(); + resp.as_bytes_mut()[..resp_data.len()].copy_from_slice(&resp_data); + resp +} + pub fn get_fmc_alias_cert(model: &mut DefaultHwModel) -> GetFmcAliasCertResp { let payload = MailboxReqHeader { chksum: caliptra_common::checksum::calc_checksum( diff --git a/runtime/tests/runtime_integration_tests/main.rs b/runtime/tests/runtime_integration_tests/main.rs index 417946cda3..4384566326 100644 --- a/runtime/tests/runtime_integration_tests/main.rs +++ b/runtime/tests/runtime_integration_tests/main.rs @@ -8,6 +8,7 @@ mod test_certs; mod test_disable; mod test_ecdsa; mod test_fips; +mod test_get_fmc_alias_csr; mod test_get_idev_csr; mod test_info; mod test_invoke_dpe; diff --git a/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.der b/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.der new file mode 100644 index 0000000000..c20863f96c Binary files /dev/null and b/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.der differ diff --git a/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.txt b/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.txt new file mode 100644 index 0000000000..ce75c7c787 --- /dev/null +++ b/runtime/tests/runtime_integration_tests/test_data/fmc_alias_csr.txt @@ -0,0 +1,33 @@ +Certificate Request: + Data: + Version: 1 (0x0) + Subject: CN=Caliptra 1.0 FMC Alias/serialNumber=65F6BB3FAE445D0D0DB36F3DC84999B60B059CF63D3E25FE00C47B239857F3BB + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (384 bit) + pub: + 04:cb:0e:91:9a:d7:5f:9e:02:f2:39:8a:28:a9:3f: + 6d:f3:01:36:37:c8:5c:d3:26:26:77:71:88:93:5c: + 6c:e8:3a:c5:c4:bb:df:05:db:15:c1:38:97:f0:f5: + ca:e3:44:76:b4:e8:c5:b8:28:b9:82:8d:11:db:ca: + 79:92:4f:8c:1b:09:37:cb:d2:f5:3c:47:b9:dd:26: + 78:5b:d5:d8:11:96:52:6f:b7:d7:31:8f:9a:9f:e1: + cf:9d:95:b0:64:d5:39 + ASN1 OID: secp384r1 + NIST CURVE: P-384 + Attributes: + Requested Extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:5 + X509v3 Key Usage: critical + Certificate Sign + 2.23.133.5.4.4: + 0.................... + Signature Algorithm: ecdsa-with-SHA384 + Signature Value: + 30:65:02:31:00:cf:f3:d2:d8:30:86:5a:f7:f9:d4:3d:63:d3: + eb:c2:1e:16:f5:d1:3b:1e:98:38:ca:3d:8f:da:ed:9d:44:8f: + d5:f6:41:35:73:8a:14:05:91:63:17:58:94:4f:56:df:ea:02: + 30:4f:db:4a:2e:26:f2:aa:ec:74:41:e4:7e:c2:6a:c5:07:e9: + b3:50:52:bb:5c:68:4b:83:ea:07:33:09:84:0b:a1:b9:8a:9f: + d8:57:73:b0:3b:76:62:f3:4e:7f:32:9f:4f diff --git a/runtime/tests/runtime_integration_tests/test_get_fmc_alias_csr.rs b/runtime/tests/runtime_integration_tests/test_get_fmc_alias_csr.rs new file mode 100644 index 0000000000..517b5ef7ea --- /dev/null +++ b/runtime/tests/runtime_integration_tests/test_get_fmc_alias_csr.rs @@ -0,0 +1,90 @@ +// Licensed under the Apache-2.0 license + +use crate::common::get_certs; +use caliptra_api::mailbox::GetFmcAliasCsrReq; +use caliptra_api::SocManager; +use caliptra_builder::{get_ci_rom_version, CiRomVersion}; +use caliptra_common::mailbox_api::{CommandId, GetRtAliasCertReq, MailboxReqHeader}; +use caliptra_drivers::{FmcAliasCsr, MAX_CSR_SIZE}; +use caliptra_error::CaliptraError; +use caliptra_hw_model::DefaultHwModel; +use caliptra_hw_model::{HwModel, ModelError}; +use caliptra_runtime::RtBootStatus; +use zerocopy::AsBytes; + +use crate::common::{run_rt_test, RuntimeTestArgs}; + +#[test] +fn test_get_fmc_alias_csr() { + fn verify_rt_cert( + model: &mut DefaultHwModel, + pub_key: openssl::pkey::PKey, + ) { + let get_rt_alias_cert_resp = get_certs::(model); + assert_ne!(0, get_rt_alias_cert_resp.data_size); + + let der = &get_rt_alias_cert_resp.data[..get_rt_alias_cert_resp.data_size as usize]; + let cert = openssl::x509::X509::from_der(der).unwrap(); + + assert!( + cert.verify(&pub_key).unwrap(), + "Invalid public key. Unable to verify RT Alias Cert", + ); + } + fn get_fmc_alias_csr(model: &mut DefaultHwModel) -> openssl::x509::X509Req { + let get_fmc_alias_csr_resp = get_certs::(model); + + assert_ne!( + FmcAliasCsr::UNPROVISIONED_CSR, + get_fmc_alias_csr_resp.data_size + ); + assert_ne!(0, get_fmc_alias_csr_resp.data_size); + + let csr_der = &get_fmc_alias_csr_resp.data[..get_fmc_alias_csr_resp.data_size as usize]; + let csr = openssl::x509::X509Req::from_der(csr_der).unwrap(); + + assert_ne!([0; MAX_CSR_SIZE], csr_der); + + csr + } + let mut model = run_rt_test(RuntimeTestArgs::default()); + + let csr = get_fmc_alias_csr(&mut model); + + let pubkey = csr.public_key().unwrap(); + assert!( + csr.verify(&pubkey).unwrap(), + "Invalid public key. Unable to verify FMC Alias CSR", + ); + + verify_rt_cert(&mut model, pubkey); +} + +#[test] +fn test_missing_csr() { + let mut model = run_rt_test(RuntimeTestArgs::default()); + + model.step_until(|m| { + m.soc_ifc().cptra_boot_status().read() == u32::from(RtBootStatus::RtReadyForCommands) + }); + + let payload = MailboxReqHeader { + chksum: caliptra_common::checksum::calc_checksum(u32::from(CommandId::GET_IDEV_CSR), &[]), + }; + + let response = model + .mailbox_execute(CommandId::GET_IDEV_CSR.into(), payload.as_bytes()) + .unwrap_err(); + + match get_ci_rom_version() { + // 1.0 and 1.1 ROM do not support this feature + CiRomVersion::Rom1_0 | CiRomVersion::Rom1_1 => assert_eq!( + response, + ModelError::MailboxCmdFailed(CaliptraError::RUNTIME_GET_IDEV_ID_UNSUPPORTED_ROM.into()) + ), + _ => assert_eq!( + response, + ModelError::MailboxCmdFailed(CaliptraError::RUNTIME_GET_IDEV_ID_UNPROVISIONED.into()) + ), + }; +} diff --git a/test/tests/caliptra_integration_tests/fake_collateral_boot_test.rs b/test/tests/caliptra_integration_tests/fake_collateral_boot_test.rs index 1fbbfd4ae4..c8ecd12a34 100755 --- a/test/tests/caliptra_integration_tests/fake_collateral_boot_test.rs +++ b/test/tests/caliptra_integration_tests/fake_collateral_boot_test.rs @@ -93,14 +93,7 @@ fn fake_boot_test() { assert_output_contains(&output, "Running Caliptra ROM"); assert_output_contains(&output, "[fake-rom-cold-reset]"); assert_output_contains(&output, "Running Caliptra FMC"); - assert_output_contains( - &output, - r#" - / ___|__ _| (_)_ __ | |_ _ __ __ _ | _ \_ _| -| | / _` | | | '_ \| __| '__/ _` | | |_) || | -| |__| (_| | | | |_) | |_| | | (_| | | _ < | | - \____\__,_|_|_| .__/ \__|_| \__,_| |_| \_\|_|"#, - ); + assert_output_contains(&output, r#"Caliptra RT"#); let payload = MailboxReqHeader { chksum: caliptra_common::checksum::calc_checksum(u32::from(CommandId::GET_LDEV_CERT), &[]), diff --git a/test/tests/caliptra_integration_tests/smoke_test.rs b/test/tests/caliptra_integration_tests/smoke_test.rs index f5477f21fd..505ee1f799 100644 --- a/test/tests/caliptra_integration_tests/smoke_test.rs +++ b/test/tests/caliptra_integration_tests/smoke_test.rs @@ -235,7 +235,7 @@ fn smoke_test() { .unwrap(); if firmware::rom_from_env() == &firmware::ROM_WITH_UART { - hw.step_until_output_contains("[rt] Runtime listening for mailbox commands...\n") + hw.step_until_output_contains("[rt] listening for commands...\n") .unwrap(); let output = hw.output().take(usize::MAX); assert_output_contains(&output, "Running Caliptra ROM"); @@ -250,14 +250,7 @@ fn smoke_test() { assert_output_contains(&output, "[kat] LMS"); assert_output_contains(&output, "[kat] --"); assert_output_contains(&output, "Running Caliptra FMC"); - assert_output_contains( - &output, - r#" - / ___|__ _| (_)_ __ | |_ _ __ __ _ | _ \_ _| -| | / _` | | | '_ \| __| '__/ _` | | |_) || | -| |__| (_| | | | |_) | |_| | | (_| | | _ < | | - \____\__,_|_|_| .__/ \__|_| \__,_| |_| \_\|_|"#, - ); + assert_output_contains(&output, r#"Caliptra RT"#); } let ldev_cert_resp = hw.mailbox_execute_req(GetLdevCertReq::default()).unwrap(); @@ -788,7 +781,7 @@ fn test_rt_wdt_timeout() { hw.step_until_boot_status(RUNTIME_BOOT_STATUS_READY, true); let fmc_target = hw.output().sink().now(); - let rt_wdt_timeout_cycles = fmc_target - wdt_start - 5_000; + let rt_wdt_timeout_cycles = fmc_target - wdt_start - 2_000; drop(hw); let security_state = *caliptra_hw_model::SecurityState::default().set_debug_locked(true); diff --git a/x509/build/build.rs b/x509/build/build.rs index aa728c6eaa..37df7dfdf9 100644 --- a/x509/build/build.rs +++ b/x509/build/build.rs @@ -39,6 +39,7 @@ fn main() { let out_dir = out_dir_os_str.to_str().unwrap(); gen_init_devid_csr(out_dir); + gen_fmc_alias_csr(out_dir); gen_local_devid_cert(out_dir); gen_fmc_alias_cert(out_dir); gen_rt_alias_cert(out_dir); @@ -58,6 +59,18 @@ fn gen_init_devid_csr(out_dir: &str) { CodeGen::gen_code("InitDevIdCsrTbs", template, out_dir); } +#[cfg(feature = "generate_templates")] +fn gen_fmc_alias_csr(out_dir: &str) { + let mut usage = KeyUsage::default(); + usage.set_key_cert_sign(true); + let bldr = csr::CsrTemplateBuilder::::new() + .add_basic_constraints_ext(true, 5) + .add_key_usage_ext(usage) + .add_ueid_ext(&[0xFF; 17]); + let template = bldr.tbs_template("Caliptra 1.0 FMC Alias"); + CodeGen::gen_code("FmcAliasCsrTbs", template, out_dir); +} + /// Generate Local DeviceId Certificate Template #[cfg(feature = "generate_templates")] fn gen_local_devid_cert(out_dir: &str) { diff --git a/x509/build/csr.rs b/x509/build/csr.rs index 3293b71849..1752ac090a 100644 --- a/x509/build/csr.rs +++ b/x509/build/csr.rs @@ -8,7 +8,7 @@ File Name: Abstract: - File contains generation of X509 Certificate Signing Request (CSR) To Be Singed (TBS) + File contains generation of X509 Certificate Signing Request (CSR) To Be Signed (TBS) template that can be substituted at firmware runtime. --*/ diff --git a/x509/build/fmc_alias_csr_tbs.rs b/x509/build/fmc_alias_csr_tbs.rs new file mode 100644 index 0000000000..71d9afb0b3 --- /dev/null +++ b/x509/build/fmc_alias_csr_tbs.rs @@ -0,0 +1,89 @@ +#[doc = "++ + +Licensed under the Apache-2.0 license. + +Abstract: + + Regenerate the template by building caliptra-x509-build with the generate-templates flag. + +--"] +pub struct FmcAliasCsrTbsParams<'a> { + pub ueid: &'a [u8; 17usize], + pub public_key: &'a [u8; 97usize], + pub subject_sn: &'a [u8; 64usize], +} +impl<'a> FmcAliasCsrTbsParams<'a> { + pub const UEID_LEN: usize = 17usize; + pub const PUBLIC_KEY_LEN: usize = 97usize; + pub const SUBJECT_SN_LEN: usize = 64usize; +} +pub struct FmcAliasCsrTbs { + tbs: [u8; Self::TBS_TEMPLATE_LEN], +} +impl FmcAliasCsrTbs { + const UEID_OFFSET: usize = 308usize; + const PUBLIC_KEY_OFFSET: usize = 140usize; + const SUBJECT_SN_OFFSET: usize = 53usize; + const UEID_LEN: usize = 17usize; + const PUBLIC_KEY_LEN: usize = 97usize; + const SUBJECT_SN_LEN: usize = 64usize; + pub const TBS_TEMPLATE_LEN: usize = 325usize; + const TBS_TEMPLATE: [u8; Self::TBS_TEMPLATE_LEN] = [ + 48u8, 130u8, 1u8, 65u8, 2u8, 1u8, 0u8, 48u8, 108u8, 49u8, 31u8, 48u8, 29u8, 6u8, 3u8, 85u8, + 4u8, 3u8, 12u8, 22u8, 67u8, 97u8, 108u8, 105u8, 112u8, 116u8, 114u8, 97u8, 32u8, 49u8, + 46u8, 48u8, 32u8, 70u8, 77u8, 67u8, 32u8, 65u8, 108u8, 105u8, 97u8, 115u8, 49u8, 73u8, + 48u8, 71u8, 6u8, 3u8, 85u8, 4u8, 5u8, 19u8, 64u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 48u8, 118u8, 48u8, + 16u8, 6u8, 7u8, 42u8, 134u8, 72u8, 206u8, 61u8, 2u8, 1u8, 6u8, 5u8, 43u8, 129u8, 4u8, 0u8, + 34u8, 3u8, 98u8, 0u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 160u8, 86u8, 48u8, 84u8, + 6u8, 9u8, 42u8, 134u8, 72u8, 134u8, 247u8, 13u8, 1u8, 9u8, 14u8, 49u8, 71u8, 48u8, 69u8, + 48u8, 18u8, 6u8, 3u8, 85u8, 29u8, 19u8, 1u8, 1u8, 255u8, 4u8, 8u8, 48u8, 6u8, 1u8, 1u8, + 255u8, 2u8, 1u8, 5u8, 48u8, 14u8, 6u8, 3u8, 85u8, 29u8, 15u8, 1u8, 1u8, 255u8, 4u8, 4u8, + 3u8, 2u8, 2u8, 4u8, 48u8, 31u8, 6u8, 6u8, 103u8, 129u8, 5u8, 5u8, 4u8, 4u8, 4u8, 21u8, + 48u8, 19u8, 4u8, 17u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + 95u8, 95u8, 95u8, 95u8, 95u8, 95u8, + ]; + pub fn new(params: &FmcAliasCsrTbsParams) -> Self { + let mut template = Self { + tbs: Self::TBS_TEMPLATE, + }; + template.apply(params); + template + } + pub fn sign( + &self, + sign_fn: impl Fn(&[u8]) -> Result, + ) -> Result { + sign_fn(&self.tbs) + } + pub fn tbs(&self) -> &[u8] { + &self.tbs + } + fn apply(&mut self, params: &FmcAliasCsrTbsParams) { + #[inline(always)] + fn apply_slice( + buf: &mut [u8; 325usize], + val: &[u8; LEN], + ) { + buf[OFFSET..OFFSET + LEN].copy_from_slice(val); + } + apply_slice::<{ Self::UEID_OFFSET }, { Self::UEID_LEN }>(&mut self.tbs, params.ueid); + apply_slice::<{ Self::PUBLIC_KEY_OFFSET }, { Self::PUBLIC_KEY_LEN }>( + &mut self.tbs, + params.public_key, + ); + apply_slice::<{ Self::SUBJECT_SN_OFFSET }, { Self::SUBJECT_SN_LEN }>( + &mut self.tbs, + params.subject_sn, + ); + } +} diff --git a/x509/src/fmc_alias_csr.rs b/x509/src/fmc_alias_csr.rs new file mode 100644 index 0000000000..99f0bc3043 --- /dev/null +++ b/x509/src/fmc_alias_csr.rs @@ -0,0 +1,177 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + idevid_csr.rs + +Abstract: + + Initial Device ID Certificate Signing Request related code. + +--*/ + +// Note: All the necessary code is auto generated +#[cfg(feature = "generate_templates")] +include!(concat!(env!("OUT_DIR"), "/fmc_alias_csr_tbs.rs")); +#[cfg(not(feature = "generate_templates"))] +include! {"../build/fmc_alias_csr_tbs.rs"} + +#[cfg(all(test, target_family = "unix"))] +mod tests { + use openssl::sha::Sha384; + use openssl::{ecdsa::EcdsaSig, x509::X509Req}; + + use x509_parser::cri_attributes::ParsedCriAttribute; + use x509_parser::extensions::ParsedExtension; + use x509_parser::oid_registry::asn1_rs::oid; + use x509_parser::prelude::{FromDer, X509CertificationRequest}; + + use super::*; + use crate::test_util::tests::*; + use crate::{Ecdsa384CsrBuilder, Ecdsa384Signature}; + + const TEST_UEID: &[u8] = &[0xAB; FmcAliasCsrTbs::UEID_LEN]; + + fn make_test_csr(subject_key: &Ecc384AsymKey) -> FmcAliasCsrTbs { + let params = FmcAliasCsrTbsParams { + public_key: &subject_key.pub_key().try_into().unwrap(), + subject_sn: &subject_key.hex_str().into_bytes().try_into().unwrap(), + ueid: &TEST_UEID.try_into().unwrap(), + }; + + FmcAliasCsrTbs::new(¶ms) + } + + #[test] + fn test_csr_signing() { + let key = Ecc384AsymKey::default(); + let ec_key = key.priv_key().ec_key().unwrap(); + let csr = make_test_csr(&key); + + let sig: EcdsaSig = csr + .sign(|b| { + let mut sha = Sha384::new(); + sha.update(b); + EcdsaSig::sign(&sha.finish(), &ec_key) + }) + .unwrap(); + + assert_ne!(csr.tbs(), FmcAliasCsrTbs::TBS_TEMPLATE); + assert_eq!( + &csr.tbs()[FmcAliasCsrTbs::PUBLIC_KEY_OFFSET + ..FmcAliasCsrTbs::PUBLIC_KEY_OFFSET + FmcAliasCsrTbs::PUBLIC_KEY_LEN], + key.pub_key(), + ); + assert_eq!( + &csr.tbs()[FmcAliasCsrTbs::SUBJECT_SN_OFFSET + ..FmcAliasCsrTbs::SUBJECT_SN_OFFSET + FmcAliasCsrTbs::SUBJECT_SN_LEN], + key.hex_str().into_bytes(), + ); + assert_eq!( + &csr.tbs()[FmcAliasCsrTbs::UEID_OFFSET + ..FmcAliasCsrTbs::UEID_OFFSET + FmcAliasCsrTbs::UEID_LEN], + TEST_UEID, + ); + + let ecdsa_sig = crate::Ecdsa384Signature { + r: sig.r().to_vec_padded(48).unwrap().try_into().unwrap(), + s: sig.s().to_vec_padded(48).unwrap().try_into().unwrap(), + }; + + let builder = crate::Ecdsa384CsrBuilder::new(csr.tbs(), &ecdsa_sig).unwrap(); + let mut buf = vec![0u8; builder.len()]; + builder.build(&mut buf).unwrap(); + + let req: X509Req = X509Req::from_der(&buf).unwrap(); + assert!(req.verify(&req.public_key().unwrap()).unwrap()); + assert!(req.verify(key.priv_key()).unwrap()); + } + + #[test] + fn test_extensions() { + let key = Ecc384AsymKey::default(); + let ec_key = key.priv_key().ec_key().unwrap(); + let csr = make_test_csr(&key); + + let sig: EcdsaSig = csr + .sign(|b| { + let mut sha = Sha384::new(); + sha.update(b); + EcdsaSig::sign(&sha.finish(), &ec_key) + }) + .unwrap(); + + let ecdsa_sig = Ecdsa384Signature { + r: sig.r().to_vec_padded(48).unwrap().try_into().unwrap(), + s: sig.s().to_vec_padded(48).unwrap().try_into().unwrap(), + }; + + let builder = Ecdsa384CsrBuilder::new(csr.tbs(), &ecdsa_sig).unwrap(); + let mut buf = vec![0u8; builder.len()]; + builder.build(&mut buf).unwrap(); + + let (_, parsed_csr) = X509CertificationRequest::from_der(&buf).unwrap(); + + let requested_extensions = parsed_csr + .certification_request_info + .iter_attributes() + .find_map(|attr| { + if let ParsedCriAttribute::ExtensionRequest(requested) = attr.parsed_attribute() { + Some(&requested.extensions) + } else { + None + } + }) + .unwrap(); + + // BasicConstraints + let bc_ext = requested_extensions + .iter() + .find(|ext| matches!(ext.parsed_extension(), ParsedExtension::BasicConstraints(_))) + .unwrap(); + let ParsedExtension::BasicConstraints(bc) = bc_ext.parsed_extension() else { + panic!("Extension is not BasicConstraints"); + }; + + assert!(bc_ext.critical); + assert!(bc.ca); + + // KeyUsage + let ku_ext = requested_extensions + .iter() + .find(|ext| matches!(ext.parsed_extension(), ParsedExtension::KeyUsage(_))) + .unwrap(); + + assert!(ku_ext.critical); + + // UEID + let ueid_ext = requested_extensions + .iter() + .find(|ext| { + if let ParsedExtension::UnsupportedExtension { oid } = ext.parsed_extension() { + oid == &oid!(2.23.133 .5 .4 .4) + } else { + false + } + }) + .unwrap(); + assert!(!ueid_ext.critical); + } + + #[test] + #[cfg(feature = "generate_templates")] + fn test_idevid_template() { + let manual_template = + std::fs::read(std::path::Path::new("./build/init_dev_id_csr_tbs.rs")).unwrap(); + let auto_generated_template = std::fs::read(std::path::Path::new(concat!( + env!("OUT_DIR"), + "/init_dev_id_csr_tbs.rs" + ))) + .unwrap(); + if auto_generated_template != manual_template { + panic!("Auto-generated IDevID CSR template is not equal to the manual template.") + } + } +} diff --git a/x509/src/lib.rs b/x509/src/lib.rs index a0be131433..b36dd2fcbf 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -16,6 +16,7 @@ Abstract: mod cert_bldr; mod fmc_alias_cert; +mod fmc_alias_csr; mod idevid_csr; mod ldevid_cert; mod rt_alias_cert; @@ -23,6 +24,7 @@ mod test_util; pub use cert_bldr::{Ecdsa384CertBuilder, Ecdsa384CsrBuilder, Ecdsa384Signature}; pub use fmc_alias_cert::{FmcAliasCertTbs, FmcAliasCertTbsParams}; +pub use fmc_alias_csr::{FmcAliasCsrTbs, FmcAliasCsrTbsParams}; pub use idevid_csr::{InitDevIdCsrTbs, InitDevIdCsrTbsParams}; pub use ldevid_cert::{LocalDevIdCertTbs, LocalDevIdCertTbsParams}; pub use rt_alias_cert::{RtAliasCertTbs, RtAliasCertTbsParams};