From 9d8db1250144372162fe9960f62a3272e112ad74 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Sat, 22 Jul 2023 06:03:05 +0000 Subject: [PATCH] x509-cert: don't bind builder with signer early This is mostly a draft after discussion in #1160 Signed-off-by: Arthur Gautier --- Cargo.lock | 23 +++++ x509-cert/Cargo.toml | 1 + x509-cert/src/builder.rs | 186 +++++++++++++++++++++---------------- x509-cert/tests/builder.rs | 70 ++++++++------ 4 files changed, 172 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d22dfcef0..fa7f23f1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -545,6 +545,7 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core", @@ -771,6 +772,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -949,6 +959,18 @@ dependencies = [ "sha2", ] +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -1931,6 +1953,7 @@ dependencies = [ "ecdsa", "hex-literal 0.4.1", "p256", + "p384", "rand", "rsa", "rstest", diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml index cc0848924..ba78b0bec 100644 --- a/x509-cert/Cargo.toml +++ b/x509-cert/Cargo.toml @@ -31,6 +31,7 @@ rand = "0.8.5" rsa = { version = "0.9.6", features = ["sha2"] } ecdsa = { version = "0.16.8", features = ["digest", "pem"] } p256 = "0.13.0" +p384 = "0.13.0" rstest = "0.18" sha2 = { version = "0.10", features = ["oid"] } tempfile = "3.5.0" diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index c778f1e2f..9bc01e445 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -5,8 +5,8 @@ use core::fmt; use der::{asn1::BitString, referenced::OwnedToRef, Encode}; use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer}; use spki::{ - DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding, - SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, + AlgorithmIdentifier, DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier, + SignatureBitStringEncoding, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, }; use crate::{ @@ -23,6 +23,8 @@ use crate::{ time::Validity, }; +const NULL_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("0.0.0"); + /// Error type #[derive(Debug)] #[non_exhaustive] @@ -68,7 +70,8 @@ impl From for Error { } } -type Result = core::result::Result; +/// Result type +pub type Result = core::result::Result; /// The type of certificate to build #[derive(Clone, Debug, Eq, PartialEq)] @@ -219,7 +222,7 @@ impl Profile { /// ``` /// use der::Decode; /// use x509_cert::spki::SubjectPublicKeyInfoOwned; -/// use x509_cert::builder::{CertificateBuilder, Profile}; +/// use x509_cert::builder::{CertificateBuilder, Profile, Builder}; /// use x509_cert::name::Name; /// use x509_cert::serial_number::SerialNumber; /// use x509_cert::time::Validity; @@ -251,21 +254,18 @@ impl Profile { /// validity, /// subject, /// pub_key, -/// &signer, /// ) -/// .expect("Create certificate"); +/// .expect("Create certificate builder"); +/// +/// let cert = builder.build(&signer).expect("Create certificate"); /// ``` -pub struct CertificateBuilder<'s, S> { +pub struct CertificateBuilder { tbs: TbsCertificate, extensions: Extensions, - cert_signer: &'s S, + profile: Profile, } -impl<'s, S> CertificateBuilder<'s, S> -where - S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, -{ +impl CertificateBuilder { /// Creates a new certificate builder pub fn new( profile: Profile, @@ -273,12 +273,12 @@ where mut validity: Validity, subject: Name, subject_public_key_info: SubjectPublicKeyInfoOwned, - cert_signer: &'s S, ) -> Result { - let verifying_key = cert_signer.verifying_key(); - let signer_pub = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; + let signature_alg = AlgorithmIdentifier { + oid: NULL_OID, + parameters: None, + }; - let signature_alg = cert_signer.signature_algorithm_identifier()?; let issuer = profile.get_issuer(&subject); validity.not_before.rfc5280_adjust_utc_time()?; @@ -303,15 +303,11 @@ where subject_unique_id: None, }; - let extensions = profile.build_extensions( - tbs.subject_public_key_info.owned_to_ref(), - signer_pub.owned_to_ref(), - &tbs, - )?; + let extensions = Extensions::default(); Ok(Self { tbs, extensions, - cert_signer, + profile, }) } @@ -344,31 +340,34 @@ where /// let subject = Name::from_str("CN=service.domination.world").unwrap(); /// /// let signer = ecdsa_signer(); -/// let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request"); +/// let mut builder = RequestBuilder::new(subject).expect("Create certificate request"); /// builder /// .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4( /// Ipv4Addr::new(192, 0, 2, 0), /// ))])) /// .unwrap(); /// -/// let cert_req = builder.build::().unwrap(); +/// let cert_req = builder.build::<_, DerSignature>(&signer).unwrap(); /// ``` -pub struct RequestBuilder<'s, S> { +pub struct RequestBuilder { info: CertReqInfo, extension_req: ExtensionReq, - req_signer: &'s S, } -impl<'s, S> RequestBuilder<'s, S> -where - S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, -{ +impl RequestBuilder { /// Creates a new certificate request builder - pub fn new(subject: Name, req_signer: &'s S) -> Result { + pub fn new(subject: Name) -> Result { let version = Default::default(); - let verifying_key = req_signer.verifying_key(); - let public_key = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; + + let algorithm = AlgorithmIdentifier { + oid: NULL_OID, + parameters: None, + }; + let public_key = SubjectPublicKeyInfoOwned { + algorithm, + subject_public_key: BitString::from_bytes(&[]).expect("unable to parse empty object"), + }; + let attributes = Default::default(); let extension_req = Default::default(); @@ -380,7 +379,6 @@ where attributes, }, extension_req, - req_signer, }) } @@ -406,64 +404,79 @@ where /// /// This trait defines the interface between builder and the signers. pub trait Builder: Sized { - /// The builder's object signer - type Signer; - /// Type built by this builder type Output: Sized; - /// Return a reference to the signer. - fn signer(&self) -> &Self::Signer; - /// Assemble the final object from signature. - fn assemble(self, signature: BitString) -> Result; + fn assemble(self, signature: BitString, signer: &S) -> Result + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey; /// Finalize and return a serialization of the object for signature. - fn finalize(&mut self) -> der::Result>; + fn finalize(&mut self, signer: &S) -> Result> + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey; /// Run the object through the signer and build it. - fn build(mut self) -> Result + fn build(mut self, signer: &S) -> Result where - Self::Signer: Signer, + S: Signer, + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, Signature: SignatureBitStringEncoding, { - let blob = self.finalize()?; + let blob = self.finalize(signer)?; - let signature = self.signer().try_sign(&blob)?.to_bitstring()?; + let signature = signer.try_sign(&blob)?.to_bitstring()?; - self.assemble(signature) + self.assemble(signature, signer) } /// Run the object through the signer and build it. - fn build_with_rng(mut self, rng: &mut impl CryptoRngCore) -> Result + fn build_with_rng( + mut self, + signer: &S, + rng: &mut impl CryptoRngCore, + ) -> Result where - Self::Signer: RandomizedSigner, + S: RandomizedSigner, + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, Signature: SignatureBitStringEncoding, { - let blob = self.finalize()?; + let blob = self.finalize(signer)?; - let signature = self - .signer() - .try_sign_with_rng(rng, &blob)? - .to_bitstring()?; + let signature = signer.try_sign_with_rng(rng, &blob)?.to_bitstring()?; - self.assemble(signature) + self.assemble(signature, signer) } } -impl<'s, S> Builder for CertificateBuilder<'s, S> -where - S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, -{ - type Signer = S; +impl Builder for CertificateBuilder { type Output = Certificate; - fn signer(&self) -> &Self::Signer { - self.cert_signer - } + fn finalize(&mut self, cert_signer: &S) -> Result> + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { + let verifying_key = cert_signer.verifying_key(); + let signer_pub = verifying_key + .to_public_key_der()? + .decode_msg::()?; + + self.tbs.signature = cert_signer.signature_algorithm_identifier()?; + + let mut default_extensions = self.profile.build_extensions( + self.tbs.subject_public_key_info.owned_to_ref(), + signer_pub.owned_to_ref(), + &self.tbs, + )?; + + self.extensions.append(&mut default_extensions); - fn finalize(&mut self) -> der::Result> { if !self.extensions.is_empty() { self.tbs.extensions = Some(self.extensions.clone()); } @@ -476,10 +489,14 @@ where } } - self.tbs.to_der() + self.tbs.to_der().map_err(Error::from) } - fn assemble(self, signature: BitString) -> Result { + fn assemble(self, signature: BitString, _signer: &S) -> Result + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { let signature_algorithm = self.tbs.signature.clone(); Ok(Certificate { @@ -490,28 +507,33 @@ where } } -impl<'s, S> Builder for RequestBuilder<'s, S> -where - S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, -{ - type Signer = S; +impl Builder for RequestBuilder { type Output = CertReq; - fn signer(&self) -> &Self::Signer { - self.req_signer - } + fn finalize(&mut self, signer: &S) -> Result> + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { + let verifying_key = signer.verifying_key(); + let public_key = verifying_key + .to_public_key_der()? + .decode_msg::()?; + self.info.public_key = public_key; - fn finalize(&mut self) -> der::Result> { self.info .attributes .insert(self.extension_req.clone().try_into()?)?; - self.info.to_der() + self.info.to_der().map_err(Error::from) } - fn assemble(self, signature: BitString) -> Result { - let algorithm = self.req_signer.signature_algorithm_identifier()?; + fn assemble(self, signature: BitString, signer: &S) -> Result + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { + let algorithm = signer.signature_algorithm_identifier()?; Ok(CertReq { info: self.info, diff --git a/x509-cert/tests/builder.rs b/x509-cert/tests/builder.rs index 6a8a55a85..77dd5f75b 100644 --- a/x509-cert/tests/builder.rs +++ b/x509-cert/tests/builder.rs @@ -2,6 +2,7 @@ use der::{asn1::PrintableString, pem::LineEnding, Decode, Encode, EncodePem}; use p256::{ecdsa::DerSignature, pkcs8::DecodePrivateKey, NistP256}; +use rand::rngs::OsRng; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::pkcs1v15::SigningKey; use sha2::Sha256; @@ -37,11 +38,10 @@ fn root_ca_certificate() { SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key"); let signer = rsa_signer(); - let builder = - CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer) - .expect("Create certificate"); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); - let certificate = builder.build().unwrap(); + let certificate = builder.build(&signer).unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); println!("{}", openssl::check_certificate(pem.as_bytes())); @@ -64,11 +64,10 @@ fn root_ca_certificate_ecdsa() { SubjectPublicKeyInfoOwned::try_from(PKCS8_PUBLIC_KEY_DER).expect("get ecdsa pub key"); let signer = ecdsa_signer(); - let builder = - CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer) - .expect("Create certificate"); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); - let certificate = builder.build::().unwrap(); + let certificate = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); println!("{}", openssl::check_certificate(pem.as_bytes())); @@ -95,11 +94,10 @@ fn sub_ca_certificate() { SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key"); let signer = ecdsa_signer(); - let builder = - CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer) - .expect("Create certificate"); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); - let certificate = builder.build::().unwrap(); + let certificate = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); println!("{}", openssl::check_certificate(pem.as_bytes())); @@ -141,11 +139,10 @@ fn leaf_certificate() { validity, subject.clone(), pub_key.clone(), - &signer, ) .expect("Create certificate"); - let certificate = builder.build::().unwrap(); + let certificate = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); println!("{}", openssl::check_certificate(pem.as_bytes())); @@ -180,11 +177,10 @@ fn leaf_certificate() { enable_key_encipherment: false, include_subject_key_identifier: false, }; - let builder = - CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer) - .expect("Create certificate"); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); - let certificate = builder.build::().unwrap(); + let certificate = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); println!("{}", openssl::check_certificate(pem.as_bytes())); @@ -217,12 +213,11 @@ fn pss_certificate() { SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key"); let signer = rsa_pss_signer(); - let builder = - CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer) - .expect("Create certificate"); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); let certificate = builder - .build_with_rng::(&mut rand::thread_rng()) + .build_with_rng::<_, rsa::pss::Signature>(&signer, &mut rand::thread_rng()) .unwrap(); let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); @@ -278,14 +273,14 @@ fn certificate_request() { let subject = Name::from_str("CN=service.domination.world").unwrap(); let signer = ecdsa_signer(); - let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request"); + let mut builder = RequestBuilder::new(subject).expect("Create certificate request"); builder .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4( Ipv4Addr::new(192, 0, 2, 0), ))])) .unwrap(); - let cert_req = builder.build::().unwrap(); + let cert_req = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = cert_req.to_pem(LineEnding::LF).expect("generate pem"); use std::fs::File; use std::io::Write; @@ -299,7 +294,7 @@ fn certificate_request_attributes() { let subject = Name::from_str("CN=service.domination.world").unwrap(); let signer = ecdsa_signer(); - let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request"); + let mut builder = RequestBuilder::new(subject).expect("Create certificate request"); builder .add_attribute(&request::attributes::ChallengePassword( DirectoryString::PrintableString( @@ -309,7 +304,7 @@ fn certificate_request_attributes() { )) .expect("unable to add attribute"); - let cert_req = builder.build::().unwrap(); + let cert_req = builder.build::<_, DerSignature>(&signer).unwrap(); let pem = cert_req.to_pem(LineEnding::LF).expect("generate pem"); use std::fs::File; use std::io::Write; @@ -317,3 +312,26 @@ fn certificate_request_attributes() { file.write_all(pem.as_bytes()).expect("Create pem file"); println!("{}", openssl::check_request(pem.as_bytes())); } + +#[test] +fn dynamic_signer() { + let subject = Name::from_str("CN=Test").expect("parse common name"); + + let csr_builder = RequestBuilder::new(subject).expect("construct builder"); + + let csr = if true { + let req_signer = p256::ecdsa::SigningKey::random(&mut OsRng); + csr_builder + .build::<_, p256::ecdsa::DerSignature>(&req_signer) + .expect("Sign request") + } else { + let req_signer = p384::ecdsa::SigningKey::random(&mut OsRng); + csr_builder + .build::<_, p384::ecdsa::DerSignature>(&req_signer) + .expect("Sign request") + }; + + let csr_pem = csr.to_pem(LineEnding::LF).expect("format CSR"); + + println!("{}", csr_pem); +}