diff --git a/Cargo.lock b/Cargo.lock index 981b5b16ff..ad85d64611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,7 @@ dependencies = [ "openssl", "ufmt", "zerocopy", + "zeroize", ] [[package]] diff --git a/api/src/mailbox.rs b/api/src/mailbox.rs index 28034e8bec..ad3185a49a 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 { @@ -153,6 +156,7 @@ pub enum MailboxResp { CertifyKeyExtended(CertifyKeyExtendedResp), AuthorizeAndStash(AuthorizeAndStashResp), GetIdevCsr(GetIdevCsrResp), + GetFmcAliasCsr(GetFmcAliasCsrResp), } impl MailboxResp { @@ -174,6 +178,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()), } } @@ -195,6 +200,7 @@ impl MailboxResp { MailboxResp::CertifyKeyExtended(resp) => Ok(resp.as_mut_bytes()), MailboxResp::AuthorizeAndStash(resp) => Ok(resp.as_mut_bytes()), MailboxResp::GetIdevCsr(resp) => Ok(resp.as_mut_bytes()), + MailboxResp::GetFmcAliasCsr(resp) => Ok(resp.as_mut_bytes()), } } @@ -1010,6 +1016,41 @@ impl Default for GetIdevCsrResp { } } +// GET_FMC_ALIAS_CSR +#[repr(C)] +#[derive(Default, Debug, IntoBytes, FromBytes, KnownLayout, Immutable, 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, IntoBytes, FromBytes, KnownLayout, Immutable, 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 8ae0376696..8c804df09a 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 = 21 * 1024; pub const RUNTIME_ORG: u32 = FMC_ORG + FMC_SIZE; -pub const RUNTIME_SIZE: u32 = 97 * 1024; +pub const RUNTIME_SIZE: u32 = 96 * 1024; pub use memory_layout::{DATA_ORG, PERSISTENT_DATA_ORG}; pub use wdt::{restart_wdt, start_wdt, stop_wdt, WdtTimeout}; diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index b663bf8087..cd374b32e0 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -86,8 +86,10 @@ pub use okref::okmutref; pub use okref::okref; pub use pcr_bank::{PcrBank, PcrId}; pub use pcr_reset::PcrResetCounter; +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/persistent.rs b/drivers/src/persistent.rs index 9ad3b372d2..49c5cb2338 100644 --- a/drivers/src/persistent.rs +++ b/drivers/src/persistent.rs @@ -21,6 +21,8 @@ use crate::{ FirmwareHandoffTable, }; +use crate::FmcAliasCsr; + #[cfg(feature = "runtime")] use crate::pcr_reset::PcrResetCounter; @@ -38,7 +40,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 RESERVED_MEMORY_SIZE: u32 = 4 * 1024; +pub const FMC_ALIAS_CSR_SIZE: u32 = 1024; +pub const RESERVED_MEMORY_SIZE: u32 = 3 * 1024; pub const PCR_LOG_MAX_COUNT: usize = 17; pub const FUSE_LOG_MAX_COUNT: usize = 62; @@ -61,6 +64,7 @@ pub type AuthManifestImageMetadataList = [AuthManifestImageMetadata; AUTH_MANIFEST_IMAGE_METADATA_MAX_COUNT]; const _: () = assert!(MAX_CSR_SIZE < IDEVID_CSR_SIZE as usize); +const _: () = assert!(MAX_CSR_SIZE < FMC_ALIAS_CSR_SIZE as usize); #[derive(Clone, TryFromBytes, IntoBytes, Zeroize)] #[repr(C)] @@ -69,6 +73,69 @@ pub struct IdevIdCsr { csr: [u8; MAX_CSR_SIZE], } +pub mod fmc_alias_csr { + use super::*; + + const _: () = assert!(size_of::() < FMC_ALIAS_CSR_SIZE as usize); + + #[derive(Clone, TryFromBytes, IntoBytes, 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 { @@ -193,6 +260,10 @@ pub struct PersistentData { pub idevid_csr: IdevIdCsr, reserved10: [u8; IDEVID_CSR_SIZE as usize - size_of::()], + pub fmc_alias_csr: FmcAliasCsr, + + reserved11: [u8; FMC_ALIAS_CSR_SIZE as usize - size_of::()], + // Reserved memory for future objects. // New objects should always source memory from this range. // Taking memory from this reserve does NOT break hitless updates. @@ -282,6 +353,12 @@ impl PersistentData { ); persistent_data_offset += IDEVID_CSR_SIZE; + assert_eq!( + addr_of!((*P).fmc_alias_csr) as u32, + memory_layout::PERSISTENT_DATA_ORG + persistent_data_offset + ); + + persistent_data_offset += FMC_ALIAS_CSR_SIZE; assert_eq!( addr_of!((*P).reserved_memory) as u32, memory_layout::PERSISTENT_DATA_ORG + persistent_data_offset diff --git a/error/src/lib.rs b/error/src/lib.rs index 2bd9d10957..8cbcbb449f 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -450,6 +450,12 @@ impl CaliptraError { pub const RUNTIME_AUTH_MANIFEST_IMAGE_METADATA_LIST_DUPLICATE_FIRMWARE_ID: CaliptraError = CaliptraError::new_const(0x000E0053); + pub const RUNTIME_GET_FMC_CSR_UNPROVISIONED: CaliptraError = + CaliptraError::new_const(0x000E0054); + + pub const RUNTIME_GET_FMC_CSR_UNSUPPORTED_FMC: CaliptraError = + CaliptraError::new_const(0x000E0055); + /// FMC Errors pub const FMC_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000F0001); pub const FMC_GLOBAL_EXCEPTION: CaliptraError = CaliptraError::new_const(0x000F0002); @@ -466,6 +472,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..0e020784bc 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] diff --git a/fmc/src/flow/crypto.rs b/fmc/src/flow/crypto.rs index 23e83edae9..9251ec1b30 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,36 @@ 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 + /// * `pub_key` - Public key to verify with + /// * `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..dccb688e28 --- /dev/null +++ b/fmc/src/flow/fmc_alias_csr.rs @@ -0,0 +1,122 @@ +// 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 +/// +/// * `env` - FMC Environment +/// +/// # 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) { + let csr_persistent_mem = &mut env.persistent_data.get_mut().fmc_alias_csr; + + *csr_persistent_mem = csr.clone(); +} + +#[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)?; + + write_csr_to_peristent_storage(env, &fmc_alias_csr); + + csr_buf.zeroize(); + + Ok(()) +} diff --git a/fmc/src/flow/mod.rs b/fmc/src/flow/mod.rs index 5e5d44712d..1b11ef7c71 100644 --- a/fmc/src/flow/mod.rs +++ b/fmc/src/flow/mod.rs @@ -14,6 +14,7 @@ Abstract: mod crypto; pub mod dice; +mod fmc_alias_csr; mod pcr; mod rt_alias; mod tci; @@ -30,5 +31,18 @@ use caliptra_drivers::CaliptraResult; /// /// * `env` - FMC Environment pub fn run(env: &mut FmcEnv) -> CaliptraResult<()> { + { + use caliptra_cfi_lib::cfi_assert_eq; + use caliptra_drivers::ResetReason; + + let reset_reason = env.soc_ifc.reset_reason(); + + if reset_reason == ResetReason::ColdReset { + cfi_assert_eq(env.soc_ifc.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..50aebd4a9e 100644 --- a/fmc/src/flow/rt_alias.rs +++ b/fmc/src/flow/rt_alias.rs @@ -8,7 +8,7 @@ File Name: Abstract: - Alias RT DICE Layer & PCR extension + aliasrt DICE Layer & PCR extension --*/ use caliptra_cfi_derive::cfi_impl_fn; @@ -49,21 +49,21 @@ impl RtAliasLayer { return Err(CaliptraError::FMC_ALIAS_KV_COLLISION); } - cprintln!("[alias rt] Derive CDI"); - cprintln!("[alias rt] Store in in slot 0x{:x}", KEY_ID_RT_CDI as u8); + cprintln!("[aliasrt] Derive CDI"); + cprintln!("[aliasrt] Store in in slot 0x{:x}", KEY_ID_RT_CDI as u8); // 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!("[aliasrt] Derive Key Pair"); cprintln!( - "[alias rt] Store priv key in slot 0x{:x}", + "[aliasrt] Store priv key in slot 0x{:x}", KEY_ID_RT_PRIV_KEY as u8 ); // Derive DICE Key Pair from CDI let key_pair = Self::derive_key_pair(env, KEY_ID_RT_CDI, KEY_ID_RT_PRIV_KEY)?; - cprintln!("[alias rt] Derive Key Pair - Done"); + cprintln!("[aliasrt] Derive Key Pair - Done"); report_boot_status(FmcBootStatus::RtAliasKeyPairDerivationComplete as u32); // Generate the Subject Serial Number and Subject Key Identifier. @@ -100,20 +100,17 @@ 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!("[aliasrt] 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!("[aliasrt] Lock RT PCRs Done"); - cprintln!("[alias rt] Populate DV"); Self::populate_dv(env)?; - cprintln!("[alias rt] Populate DV Done"); + cprintln!("[aliasrt] Populate DV Done"); report_boot_status(crate::FmcBootStatus::RtMeasurementComplete as u32); // Retrieve Dice Input Layer from Hand Off and Derive Key @@ -173,7 +170,7 @@ impl RtAliasLayer { } ResetReason::WarmReset => { cfi_assert_eq(reset_reason, ResetReason::WarmReset); - cprintln!("[alias rt : skip pcr extension"); + cprintln!("[aliasrt :skip pcr extension"); Ok(()) } ResetReason::Unknown => { @@ -329,7 +326,7 @@ impl RtAliasLayer { // Sign the `To Be Signed` portion cprintln!( - "[alias rt] Signing Cert with AUTHO + "[aliasrt] Signing Cert with AUTHO RITY.KEYID = {}", auth_priv_key as u8 ); @@ -341,7 +338,7 @@ impl RtAliasLayer { let sig = okref(&sig)?; // Clear the authority private key cprintln!( - "[alias rt] Erasing AUTHORITY.KEYID = {}", + "[aliasrt] Erasing AUTHORITY.KEYID = {}", auth_priv_key as u8 ); // FMC ensures that CDIFMC and PrivateKeyFMC are locked to block further usage until the next boot. @@ -350,13 +347,13 @@ impl RtAliasLayer { let _pub_x: [u8; 48] = (&pub_key.x).into(); let _pub_y: [u8; 48] = (&pub_key.y).into(); - cprintln!("[alias rt] PUB.X = {}", HexBytes(&_pub_x)); - cprintln!("[alias rt] PUB.Y = {}", HexBytes(&_pub_y)); + cprintln!("[aliasrt] PUB.X = {}", HexBytes(&_pub_x)); + cprintln!("[aliasrt] PUB.Y = {}", HexBytes(&_pub_y)); let _sig_r: [u8; 48] = (&sig.r).into(); let _sig_s: [u8; 48] = (&sig.s).into(); - cprintln!("[alias rt] SIG.R = {}", HexBytes(&_sig_r)); - cprintln!("[alias rt] SIG.S = {}", HexBytes(&_sig_s)); + cprintln!("[aliasrt] SIG.R = {}", HexBytes(&_sig_r)); + cprintln!("[aliasrt] SIG.S = {}", HexBytes(&_sig_s)); // Verify the signature of the `To Be Signed` portion if Crypto::ecdsa384_verify(env, auth_pub_key, tbs.tbs(), sig)? != Ecc384Result::Success { 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..bd8ec23e8c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -64,4 +64,4 @@ fips_self_test=[] no-cfi = ["caliptra-image-verify/no-cfi", "caliptra-drivers/no-cfi"] fpga_realtime = ["caliptra-drivers/fpga_realtime"] "hw-1.0" = ["caliptra-builder/hw-1.0", "caliptra-drivers/hw-1.0", "caliptra-registers/hw-1.0", "caliptra-kat/hw-1.0","caliptra-cpu/hw-1.0"] -fips-test-hooks = ["caliptra-drivers/fips-test-hooks"] \ No newline at end of file +fips-test-hooks = ["caliptra-drivers/fips-test-hooks"] diff --git a/runtime/src/drivers.rs b/runtime/src/drivers.rs index 107aa4fc21..8769a0e3cd 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..0f60901285 --- /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::{FromBytes, IntoBytes}; + +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), + 0 => Err(CaliptraError::RUNTIME_GET_FMC_CSR_UNSUPPORTED_FMC), + 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 `FmcAliasCsr` 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)) + } + } + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index aa802cedb2..a6af89223d 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), }; let resp = okmutref(&mut resp)?; @@ -272,7 +271,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/tests/runtime_integration_tests/common.rs b/runtime/tests/runtime_integration_tests/common.rs index 020df5fc08..1e1bdd8eeb 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}, @@ -34,7 +35,7 @@ use openssl::{ x509::{X509Builder, X509}, x509::{X509Name, X509NameBuilder}, }; -use zerocopy::{FromBytes, IntoBytes}; +use zerocopy::{FromBytes, FromZeros, IntoBytes}; pub const TEST_LABEL: [u8; 48] = [ 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, @@ -276,6 +277,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_mut_bytes()[..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_get_fmc_alias_csr.rs b/runtime/tests/runtime_integration_tests/test_get_fmc_alias_csr.rs new file mode 100644 index 0000000000..e411452b76 --- /dev/null +++ b/runtime/tests/runtime_integration_tests/test_get_fmc_alias_csr.rs @@ -0,0 +1,55 @@ +// Licensed under the Apache-2.0 license + +use crate::common::get_certs; +use caliptra_api::mailbox::GetFmcAliasCsrReq; +use caliptra_common::mailbox_api::GetRtAliasCertReq; +use caliptra_drivers::{FmcAliasCsr, MAX_CSR_SIZE}; +use caliptra_hw_model::DefaultHwModel; + +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); +} diff --git a/test/tests/caliptra_integration_tests/smoke_test.rs b/test/tests/caliptra_integration_tests/smoke_test.rs index 4d1a7562ff..181c05249a 100644 --- a/test/tests/caliptra_integration_tests/smoke_test.rs +++ b/test/tests/caliptra_integration_tests/smoke_test.rs @@ -237,6 +237,7 @@ fn smoke_test() { if firmware::rom_from_env() == &firmware::ROM_WITH_UART { hw.step_until_output_contains("[rt] Runtime listening for mailbox commands...\n") .unwrap(); + let output = hw.output().take(usize::MAX); assert_output_contains(&output, "Running Caliptra ROM"); assert_output_contains(&output, "[cold-reset]"); 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..2835cb992e --- /dev/null +++ b/x509/src/fmc_alias_csr.rs @@ -0,0 +1,177 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + fmc_alis_csr.rs + +Abstract: + + FMC Alias CSR 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};