diff --git a/Cargo.lock b/Cargo.lock index c644d4c83..eb95d7d02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1240,9 +1240,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.0-pre.1" +version = "0.9.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16504cc31b04d2a5ec729f0c7e1b62e76634a9537f089df0ca1981dc8208a89" +checksum = "65db0998ad35adcaca498b7358992e088ee16cc783fe6fb899da203e113a63e5" dependencies = [ "byteorder", "const-oid 0.9.2", diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml index f216f6211..3ba1503e2 100644 --- a/x509-cert/Cargo.toml +++ b/x509-cert/Cargo.toml @@ -22,12 +22,12 @@ spki = { version = "0.7.1", features = ["alloc"] } # optional dependencies arbitrary = { version = "1.3", features = ["derive"], optional = true } sha1 = { version = "0.10.0", optional = true } -signature = { version = "2.1.0", optional = true } +signature = { version = "2.1.0", features = ["rand_core"], optional = true } [dev-dependencies] hex-literal = "0.4" rand = "0.8.5" -rsa = { version = "0.9.0-pre.1", features = ["sha2"] } +rsa = { version = "0.9.0-pre.2", features = ["sha2"] } ecdsa = { version = "0.16.4", features = ["digest", "pem"] } p256 = "0.13.0" rstest = "0.17" diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index 10b050785..399e7ee25 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -3,7 +3,7 @@ use alloc::vec; use core::fmt; use der::{asn1::BitString, referenced::OwnedToRef, Encode}; -use signature::{Keypair, SignatureEncoding, Signer}; +use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, SignatureEncoding, Signer}; use spki::{ DynSignatureAlgorithmIdentifier, EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, @@ -32,7 +32,7 @@ pub enum Error { /// Public key errors propagated from the [`spki::Error`] type. PublicKey(spki::Error), - /// Signing error propagated for the [`signature::Signer`] type. + /// Signing error propagated for the [`signature::Error`] type. Signature(signature::Error), } @@ -244,17 +244,14 @@ where S::VerifyingKey: EncodePublicKey, { /// Creates a new certificate builder - pub fn new( + pub fn new( profile: Profile, serial_number: SerialNumber, mut validity: Validity, subject: Name, subject_public_key_info: SubjectPublicKeyInfoOwned, signer: &'s S, - ) -> Result - where - S: Signer, - { + ) -> Result { let verifying_key = signer.verifying_key(); let signer_pub = verifying_key .to_public_key_der()? @@ -313,12 +310,7 @@ where Ok(()) } - /// Run the certificate through the signer and build the end certificate. - pub fn build(mut self) -> Result - where - S: Signer, - Signature: SignatureEncoding, - { + fn finalize(&mut self) { if self.tbs.extensions.is_none() { if self.tbs.issuer_unique_id.is_some() || self.tbs.subject_unique_id.is_some() { self.tbs.version = Version::V2; @@ -326,6 +318,16 @@ where self.tbs.version = Version::V1; } } + } + + /// Run the certificate through the signer and build the end certificate. + pub fn build(mut self) -> Result + where + S: Signer, + Signature: SignatureEncoding, + { + self.finalize(); + let signature = self.signer.try_sign(&self.tbs.to_der()?)?; let signature = BitString::from_bytes(signature.to_bytes().as_ref())?; @@ -337,4 +339,24 @@ where Ok(cert) } + + /// Run the certificate through the signer and build the end certificate. + pub fn build_with_rng(mut self, rng: &mut impl CryptoRngCore) -> Result + where + S: RandomizedSigner, + Signature: SignatureEncoding, + { + self.finalize(); + + let signature = self.signer.try_sign_with_rng(rng, &self.tbs.to_der()?)?; + let signature = BitString::from_bytes(signature.to_bytes().as_ref())?; + + let cert = Certificate { + tbs_certificate: self.tbs.clone(), + signature_algorithm: self.tbs.signature, + signature, + }; + + Ok(cert) + } } diff --git a/x509-cert/tests/builder.rs b/x509-cert/tests/builder.rs index 4dd730fba..27961dfe7 100644 --- a/x509-cert/tests/builder.rs +++ b/x509-cert/tests/builder.rs @@ -62,15 +62,9 @@ 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, &signer) + .expect("Create certificate"); let certificate = builder.build::>().unwrap(); @@ -106,15 +100,9 @@ fn leaf_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, &signer) + .expect("Create certificate"); let certificate = builder.build::>().unwrap(); @@ -141,11 +129,68 @@ fn leaf_certificate() { zlint::check_certificate(pem.as_bytes(), ignored); } +#[test] +fn pss_certificate() { + let serial_number = SerialNumber::from(42u32); + let validity = Validity::from_now(Duration::new(5, 0)).unwrap(); + + let issuer = + Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap(); + let profile = Profile::Leaf { + issuer, + enable_key_agreement: false, + enable_key_encipherment: false, + }; + + let subject = Name::from_str("CN=service.domination.world").unwrap(); + let pub_key = + 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 certificate = builder + .build_with_rng::(&mut rand::thread_rng()) + .unwrap(); + + let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); + println!("{}", openssl::check_certificate(pem.as_bytes())); + + // TODO(baloo): not too sure we should tackle those in this API. + let ignored = &[ + "e_sub_cert_aia_missing", + "e_sub_cert_crl_distribution_points_missing", + "w_sub_cert_aia_does_not_contain_issuing_ca_url", + // Missing policies + "e_sub_cert_certificate_policies_missing", + "e_sub_cert_cert_policy_empty", + // Needs to be added by the end-user + "e_sub_cert_aia_does_not_contain_ocsp_url", + // SAN needs to include DNS name (if used) + "e_ext_san_missing", + "e_subject_common_name_not_exactly_from_san", + // Extended key usage needs to be added by end-user and is use-case dependent + "e_sub_cert_eku_missing", + // zlint warns on RSAPSS signature algorithms + "e_signature_algorithm_not_supported", + ]; + + zlint::check_certificate(pem.as_bytes(), ignored); +} + const RSA_2048_PRIV_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); fn rsa_signer() -> SigningKey { let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap(); - let signing_key = SigningKey::::new_with_prefix(private_key); + let signing_key = SigningKey::::new(private_key); + signing_key +} + +fn rsa_pss_signer() -> rsa::pss::SigningKey { + let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap(); + let signing_key = rsa::pss::SigningKey::::new(private_key); signing_key }