From f2b4f4ca67c6d984c3c2de16c4d32ffce395a229 Mon Sep 17 00:00:00 2001 From: Carl Lundin Date: Mon, 13 Jan 2025 13:42:19 -0800 Subject: [PATCH] Refactor certificate generation code in CertifyKey to common code so it can be used in DeriveContext. --- crypto/src/lib.rs | 66 +++++++ crypto/src/openssl.rs | 106 +++++++++--- crypto/src/rustcrypto.rs | 102 ++++++++--- dpe/src/commands/certify_key.rs | 176 ++----------------- dpe/src/x509.rs | 294 +++++++++++++++++++++++++++++++- 5 files changed, 533 insertions(+), 211 deletions(-) diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 33d9b348..d5ef52d2 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -170,6 +170,20 @@ pub trait Crypto { info: &[u8], ) -> Result; + /// Derive a CDI for an exported private key based on the current base CDI and measurements + /// + /// # Arguments + /// + /// * `algs` - Which length of algorithms to use. + /// * `measurement` - A digest of the measurements which should be used for CDI derivation + /// * `info` - Caller-supplied info string to use in CDI derivation + fn derive_exported_cdi( + &mut self, + algs: AlgLen, + measurement: &Digest, + info: &[u8], + ) -> Result; + /// CFI wrapper around derive_cdi /// /// To implement this function, you need to add the @@ -182,6 +196,28 @@ pub trait Crypto { info: &[u8], ) -> Result; + /// CFI wrapper around derive_cdi_exported + /// + /// To implement this function, you need to add the + /// cfi_impl_fn proc_macro to derive_exported_cdi. + #[cfg(not(feature = "no-cfi"))] + fn __cfi_derive_exported_cdi( + &mut self, + algs: AlgLen, + measurement: &Digest, + info: &[u8], + ) -> Result; + + /// Retrieves the CDI for an exported private key. + fn get_exported_cdi(&mut self) -> Result; + + /// CFI wrapper around get_exported_cdi + /// + /// To implement this function, you need to add the + /// cfi_impl_fn proc_macro to derive_cdi. + #[cfg(not(feature = "no-cfi"))] + fn __cfi_get_exported_cdi(&mut self) -> Result; + /// Derives a key pair using a cryptographically secure KDF /// /// # Arguments @@ -199,6 +235,23 @@ pub trait Crypto { info: &[u8], ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError>; + /// Derives an exported key pair using a cryptographically secure KDF + /// + /// # Arguments + /// + /// * `algs` - Which length of algorithms to use. + /// * `cdi` - Caller-supplied private key to use in public key derivation + /// * `label` - Caller-supplied label to use in asymmetric key derivation + /// * `info` - Caller-supplied info string to use in asymmetric key derivation + /// + fn derive_key_pair_exported( + &mut self, + algs: AlgLen, + cdi: &Self::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError>; + /// CFI wrapper around derive_key_pair /// /// To implement this function, you need to add the @@ -212,6 +265,19 @@ pub trait Crypto { info: &[u8], ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError>; + /// CFI wrapper around derive_key_pair_exported + /// + /// To implement this function, you need to add the + /// cfi_impl_fn proc_macro to derive_key_pair. + #[cfg(not(feature = "no-cfi"))] + fn __cfi_derive_key_pair_exported( + &mut self, + algs: AlgLen, + cdi: &Self::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError>; + /// Sign `digest` with the platform Alias Key /// /// # Arguments diff --git a/crypto/src/openssl.rs b/crypto/src/openssl.rs index f1bba622..1e303a9f 100644 --- a/crypto/src/openssl.rs +++ b/crypto/src/openssl.rs @@ -42,22 +42,31 @@ impl Hasher for OpensslHasher { } #[cfg(feature = "deterministic_rand")] -pub struct OpensslCrypto(StdRng); +pub struct OpensslCrypto { + rng: StdRng, + // Cached CDI for exported CDI operations. + exported_cdi: Option>, +} #[cfg(not(feature = "deterministic_rand"))] -pub struct OpensslCrypto; +pub struct OpensslCrypto { + exported_cdi: Option>, +} impl OpensslCrypto { #[cfg(feature = "deterministic_rand")] pub fn new() -> Self { const SEED: [u8; 32] = [1; 32]; let seeded_rng = StdRng::from_seed(SEED); - OpensslCrypto(seeded_rng) + OpensslCrypto { + rng: seeded_rng, + exported_cdi: None, + } } #[cfg(not(feature = "deterministic_rand"))] pub fn new() -> Self { - Self {} + Self { exported_cdi: None } } fn get_digest(algs: AlgLen) -> MessageDigest { @@ -90,6 +99,35 @@ impl OpensslCrypto { EcKey::from_private_components(&group, priv_key_bn, &pub_point) } + + fn derive_key_pair_inner( + &mut self, + algs: AlgLen, + cdi: &::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(::PrivKey, EcdsaPub), CryptoError> { + let priv_key = hkdf_get_priv_key(algs, cdi, label, info)?; + + let ec_priv_key = OpensslCrypto::ec_key_from_priv_key(algs, &priv_key)?; + let nid = OpensslCrypto::get_curve(algs); + + let group = EcGroup::from_curve_name(nid).unwrap(); + let mut bn_ctx = BigNumContext::new().unwrap(); + + let mut x = BigNum::new().unwrap(); + let mut y = BigNum::new().unwrap(); + + ec_priv_key + .public_key() + .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) + .unwrap(); + + let x = CryptoBuf::new(&x.to_vec_padded(algs.size() as i32).unwrap()).unwrap(); + let y = CryptoBuf::new(&y.to_vec_padded(algs.size() as i32).unwrap()).unwrap(); + + Ok((priv_key, EcdsaPub { x, y })) + } } impl Default for OpensslCrypto { @@ -104,12 +142,15 @@ type OpensslPrivKey = CryptoBuf; impl Crypto for OpensslCrypto { type Cdi = OpensslCdi; - type Hasher<'c> = OpensslHasher where Self: 'c; + type Hasher<'c> + = OpensslHasher + where + Self: 'c; type PrivKey = OpensslPrivKey; #[cfg(feature = "deterministic_rand")] fn rand_bytes(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> { - StdRng::fill_bytes(&mut self.0, dst); + StdRng::fill_bytes(&mut self.rng, dst); Ok(()) } @@ -130,7 +171,28 @@ impl Crypto for OpensslCrypto { measurement: &Digest, info: &[u8], ) -> Result { - hkdf_derive_cdi(algs, measurement, info) + let cdi = hkdf_derive_cdi(algs, measurement, info)?; + self.exported_cdi = Some(cdi.clone()); + Ok(cdi) + } + + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn derive_exported_cdi( + &mut self, + algs: AlgLen, + measurement: &Digest, + info: &[u8], + ) -> Result { + let cdi = hkdf_derive_cdi(algs, measurement, info)?; + self.exported_cdi = Some(cdi.clone()); + Ok(cdi) + } + + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn get_exported_cdi(&mut self) -> Result { + self.exported_cdi + .clone() + .ok_or(CryptoError::CryptoLibError(0)) } #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] @@ -141,26 +203,18 @@ impl Crypto for OpensslCrypto { label: &[u8], info: &[u8], ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError> { - let priv_key = hkdf_get_priv_key(algs, cdi, label, info)?; - - let ec_priv_key = OpensslCrypto::ec_key_from_priv_key(algs, &priv_key)?; - let nid = OpensslCrypto::get_curve(algs); - - let group = EcGroup::from_curve_name(nid).unwrap(); - let mut bn_ctx = BigNumContext::new().unwrap(); - - let mut x = BigNum::new().unwrap(); - let mut y = BigNum::new().unwrap(); - - ec_priv_key - .public_key() - .affine_coordinates(&group, &mut x, &mut y, &mut bn_ctx) - .unwrap(); - - let x = CryptoBuf::new(&x.to_vec_padded(algs.size() as i32).unwrap()).unwrap(); - let y = CryptoBuf::new(&y.to_vec_padded(algs.size() as i32).unwrap()).unwrap(); + self.derive_key_pair_inner(algs, cdi, label, info) + } - Ok((priv_key, EcdsaPub { x, y })) + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn derive_key_pair_exported( + &mut self, + algs: AlgLen, + cdi: &Self::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError> { + self.derive_key_pair_inner(algs, cdi, label, info) } fn ecdsa_sign_with_alias( diff --git a/crypto/src/rustcrypto.rs b/crypto/src/rustcrypto.rs index 5a691032..cfee02e0 100644 --- a/crypto/src/rustcrypto.rs +++ b/crypto/src/rustcrypto.rs @@ -54,24 +54,65 @@ impl Hasher for RustCryptoHasher { } } -pub struct RustCryptoImpl(StdRng); +pub struct RustCryptoImpl { + rng: StdRng, + // Cached CDI for exported CDI operations. + exported_cdi: Option>, +} impl RustCryptoImpl { #[cfg(not(feature = "deterministic_rand"))] pub fn new() -> Self { - RustCryptoImpl(StdRng::from_entropy()) + RustCryptoImpl { + rng: StdRng::from_entropy(), + exported_cdi: None, + } } #[cfg(feature = "deterministic_rand")] pub fn new() -> Self { const SEED: [u8; 32] = [1; 32]; let seeded_rng = StdRng::from_seed(SEED); - RustCryptoImpl(seeded_rng) + RustCryptoImpl { + rng: seeded_rng, + exported_cdi: None, + } + } + + fn derive_key_pair_inner( + &mut self, + algs: AlgLen, + cdi: &::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(::PrivKey, EcdsaPub), CryptoError> { + let secret = hkdf_get_priv_key(algs, cdi, label, info)?; + match algs { + AlgLen::Bit256 => { + let signing = p256::ecdsa::SigningKey::from_slice(&secret.bytes())?; + let verifying = p256::ecdsa::VerifyingKey::from(&signing); + let point = verifying.to_encoded_point(false); + let x = CryptoBuf::new(point.x().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; + let y = CryptoBuf::new(point.y().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; + Ok((secret, EcdsaPub { x, y })) + } + AlgLen::Bit384 => { + let signing = p384::ecdsa::SigningKey::from_slice(&secret.bytes())?; + let verifying = p384::ecdsa::VerifyingKey::from(&signing); + let point = verifying.to_encoded_point(false); + let x = CryptoBuf::new(point.x().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; + let y = CryptoBuf::new(point.y().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; + Ok((secret, EcdsaPub { x, y })) + } + } } } impl Crypto for RustCryptoImpl { type Cdi = Vec; - type Hasher<'c> = RustCryptoHasher where Self: 'c; + type Hasher<'c> + = RustCryptoHasher + where + Self: 'c; type PrivKey = CryptoBuf; fn hash_initialize(&mut self, algs: AlgLen) -> Result, CryptoError> { @@ -83,10 +124,11 @@ impl Crypto for RustCryptoImpl { } fn rand_bytes(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> { - StdRng::fill_bytes(&mut self.0, dst); + StdRng::fill_bytes(&mut self.rng, dst); Ok(()) } + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] fn derive_cdi( &mut self, algs: AlgLen, @@ -96,6 +138,25 @@ impl Crypto for RustCryptoImpl { hkdf_derive_cdi(algs, measurement, info) } + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn derive_exported_cdi( + &mut self, + algs: AlgLen, + measurement: &Digest, + info: &[u8], + ) -> Result { + let cdi = hkdf_derive_cdi(algs, measurement, info)?; + self.exported_cdi = Some(cdi.clone()); + Ok(cdi) + } + + fn get_exported_cdi(&mut self) -> Result { + self.exported_cdi + .clone() + .ok_or(CryptoError::CryptoLibError(0)) + } + + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] fn derive_key_pair( &mut self, algs: AlgLen, @@ -103,25 +164,18 @@ impl Crypto for RustCryptoImpl { label: &[u8], info: &[u8], ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError> { - let secret = hkdf_get_priv_key(algs, cdi, label, info)?; - match algs { - AlgLen::Bit256 => { - let signing = p256::ecdsa::SigningKey::from_slice(&secret.bytes())?; - let verifying = p256::ecdsa::VerifyingKey::from(&signing); - let point = verifying.to_encoded_point(false); - let x = CryptoBuf::new(point.x().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; - let y = CryptoBuf::new(point.y().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; - Ok((secret, EcdsaPub { x, y })) - } - AlgLen::Bit384 => { - let signing = p384::ecdsa::SigningKey::from_slice(&secret.bytes())?; - let verifying = p384::ecdsa::VerifyingKey::from(&signing); - let point = verifying.to_encoded_point(false); - let x = CryptoBuf::new(point.x().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; - let y = CryptoBuf::new(point.y().ok_or(RUSTCRYPTO_ECDSA_ERROR)?.as_slice())?; - Ok((secret, EcdsaPub { x, y })) - } - } + self.derive_key_pair_inner(algs, cdi, label, info) + } + + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn derive_key_pair_exported( + &mut self, + algs: AlgLen, + cdi: &Self::Cdi, + label: &[u8], + info: &[u8], + ) -> Result<(Self::PrivKey, EcdsaPub), CryptoError> { + self.derive_key_pair_inner(algs, cdi, label, info) } fn ecdsa_sign_with_alias( diff --git a/dpe/src/commands/certify_key.rs b/dpe/src/commands/certify_key.rs index 5b217ed2..b6cb15b5 100644 --- a/dpe/src/commands/certify_key.rs +++ b/dpe/src/commands/certify_key.rs @@ -4,22 +4,16 @@ use crate::{ context::ContextHandle, dpe_instance::{DpeEnv, DpeInstance, DpeTypes}, response::{CertifyKeyResp, DpeErrorCode, Response, ResponseHdr}, - tci::TciNodeData, - x509::{CertWriter, DirectoryString, MeasurementData, Name}, - DPE_PROFILE, MAX_CERT_SIZE, MAX_HANDLES, + x509::{create_dpe_cert, create_dpe_csr, CreateDpeCertArgs, CreateDpeCertResult}, + DPE_PROFILE, MAX_CERT_SIZE, }; use bitflags::bitflags; #[cfg(not(feature = "no-cfi"))] use caliptra_cfi_derive_git::cfi_impl_fn; -use caliptra_cfi_lib_git::cfi_launder; #[cfg(not(feature = "no-cfi"))] use caliptra_cfi_lib_git::{cfi_assert, cfi_assert_eq}; use cfg_if::cfg_if; -use crypto::{Crypto, Hasher}; #[cfg(not(feature = "disable_x509"))] -use platform::MAX_ISSUER_NAME_SIZE; -use platform::{Platform, PlatformError, MAX_KEY_IDENTIFIER_SIZE}; - #[repr(C)] #[derive( Debug, @@ -94,115 +88,24 @@ impl CommandExecution for CertifyKeyCmd { } } - let algs = DPE_PROFILE.alg_len(); - let digest = dpe.compute_measurement_hash(env, idx)?; - let cdi = env - .crypto - .derive_cdi(DPE_PROFILE.alg_len(), &digest, b"DPE")?; - let key_pair = env.crypto.derive_key_pair(algs, &cdi, &self.label, b"ECC"); - 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()); - } - #[allow(unused_variables)] - let (priv_key, pub_key) = key_pair?; - - let mut subj_serial = [0u8; DPE_PROFILE.get_hash_size() * 2]; - env.crypto - .get_pubkey_serial(DPE_PROFILE.alg_len(), &pub_key, &mut subj_serial)?; - // The serial number of the subject can be at most 64 bytes - let truncated_subj_serial = &subj_serial[..64]; - - let subject_name = Name { - cn: DirectoryString::PrintableString(b"DPE Leaf"), - serial: DirectoryString::PrintableString(truncated_subj_serial), - }; - - // Get TCI Nodes - const INITIALIZER: TciNodeData = TciNodeData::new(); - let mut nodes = [INITIALIZER; MAX_HANDLES]; - let tcb_count = dpe.get_tcb_nodes(idx, &mut nodes)?; - if tcb_count > MAX_HANDLES { - return Err(DpeErrorCode::InternalError); - } - - let mut subject_key_identifier = [0u8; MAX_KEY_IDENTIFIER_SIZE]; - // compute key identifier as SHA hash of the DER encoded subject public key - let mut hasher = env.crypto.hash_initialize(DPE_PROFILE.alg_len())?; - hasher.update(&[0x04])?; - hasher.update(pub_key.x.bytes())?; - hasher.update(pub_key.y.bytes())?; - let hashed_pub_key = hasher.finish()?; - if hashed_pub_key.len() < MAX_KEY_IDENTIFIER_SIZE { - return Err(DpeErrorCode::InternalError); - } - // truncate key identifier to 20 bytes - subject_key_identifier.copy_from_slice(&hashed_pub_key.bytes()[..MAX_KEY_IDENTIFIER_SIZE]); - - let mut authority_key_identifier = [0u8; MAX_KEY_IDENTIFIER_SIZE]; - env.platform - .get_issuer_key_identifier(&mut authority_key_identifier)?; - - let subject_alt_name = match env.platform.get_subject_alternative_name() { - Ok(subject_alt_name) => Some(subject_alt_name), - Err(PlatformError::NotImplemented) => None, - Err(e) => Err(DpeErrorCode::Platform(e))?, + let args = CreateDpeCertArgs { + handle: &self.handle, + locality, + cdi_label: b"DPE", + key_label: &self.label, + context: b"ECC", }; + let mut cert = [0; MAX_CERT_SIZE]; - let measurements = MeasurementData { - label: &self.label, - tci_nodes: &nodes[..tcb_count], - is_ca: false, - supports_recursive: dpe.support.recursive(), - subject_key_identifier, - authority_key_identifier, - subject_alt_name, - }; - - let mut cert = [0u8; MAX_CERT_SIZE]; - let cert_size = match self.format { + let CreateDpeCertResult { cert_size, pub_key } = match self.format { Self::FORMAT_X509 => { cfg_if! { if #[cfg(not(feature = "disable_x509"))] { - let mut issuer_name = [0u8; MAX_ISSUER_NAME_SIZE]; - let issuer_len = env.platform.get_issuer_name(&mut issuer_name)?; #[cfg(not(feature = "no-cfi"))] cfi_assert_eq(self.format, Self::FORMAT_X509); - let mut tbs_buffer = [0u8; MAX_CERT_SIZE]; - let mut tbs_writer = CertWriter::new(&mut tbs_buffer, true); - if issuer_len > MAX_ISSUER_NAME_SIZE { - return Err(DpeErrorCode::InternalError); - } - let cert_validity = env.platform.get_cert_validity()?; - let mut bytes_written = tbs_writer.encode_ecdsa_tbs( - /*serial=*/ - &subject_name.serial.bytes()[..20], // Serial number must be truncated to 20 bytes - &issuer_name[..issuer_len], - &subject_name, - &pub_key, - &measurements, - &cert_validity, - )?; - if bytes_written > MAX_CERT_SIZE { - return Err(DpeErrorCode::InternalError); - } - - let tbs_digest = env - .crypto - .hash(DPE_PROFILE.alg_len(), &tbs_buffer[..bytes_written])?; - let sig = env - .crypto - .ecdsa_sign_with_alias(DPE_PROFILE.alg_len(), &tbs_digest)?; - - let mut cert_writer = CertWriter::new(&mut cert, true); - bytes_written = - cert_writer.encode_ecdsa_certificate(&tbs_buffer[..bytes_written], &sig)?; - u32::try_from(bytes_written).map_err(|_| DpeErrorCode::InternalError)? + create_dpe_cert(&args, dpe, env, &mut cert) } else { - Err(DpeErrorCode::ArgumentNotSupported)? + Err(DpeErrorCode::ArgumentNotSupported) } } } @@ -211,57 +114,14 @@ impl CommandExecution for CertifyKeyCmd { if #[cfg(not(feature = "disable_csr"))] { #[cfg(not(feature = "no-cfi"))] cfi_assert_eq(self.format, Self::FORMAT_CSR); - let mut cert_req_info_buffer = [0u8; MAX_CERT_SIZE]; - let mut cert_req_info_writer = CertWriter::new(&mut cert_req_info_buffer, true); - let mut bytes_written = cert_req_info_writer - .encode_certification_request_info( - &pub_key, - &subject_name, - &measurements, - )?; - if bytes_written > MAX_CERT_SIZE { - return Err(DpeErrorCode::InternalError); - } - - let cert_req_info_digest = env.crypto.hash( - DPE_PROFILE.alg_len(), - &cert_req_info_buffer[..bytes_written], - )?; - // The PKCS#10 CSR is self-signed so the private key signs it instead of the alias key. - let cert_req_info_sig = env.crypto.ecdsa_sign_with_derived( - DPE_PROFILE.alg_len(), - &cert_req_info_digest, - &priv_key, - &pub_key, - )?; - - let mut csr_buffer = [0u8; MAX_CERT_SIZE]; - let mut csr_writer = CertWriter::new(&mut csr_buffer, true); - bytes_written = csr_writer - .encode_csr(&cert_req_info_buffer[..bytes_written], &cert_req_info_sig)?; - if bytes_written > MAX_CERT_SIZE { - return Err(DpeErrorCode::InternalError); - } - - let csr_digest = env - .crypto - .hash(DPE_PROFILE.alg_len(), &csr_buffer[..bytes_written])?; - let csr_sig = env - .crypto - .ecdsa_sign_with_alias(DPE_PROFILE.alg_len(), &csr_digest)?; - let sid = env.platform.get_signer_identifier()?; - - let mut cms_writer = CertWriter::new(&mut cert, true); - bytes_written = - cms_writer.encode_cms(&csr_buffer[..bytes_written], &csr_sig, &sid)?; - u32::try_from(bytes_written).map_err(|_| DpeErrorCode::InternalError)? + create_dpe_csr(&args, dpe, env, &mut cert) } else { - Err(DpeErrorCode::ArgumentNotSupported)? + Err(DpeErrorCode::ArgumentNotSupported) } } } _ => return Err(DpeErrorCode::InvalidArgument), - }; + }?; let derived_pubkey_x: [u8; DPE_PROFILE.get_ecc_int_size()] = pub_key @@ -297,14 +157,14 @@ mod tests { commands::{Command, CommandHdr, DeriveContextCmd, DeriveContextFlags, InitCtxCmd}, dpe_instance::tests::{TestTypes, SIMULATION_HANDLE, TEST_LOCALITIES}, support::Support, - x509::tests::TcbInfo, + x509::{tests::TcbInfo, DirectoryString, Name}, }; use caliptra_cfi_lib_git::CfiCounter; use cms::{ content_info::{CmsVersion, ContentInfo}, signed_data::{SignedData, SignerIdentifier}, }; - use crypto::{AlgLen, CryptoBuf, EcdsaPub, OpensslCrypto}; + use crypto::{AlgLen, Crypto, CryptoBuf, EcdsaPub, OpensslCrypto}; use der::{Decode, Encode}; use openssl::{ bn::BigNum, @@ -312,7 +172,7 @@ mod tests { ecdsa::EcdsaSig, nid::*, }; - use platform::default::DefaultPlatform; + use platform::{default::DefaultPlatform, Platform}; use spki::ObjectIdentifier; use std::str; use x509_parser::nom::Parser; diff --git a/dpe/src/x509.rs b/dpe/src/x509.rs index 24a20569..07f1fe58 100644 --- a/dpe/src/x509.rs +++ b/dpe/src/x509.rs @@ -6,17 +6,25 @@ //! this functionality for a no_std environment. use crate::{ + context::ContextHandle, + dpe_instance::{DpeEnv, DpeTypes}, response::DpeErrorCode, tci::{TciMeasurement, TciNodeData}, - DpeProfile, DPE_PROFILE, + DpeInstance, DpeProfile, DPE_PROFILE, MAX_CERT_SIZE, MAX_HANDLES, }; use bitflags::bitflags; -use crypto::{EcdsaPub, EcdsaSig}; +use caliptra_cfi_lib_git::cfi_launder; +#[cfg(not(feature = "no-cfi"))] +use caliptra_cfi_lib_git::{cfi_assert, cfi_assert_eq}; +use crypto::{Crypto, Digest, EcdsaPub, EcdsaSig, Hasher}; #[cfg(not(feature = "disable_x509"))] use platform::CertValidity; #[cfg(not(feature = "disable_csr"))] use platform::SignerIdentifier; -use platform::{OtherName, SubjectAltName, MAX_KEY_IDENTIFIER_SIZE}; +use platform::{ + OtherName, Platform, PlatformError, SubjectAltName, MAX_ISSUER_NAME_SIZE, + MAX_KEY_IDENTIFIER_SIZE, +}; use zerocopy::IntoBytes; pub enum DirectoryString<'a> { @@ -2227,6 +2235,286 @@ impl CertWriter<'_> { } } +enum CertificateFormat { + X509, + Csr, +} + +enum CertificateType { + Leaf, + Exported, +} + +pub(crate) struct CreateDpeCertArgs<'a> { + pub handle: &'a ContextHandle, + pub locality: u32, + pub cdi_label: &'a [u8], + pub key_label: &'a [u8], + pub context: &'a [u8], +} + +pub(crate) struct CreateDpeCertResult { + pub cert_size: u32, + pub pub_key: EcdsaPub, +} + +fn get_dpe_measurement_digest( + dpe: &mut DpeInstance, + env: &mut DpeEnv, + handle: &ContextHandle, + locality: u32, +) -> Result { + let parent_idx = dpe.get_active_context_pos(handle, locality)?; + let digest = dpe.compute_measurement_hash(env, parent_idx)?; + Ok(digest) +} + +fn get_subject_name<'a>( + env: &mut DpeEnv, + pub_key: &'a EcdsaPub, + subj_serial: &'a mut [u8], +) -> Result, DpeErrorCode> { + env.crypto + .get_pubkey_serial(DPE_PROFILE.alg_len(), pub_key, subj_serial)?; + + // The serial number of the subject can be at most 64 bytes + let truncated_subj_serial = &subj_serial[..64]; + + let subject_name = Name { + cn: DirectoryString::PrintableString(b"DPE Leaf"), + serial: DirectoryString::PrintableString(truncated_subj_serial), + }; + Ok(subject_name) +} + +fn get_tci_nodes( + dpe: &mut DpeInstance, + handle: &ContextHandle, + locality: u32, + nodes: &mut [TciNodeData], +) -> Result { + let parent_idx = dpe.get_active_context_pos(handle, locality)?; + let tcb_count = dpe.get_tcb_nodes(parent_idx, nodes)?; + if tcb_count > MAX_HANDLES { + return Err(DpeErrorCode::InternalError); + } + Ok(tcb_count) +} + +fn get_subject_key_identifier( + env: &mut DpeEnv, + pub_key: &EcdsaPub, + subject_key_identifier: &mut [u8], +) -> Result<(), DpeErrorCode> { + // compute key identifier as SHA hash of the DER encoded subject public key + let mut hasher = env.crypto.hash_initialize(DPE_PROFILE.alg_len())?; + hasher.update(&[0x04])?; + hasher.update(pub_key.x.bytes())?; + hasher.update(pub_key.y.bytes())?; + let hashed_pub_key = hasher.finish()?; + if hashed_pub_key.len() < MAX_KEY_IDENTIFIER_SIZE { + return Err(DpeErrorCode::InternalError); + } + // truncate key identifier to 20 bytes + subject_key_identifier.copy_from_slice(&hashed_pub_key.bytes()[..MAX_KEY_IDENTIFIER_SIZE]); + Ok(()) +} + +// TODO(clundin): Remove this lint when adding the DeriveContext certificate generation code. +#[allow(unused)] +pub(crate) fn create_exported_dpe_cert( + args: &CreateDpeCertArgs, + dpe: &mut DpeInstance, + env: &mut DpeEnv, + cert: &mut [u8], +) -> Result { + create_dpe_cert_or_csr( + args, + dpe, + env, + CertificateFormat::X509, + CertificateType::Exported, + cert, + ) +} + +pub(crate) fn create_dpe_cert( + args: &CreateDpeCertArgs, + dpe: &mut DpeInstance, + env: &mut DpeEnv, + cert: &mut [u8], +) -> Result { + create_dpe_cert_or_csr( + args, + dpe, + env, + CertificateFormat::X509, + CertificateType::Leaf, + cert, + ) +} + +pub(crate) fn create_dpe_csr( + args: &CreateDpeCertArgs, + dpe: &mut DpeInstance, + env: &mut DpeEnv, + csr: &mut [u8], +) -> Result { + create_dpe_cert_or_csr( + args, + dpe, + env, + CertificateFormat::Csr, + CertificateType::Leaf, + csr, + ) +} + +fn create_dpe_cert_or_csr( + args: &CreateDpeCertArgs, + dpe: &mut DpeInstance, + env: &mut DpeEnv, + cert_format: CertificateFormat, + cert_type: CertificateType, + output_cert_or_csr: &mut [u8], +) -> Result { + let algs = DPE_PROFILE.alg_len(); + let digest = get_dpe_measurement_digest(dpe, env, args.handle, args.locality)?; + + let key_pair = match cert_type { + CertificateType::Exported => { + let cdi = env + .crypto + .derive_exported_cdi(algs, &digest, args.cdi_label)?; + env.crypto + .derive_key_pair_exported(algs, &cdi, args.key_label, args.context) + } + CertificateType::Leaf => { + let cdi = env.crypto.derive_cdi(algs, &digest, args.cdi_label)?; + env.crypto + .derive_key_pair(algs, &cdi, args.key_label, args.context) + } + }; + 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?; + + let mut subj_serial = [0u8; DPE_PROFILE.get_hash_size() * 2]; + let subject_name = get_subject_name(env, &pub_key, &mut subj_serial)?; + + const INITIALIZER: TciNodeData = TciNodeData::new(); + let mut nodes = [INITIALIZER; MAX_HANDLES]; + let tcb_count = get_tci_nodes(dpe, args.handle, args.locality, &mut nodes)?; + + let mut subject_key_identifier = [0u8; MAX_KEY_IDENTIFIER_SIZE]; + get_subject_key_identifier(env, &pub_key, &mut subject_key_identifier)?; + + let mut authority_key_identifier = [0u8; MAX_KEY_IDENTIFIER_SIZE]; + env.platform + .get_issuer_key_identifier(&mut authority_key_identifier)?; + + let subject_alt_name = match env.platform.get_subject_alternative_name() { + Ok(subject_alt_name) => Some(subject_alt_name), + Err(PlatformError::NotImplemented) => None, + Err(e) => Err(DpeErrorCode::Platform(e))?, + }; + + let is_ca = match cert_type { + CertificateType::Leaf => false, + CertificateType::Exported => true, + }; + + let supports_recursive = match cert_type { + CertificateType::Leaf => dpe.support.recursive(), + CertificateType::Exported => false, + }; + + let measurements = MeasurementData { + label: args.key_label, + tci_nodes: &nodes[..tcb_count], + is_ca, + supports_recursive, + subject_key_identifier, + authority_key_identifier, + subject_alt_name, + }; + let mut scratch_buf = [0u8; MAX_CERT_SIZE]; + let mut scratch_writer = CertWriter::new(&mut scratch_buf, true); + let cert_size = match cert_format { + CertificateFormat::X509 => { + let mut issuer_name = [0u8; MAX_ISSUER_NAME_SIZE]; + let issuer_len = env.platform.get_issuer_name(&mut issuer_name)?; + if issuer_len > MAX_ISSUER_NAME_SIZE { + return Err(DpeErrorCode::InternalError); + } + let cert_validity = env.platform.get_cert_validity()?; + let mut bytes_written = scratch_writer.encode_ecdsa_tbs( + &subject_name.serial.bytes()[..20], // Serial number must be truncated to 20 bytes + &issuer_name[..issuer_len], + &subject_name, + &pub_key, + &measurements, + &cert_validity, + )?; + if bytes_written > MAX_CERT_SIZE { + return Err(DpeErrorCode::InternalError); + } + let tbs_digest = env.crypto.hash(algs, &scratch_buf[..bytes_written])?; + let sig = env + .crypto + .ecdsa_sign_with_alias(DPE_PROFILE.alg_len(), &tbs_digest)?; + let mut cert_writer = CertWriter::new(output_cert_or_csr, true); + bytes_written = + cert_writer.encode_ecdsa_certificate(&scratch_buf[..bytes_written], &sig)?; + u32::try_from(bytes_written).map_err(|_| DpeErrorCode::InternalError)? + } + CertificateFormat::Csr => { + let mut bytes_written = scratch_writer.encode_certification_request_info( + &pub_key, + &subject_name, + &measurements, + )?; + if bytes_written > MAX_CERT_SIZE { + return Err(DpeErrorCode::InternalError); + } + + let cert_req_info_digest = env.crypto.hash(algs, &scratch_buf[..bytes_written])?; + // The PKCS#10 CSR is self-signed so the private key signs it instead of the alias key. + let cert_req_info_sig = env.crypto.ecdsa_sign_with_derived( + algs, + &cert_req_info_digest, + &priv_key, + &pub_key, + )?; + + let mut csr_buffer = [0u8; MAX_CERT_SIZE]; + let mut csr_writer = CertWriter::new(&mut csr_buffer, true); + bytes_written = + csr_writer.encode_csr(&scratch_buf[..bytes_written], &cert_req_info_sig)?; + if bytes_written > MAX_CERT_SIZE { + return Err(DpeErrorCode::InternalError); + } + + let csr_digest = env.crypto.hash(algs, &csr_buffer[..bytes_written])?; + let csr_sig = env + .crypto + .ecdsa_sign_with_alias(DPE_PROFILE.alg_len(), &csr_digest)?; + let sid = env.platform.get_signer_identifier()?; + + let mut cms_writer = CertWriter::new(output_cert_or_csr, true); + bytes_written = cms_writer.encode_cms(&csr_buffer[..bytes_written], &csr_sig, &sid)?; + u32::try_from(bytes_written).map_err(|_| DpeErrorCode::InternalError)? + } + }; + + Ok(CreateDpeCertResult { cert_size, pub_key }) +} + #[cfg(test)] pub(crate) mod tests { use crate::tci::{TciMeasurement, TciNodeData};