From 30489d74833e8230bb1bc1d9412d17b776ce05bd Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 16 Jan 2024 03:23:57 -0500 Subject: [PATCH] Rework Certificate issuance API, make DER/PEM serialization stable (#205) Previously a `Certificate` was a container for `CertificateParams` and a `KeyPair`, most commonly created from a `CertificateParams` instance. Serializing the `Certificate` (either as self signed with `serialize_pem` or `serialize_der`, or signed by an issuer with `serialize_pem_with_signer` or `serialize_der_with_signer`) would issue a certificate and produce the serialized form in one operation. The net result is that if a user wanted both DER and PEM serializations they would likely call `serialize_der(_with_signer)` and then `serialize_pem(_with_signer)` and mistakenly end up with the encoding of two distinct certificates, not the PEM and DER encoding of the same cert. Since the `KeyPair` contains a private key this API design also meant that the `Certificate` type had to be handled with care, and `Zeroized`. This branch reworks the issuance API and `Certificate` type to better match user expectation: `Certificate` is only public material and represents an issued certificate that can be serialized in a stable manner in DER or PEM encoding. I recommend reviewing this commit-by-commit, but here is a summary of the most notable API changes: * `Certificate::from_params` and `Certificate::serialize_der` and `Certificate::serialize_pem` for issuing a self-signed certificate are replaced with `Certificate::generate_self_signed()` and calling `der` or `pem` on the result. * `Certificate::from_params` and `Certificate::serialize_der_with_signer` and `Certificate::serialize_pem_with_signer` for issuing a certificate signed by another certificate are replaced with `Certificate::generate()` and calling `der` or `pem` on the result. * `CertificateSigningRequest::serialize_der_with_signer` and `CertificateSigningRequest::serialize_pem_with_signer` for issuing a certificate from a CSR are replaced with `Certificate::from_request` and calling `der` or `pem` on the result. The `CertificateSigningRequest` type is renamed to `CertificateSigningRequestParams` to better emphasize its role and match the other `*Params` types that already exist. * Since we now calculate the DER encoding of the certificate at `Certificate` construction time, the `pem` and `der` fns are now infallible. * Since `Certificate` no longer holds `KeyPair`, the `generate` fns now expect a `&KeyPair` argument for the signer when issuing a certificate signed by another certificate. * The generation fns now return a `CertifiedKey` that contains both a `Certificate` and a `KeyPair`. For params that specify a compatible `KeyPair` it is passed through in the `CertifiedKey` as-is. For params without a `KeyPair` a newly generated `KeyPair` is used. In the future we should look at harmonizing the creation of `CertificateSigningRequest` and `CertificateRevocationList` to better match this updated API. Unfortunately I don't have time to handle that at the moment. Since this API surface is relatively niche compared to the `Certificate` issuance flow it felt valuable to resolve #62 without blocking on this future work. Resolves https://github.com/rustls/rcgen/issues/62 --- Cargo.lock | 2 +- rcgen/Cargo.toml | 2 +- rcgen/examples/rsa-irc-openssl.rs | 12 +- rcgen/examples/rsa-irc.rs | 12 +- rcgen/examples/sign-leaf-with-ca.rs | 16 +- rcgen/examples/simple.rs | 14 +- rcgen/src/crl.rs | 54 +++- rcgen/src/csr.rs | 25 +- rcgen/src/key_pair.rs | 22 +- rcgen/src/lib.rs | 441 +++++++++++++++++----------- rcgen/tests/botan.rs | 125 ++++---- rcgen/tests/generic.rs | 35 +-- rcgen/tests/openssl.rs | 147 +++++----- rcgen/tests/util.rs | 18 +- rcgen/tests/webpki.rs | 288 ++++++++++-------- rustls-cert-gen/src/cert.rs | 58 ++-- rustls-cert-gen/src/main.rs | 7 +- 17 files changed, 735 insertions(+), 543 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 210752ac..3493fcfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -862,7 +862,7 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.12.0" +version = "0.13.0" dependencies = [ "aws-lc-rs", "botan", diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index b529d9e5..98059929 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rcgen" -version = "0.12.0" +version = "0.13.0" documentation = "https://docs.rs/rcgen" description.workspace = true repository.workspace = true diff --git a/rcgen/examples/rsa-irc-openssl.rs b/rcgen/examples/rsa-irc-openssl.rs index 4ed01e21..38a22de3 100644 --- a/rcgen/examples/rsa-irc-openssl.rs +++ b/rcgen/examples/rsa-irc-openssl.rs @@ -1,3 +1,5 @@ +use rcgen::CertifiedKey; + fn main() -> Result<(), Box> { use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName}; use std::fmt::Write; @@ -15,8 +17,8 @@ fn main() -> Result<(), Box> { let key_pair = rcgen::KeyPair::from_pem(&key_pair_pem)?; params.key_pair = Some(key_pair); - let cert = Certificate::from_params(params)?; - let pem_serialized = cert.serialize_pem()?; + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?; + let pem_serialized = cert.pem(); let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); let hash = ring::digest::digest(&ring::digest::SHA512, der_serialized); @@ -26,11 +28,11 @@ fn main() -> Result<(), Box> { }); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); - println!("{}", cert.serialize_private_key_pem()); + println!("{}", key_pair.serialize_pem()); std::fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", pem_serialized.as_bytes())?; fs::write("certs/cert.der", der_serialized)?; - fs::write("certs/key.pem", cert.serialize_private_key_pem().as_bytes())?; - fs::write("certs/key.der", cert.serialize_private_key_der())?; + fs::write("certs/key.pem", key_pair.serialize_pem().as_bytes())?; + fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } diff --git a/rcgen/examples/rsa-irc.rs b/rcgen/examples/rsa-irc.rs index 2e2911dd..8c30d821 100644 --- a/rcgen/examples/rsa-irc.rs +++ b/rcgen/examples/rsa-irc.rs @@ -1,3 +1,5 @@ +use rcgen::CertifiedKey; + fn main() -> Result<(), Box> { use rand::rngs::OsRng; use rsa::pkcs8::EncodePrivateKey; @@ -21,8 +23,8 @@ fn main() -> Result<(), Box> { let key_pair = rcgen::KeyPair::try_from(private_key_der.as_bytes()).unwrap(); params.key_pair = Some(key_pair); - let cert = Certificate::from_params(params)?; - let pem_serialized = cert.serialize_pem()?; + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?; + let pem_serialized = cert.pem(); let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); let hash = ring::digest::digest(&ring::digest::SHA512, der_serialized); @@ -32,11 +34,11 @@ fn main() -> Result<(), Box> { }); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); - println!("{}", cert.serialize_private_key_pem()); + println!("{}", key_pair.serialize_pem()); std::fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", pem_serialized.as_bytes())?; fs::write("certs/cert.der", der_serialized)?; - fs::write("certs/key.pem", cert.serialize_private_key_pem().as_bytes())?; - fs::write("certs/key.der", cert.serialize_private_key_der())?; + fs::write("certs/key.pem", key_pair.serialize_pem().as_bytes())?; + fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } diff --git a/rcgen/examples/sign-leaf-with-ca.rs b/rcgen/examples/sign-leaf-with-ca.rs index ae8c83af..d1f4b250 100644 --- a/rcgen/examples/sign-leaf-with-ca.rs +++ b/rcgen/examples/sign-leaf-with-ca.rs @@ -1,22 +1,22 @@ use rcgen::{ - BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString, - ExtendedKeyUsagePurpose, IsCa, KeyUsagePurpose, + BasicConstraints, Certificate, CertificateParams, CertifiedKey, DnType, + DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyUsagePurpose, }; use time::{Duration, OffsetDateTime}; /// Example demonstrating signing end-endity certificate with ca fn main() { - let ca = new_ca(); + let ca = new_ca().cert; let end_entity = new_end_entity(); - let end_entity_pem = end_entity.serialize_pem_with_signer(&ca).unwrap(); + let end_entity_pem = end_entity.pem(); println!("directly signed end-entity certificate: {end_entity_pem}"); - let ca_cert_pem = ca.serialize_pem().unwrap(); + let ca_cert_pem = ca.pem(); println!("ca certificate: {ca_cert_pem}",); } -fn new_ca() -> Certificate { +fn new_ca() -> CertifiedKey { let mut params = CertificateParams::new(Vec::default()); let (yesterday, tomorrow) = validity_period(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); @@ -32,7 +32,7 @@ fn new_ca() -> Certificate { params.not_before = yesterday; params.not_after = tomorrow; - Certificate::from_params(params).unwrap() + Certificate::generate_self_signed(params).unwrap() } fn new_end_entity() -> Certificate { @@ -47,7 +47,7 @@ fn new_end_entity() -> Certificate { .push(ExtendedKeyUsagePurpose::ServerAuth); params.not_before = yesterday; params.not_after = tomorrow; - Certificate::from_params(params).unwrap() + Certificate::generate_self_signed(params).unwrap().cert } fn validity_period() -> (OffsetDateTime, OffsetDateTime) { diff --git a/rcgen/examples/simple.rs b/rcgen/examples/simple.rs index ba0da745..58824f29 100644 --- a/rcgen/examples/simple.rs +++ b/rcgen/examples/simple.rs @@ -1,4 +1,6 @@ -use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName, DnType, SanType}; +use rcgen::{ + date_time_ymd, Certificate, CertificateParams, CertifiedKey, DistinguishedName, DnType, SanType, +}; use std::fs; fn main() -> Result<(), Box> { @@ -17,17 +19,17 @@ fn main() -> Result<(), Box> { SanType::DnsName("localhost".to_string()), ]; - let cert = Certificate::from_params(params)?; + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params)?; - let pem_serialized = cert.serialize_pem()?; + let pem_serialized = cert.pem(); let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); println!("{pem_serialized}"); - println!("{}", cert.serialize_private_key_pem()); + println!("{}", key_pair.serialize_pem()); fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", pem_serialized.as_bytes())?; fs::write("certs/cert.der", der_serialized)?; - fs::write("certs/key.pem", cert.serialize_private_key_pem().as_bytes())?; - fs::write("certs/key.der", cert.serialize_private_key_der())?; + fs::write("certs/key.pem", key_pair.serialize_pem().as_bytes())?; + fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } diff --git a/rcgen/src/crl.rs b/rcgen/src/crl.rs index 75a89bcd..74d62d7e 100644 --- a/rcgen/src/crl.rs +++ b/rcgen/src/crl.rs @@ -9,7 +9,7 @@ use crate::oid::*; use crate::ENCODE_CONFIG; use crate::{ write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier, - write_x509_extension, + write_x509_extension, DistinguishedName, KeyPair, }; use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, SignatureAlgorithm}; @@ -26,7 +26,7 @@ use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, Sign /// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]); /// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); /// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign]; -/// let issuer = Certificate::from_params(issuer_params).unwrap(); +/// let issuer = Certificate::generate_self_signed(issuer_params).unwrap(); /// // Describe a revoked certificate. /// let revoked_cert = RevokedCertParams{ /// serial_number: SerialNumber::from(9999), @@ -64,19 +64,31 @@ impl CertificateRevocationList { } /// Serializes the certificate revocation list (CRL) in binary DER format, signed with /// the issuing certificate authority's key. - pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, Error> { + pub fn serialize_der_with_signer( + &self, + ca: &Certificate, + ca_key: &KeyPair, + ) -> Result, Error> { if !ca.params.key_usages.is_empty() && !ca.params.key_usages.contains(&KeyUsagePurpose::CrlSign) { return Err(Error::IssuerNotCrlSigner); } - self.params.serialize_der_with_signer(ca) + self.params.serialize_der_with_signer( + self.params.alg, + ca_key, + &ca.params.distinguished_name, + ) } /// Serializes the certificate revocation list (CRL) in ASCII PEM format, signed with /// the issuing certificate authority's key. #[cfg(feature = "pem")] - pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { - let contents = self.serialize_der_with_signer(ca)?; + pub fn serialize_pem_with_signer( + &self, + ca: &Certificate, + ca_key: &KeyPair, + ) -> Result { + let contents = self.serialize_der_with_signer(ca, ca_key)?; let p = Pem::new("X509 CRL", contents); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } @@ -172,12 +184,17 @@ pub struct CertificateRevocationListParams { } impl CertificateRevocationListParams { - fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, Error> { + fn serialize_der_with_signer( + &self, + sig_alg: &SignatureAlgorithm, + issuer: &KeyPair, + issuer_name: &DistinguishedName, + ) -> Result, Error> { yasna::try_construct_der(|writer| { // https://www.rfc-editor.org/rfc/rfc5280#section-5.1 writer.write_sequence(|writer| { let tbs_cert_list_serialized = yasna::try_construct_der(|writer| { - self.write_crl(writer, ca)?; + self.write_crl(writer, sig_alg, issuer, issuer_name)?; Ok::<(), Error>(()) })?; @@ -185,16 +202,22 @@ impl CertificateRevocationListParams { writer.next().write_der(&tbs_cert_list_serialized); // Write signatureAlgorithm - ca.params.alg.write_alg_ident(writer.next()); + sig_alg.write_alg_ident(writer.next()); // Write signature - ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?; + issuer.sign(&tbs_cert_list_serialized, writer.next())?; Ok(()) }) }) } - fn write_crl(&self, writer: DERWriter, ca: &Certificate) -> Result<(), Error> { + fn write_crl( + &self, + writer: DERWriter, + sig_alg: &SignatureAlgorithm, + issuer: &KeyPair, + issuer_name: &DistinguishedName, + ) -> Result<(), Error> { writer.write_sequence(|writer| { // Write CRL version. // RFC 5280 §5.1.2.1: @@ -211,12 +234,12 @@ impl CertificateRevocationListParams { // RFC 5280 §5.1.2.2: // This field MUST contain the same algorithm identifier as the // signatureAlgorithm field in the sequence CertificateList - ca.params.alg.write_alg_ident(writer.next()); + sig_alg.write_alg_ident(writer.next()); // Write issuer. // RFC 5280 §5.1.2.3: // The issuer field MUST contain a non-empty X.500 distinguished name (DN). - write_distinguished_name(writer.next(), &ca.params.distinguished_name); + write_distinguished_name(writer.next(), issuer_name); // Write thisUpdate date. // RFC 5280 §5.1.2.4: @@ -252,7 +275,10 @@ impl CertificateRevocationListParams { writer.next().write_tagged(Tag::context(0), |writer| { writer.write_sequence(|writer| { // Write authority key identifier. - write_x509_authority_key_identifier(writer.next(), ca); + write_x509_authority_key_identifier( + writer.next(), + self.key_identifier_method.derive(issuer.public_key_der()), + ); // Write CRL number. write_x509_extension(writer.next(), OID_CRL_NUMBER, false, |writer| { diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index e0d9eb6f..1871f92e 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -1,10 +1,8 @@ #[cfg(feature = "x509-parser")] -use crate::{DistinguishedName, SanType}; -#[cfg(feature = "pem")] -use pem::Pem; +use crate::{DistinguishedName, Error, SanType}; use std::hash::Hash; -use crate::{Certificate, CertificateParams, Error, PublicKeyData, SignatureAlgorithm}; +use crate::{CertificateParams, PublicKeyData, SignatureAlgorithm}; /// A public key, extracted from a CSR #[derive(Debug, PartialEq, Eq, Hash)] @@ -23,15 +21,15 @@ impl PublicKeyData for PublicKey { } } -/// Data for a certificate signing request -pub struct CertificateSigningRequest { +/// Parameters for a certificate signing request +pub struct CertificateSigningRequestParams { /// Parameters for the certificate to be signed. pub params: CertificateParams, /// Public key to include in the certificate signing request. pub public_key: PublicKey, } -impl CertificateSigningRequest { +impl CertificateSigningRequestParams { /// Parse a certificate signing request from the ASCII PEM format /// /// See [`from_der`](Self::from_der) for more details. @@ -92,17 +90,4 @@ impl CertificateSigningRequest { public_key: PublicKey { alg, raw }, }) } - /// Serializes the requested certificate, signed with another certificate's key, in binary DER format - pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, Error> { - self.params.serialize_der_with_signer(&self.public_key, ca) - } - /// Serializes the requested certificate, signed with another certificate's key, to the ASCII PEM format - #[cfg(feature = "pem")] - pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { - let contents = self - .params - .serialize_der_with_signer(&self.public_key, ca)?; - let p = Pem::new("CERTIFICATE", contents); - Ok(pem::encode_config(&p, crate::ENCODE_CONFIG)) - } } diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 00197ac6..1c61b08c 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -17,7 +17,7 @@ use crate::sign_algo::SignAlgo; use crate::ENCODE_CONFIG; use crate::{Error, SignatureAlgorithm}; -/// A key pair vairant +/// A key pair variant #[allow(clippy::large_enum_variant)] pub(crate) enum KeyPairKind { /// A Ecdsa key pair @@ -211,6 +211,26 @@ impl KeyPair { } } + /// Validate a provided key pair's compatibility with `sig_alg` or generate a new one. + /// + /// If a provided `existing_key_pair` is not compatible with the `sig_alg` an error is + /// returned. + /// + /// If `None` is provided for `existing_key_pair` a new key pair compatible with `sig_alg` + /// is generated from scratch. + pub(crate) fn validate_or_generate( + existing_key_pair: &mut Option, + sig_alg: &'static SignatureAlgorithm, + ) -> Result { + match existing_key_pair.take() { + Some(kp) if !kp.is_compatible(sig_alg) => { + return Err(Error::CertificateKeyPairMismatch) + }, + Some(kp) => Ok(kp), + None => KeyPair::generate(sig_alg), + } + } + /// Get the raw public key of this key pair /// /// The key is in raw format, as how [`ring::signature::KeyPair::public_key`] diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index cf89a9d3..3770646f 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -6,7 +6,7 @@ This crate provides a way to generate self signed X.509 certificates. The most simple way of using this crate is by calling the [`generate_simple_self_signed`] function. For more customization abilities, we provide the lower level -[`Certificate::from_params`] function. +[`Certificate::generate_self_signed`] and [`Certificate::generate`] functions. */ #![cfg_attr( feature = "pem", @@ -15,15 +15,15 @@ For more customization abilities, we provide the lower level ``` extern crate rcgen; -use rcgen::generate_simple_self_signed; +use rcgen::{generate_simple_self_signed, CertifiedKey}; # fn main () { // Generate a certificate that's valid for "localhost" and "hello.world.example" let subject_alt_names = vec!["hello.world.example".to_string(), "localhost".to_string()]; -let cert = generate_simple_self_signed(subject_alt_names).unwrap(); -println!("{}", cert.serialize_pem().unwrap()); -println!("{}", cert.serialize_private_key_pem()); +let CertifiedKey{cert, key_pair} = generate_simple_self_signed(subject_alt_names).unwrap(); +println!("{}", cert.pem()); +println!("{}", key_pair.serialize_pem()); # } ```"## )] @@ -55,7 +55,7 @@ pub use crate::crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams, }; -pub use crate::csr::{CertificateSigningRequest, PublicKey}; +pub use crate::csr::{CertificateSigningRequestParams, PublicKey}; pub use crate::error::Error; use crate::key_pair::PublicKeyData; pub use crate::key_pair::{KeyPair, RemoteKeyPair}; @@ -69,10 +69,19 @@ pub use crate::sign_algo::SignatureAlgorithm; )] pub type RcgenError = Error; -/// A self signed certificate together with signing keys +/// An issued certificate, together with the subject keypair. +pub struct CertifiedKey { + /// An issued certificate. + pub cert: Certificate, + /// The certificate's subject key pair. + pub key_pair: KeyPair, +} + +/// An issued certificate together with the parameters used to generate it. pub struct Certificate { params: CertificateParams, - key_pair: KeyPair, + subject_public_key_info: Vec, + der: Vec, } /** @@ -81,7 +90,7 @@ KISS function to generate a self signed certificate Given a set of domain names you want your certificate to be valid for, this function fills in the other generation parameters with reasonable defaults and generates a self signed certificate -as output. +and key pair as output. */ #[cfg_attr( feature = "pem", @@ -90,23 +99,23 @@ as output. ``` extern crate rcgen; -use rcgen::generate_simple_self_signed; +use rcgen::{generate_simple_self_signed, CertifiedKey}; # fn main () { let subject_alt_names :&[_] = &["hello.world.example".to_string(), "localhost".to_string()]; -let cert = generate_simple_self_signed(subject_alt_names).unwrap(); +let CertifiedKey{cert, key_pair} = generate_simple_self_signed(subject_alt_names).unwrap(); // The certificate is now valid for localhost and the domain "hello.world.example" -println!("{}", cert.serialize_pem().unwrap()); -println!("{}", cert.serialize_private_key_pem()); +println!("{}", cert.pem()); +println!("{}", key_pair.serialize_pem()); # } ``` "## )] pub fn generate_simple_self_signed( subject_alt_names: impl Into>, -) -> Result { - Certificate::from_params(CertificateParams::new(subject_alt_names)) +) -> Result { + Certificate::generate_self_signed(CertificateParams::new(subject_alt_names)) } // https://tools.ietf.org/html/rfc5280#section-4.1.1 @@ -537,7 +546,7 @@ pub struct CertificateParams { pub use_authority_key_identifier_extension: bool, /// Method to generate key identifiers from public keys /// - /// Defaults to SHA-256. + /// Defaults to a truncated SHA-256 digest. See [`KeyIdMethod`] for more information. pub key_identifier_method: KeyIdMethod, } @@ -569,7 +578,23 @@ impl Default for CertificateParams { } impl CertificateParams { - /// Parses a ca certificate from the ASCII PEM format for signing + /// Generate certificate parameters with reasonable defaults + pub fn new(subject_alt_names: impl Into>) -> Self { + let subject_alt_names = subject_alt_names + .into() + .into_iter() + .map(|s| match s.parse() { + Ok(ip) => SanType::IpAddress(ip), + Err(_) => SanType::DnsName(s), + }) + .collect::>(); + CertificateParams { + subject_alt_names, + ..Default::default() + } + } + + /// Parses an existing ca certificate from the ASCII PEM format. /// /// See [`from_ca_cert_der`](Self::from_ca_cert_der) for more details. #[cfg(all(feature = "pem", feature = "x509-parser"))] @@ -578,19 +603,21 @@ impl CertificateParams { Self::from_ca_cert_der(certificate.contents(), key_pair) } - /// Parses a ca certificate from the DER format for signing + /// Parses an existing ca certificate from the DER format. /// - /// This function is only of use if you have an existing ca certificate with - /// which you want to sign a certificate newly generated by `rcgen` using the - /// [`serialize_der_with_signer`](Certificate::serialize_der_with_signer) or - /// [`serialize_pem_with_signer`](Certificate::serialize_pem_with_signer) - /// functions. + /// This function is only of use if you have an existing CA certificate + /// you would like to use to sign a certificate generated by `rcgen`. + /// By providing the returned [`Certificate`] and the passed-through + /// [`KeyPair`] to [`Certificate::generate`] or + /// [`Certificate::from_request`] functions you can generate a new certificate + /// from [`CertificateParams`], signed by the pre-existing CA. /// - /// This function only extracts from the given ca cert the information - /// needed for signing. Any information beyond that is not extracted - /// and left to defaults. + /// In general this function only extracts the information needed for signing. + /// Other attributes of the [`Certificate`] may be left as defaults. /// - /// Will not check if certificate is a ca certificate! + /// This function assumes the provided certificate is a CA. It will not check + /// for the presence of the `BasicConstraints` extension, or perform any other + /// validation. #[cfg(feature = "x509-parser")] pub fn from_ca_cert_der(ca_cert: &[u8], key_pair: KeyPair) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) @@ -887,7 +914,7 @@ impl CertificateParams { writer.write_sequence(|writer| { // Write version writer.next().write_u8(0); - // Write issuer + // Write subject name write_distinguished_name(writer.next(), &distinguished_name); // Write subjectPublicKeyInfo pub_key.serialize_public_key_der(writer.next()); @@ -924,8 +951,11 @@ impl CertificateParams { &self, writer: DERWriter, pub_key: &K, - ca: &Certificate, + sig_alg: &SignatureAlgorithm, + issuer: &KeyPair, + issuer_name: &DistinguishedName, ) -> Result<(), Error> { + let pub_key_spki = yasna::construct_der(|writer| pub_key.serialize_public_key_der(writer)); writer.write_sequence(|writer| { // Write version writer.next().write_tagged(Tag::context(0), |writer| { @@ -941,10 +971,10 @@ impl CertificateParams { sl[0] = sl[0] & 0x7f; // MSB must be 0 to ensure encoding bignum in 20 bytes writer.next().write_bigint_bytes(&sl, true); }; - // Write signature - ca.params.alg.write_alg_ident(writer.next()); - // Write issuer - write_distinguished_name(writer.next(), &ca.params.distinguished_name); + // Write signature algorithm + sig_alg.write_alg_ident(writer.next()); + // Write issuer name + write_distinguished_name(writer.next(), issuer_name); // Write validity writer.next().write_sequence(|writer| { // Not before @@ -969,7 +999,10 @@ impl CertificateParams { writer.next().write_tagged(Tag::context(3), |writer| { writer.write_sequence(|writer| { if self.use_authority_key_identifier_extension { - write_x509_authority_key_identifier(writer.next(), ca) + write_x509_authority_key_identifier( + writer.next(), + self.key_identifier_method.derive(issuer.public_key_der()), + ); } // Write subject_alt_names if !self.subject_alt_names.is_empty() { @@ -1077,8 +1110,9 @@ impl CertificateParams { OID_SUBJECT_KEY_IDENTIFIER, false, |writer| { - let key_identifier = self.key_identifier(pub_key); - writer.write_bytes(key_identifier.as_ref()); + writer.write_bytes( + &self.key_identifier_method.derive(pub_key_spki), + ); }, ); // Write basic_constraints @@ -1106,8 +1140,9 @@ impl CertificateParams { OID_SUBJECT_KEY_IDENTIFIER, false, |writer| { - let key_identifier = self.key_identifier(pub_key); - writer.write_bytes(key_identifier.as_ref()); + writer.write_bytes( + &self.key_identifier_method.derive(pub_key_spki), + ); }, ); // Write basic_constraints @@ -1137,41 +1172,28 @@ impl CertificateParams { Ok(()) }) } - /// Calculates a subject key identifier for the certificate subject's public key. - /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. - fn key_identifier(&self, pub_key: &K) -> Vec { - // Decide which method from RFC 7093 to use - let digest_method = match &self.key_identifier_method { - KeyIdMethod::Sha256 => &digest::SHA256, - KeyIdMethod::Sha384 => &digest::SHA384, - KeyIdMethod::Sha512 => &digest::SHA512, - KeyIdMethod::PreSpecified(b) => { - return b.to_vec(); - }, - }; - let digest = digest::digest(digest_method, pub_key.raw_bytes()); - let truncated_digest = &digest.as_ref()[0..20]; - truncated_digest.to_vec() - } + fn serialize_der_with_signer( &self, pub_key: &K, - ca: &Certificate, + sig_alg: &SignatureAlgorithm, + issuer: &KeyPair, + issuer_name: &DistinguishedName, ) -> Result, Error> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let tbs_cert_list_serialized = yasna::try_construct_der(|writer| { - self.write_cert(writer, pub_key, ca)?; + self.write_cert(writer, pub_key, sig_alg, issuer, issuer_name)?; Ok::<(), Error>(()) })?; // Write tbsCertList writer.next().write_der(&tbs_cert_list_serialized); // Write signatureAlgorithm - ca.params.alg.write_alg_ident(writer.next()); + sig_alg.write_alg_ident(writer.next()); // Write signature - ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?; + issuer.sign(&tbs_cert_list_serialized, writer.next())?; Ok(()) }) @@ -1202,24 +1224,6 @@ pub enum BasicConstraints { Constrained(u8), } -impl CertificateParams { - /// Generate certificate parameters with reasonable defaults - pub fn new(subject_alt_names: impl Into>) -> Self { - let subject_alt_names = subject_alt_names - .into() - .into_iter() - .map(|s| match s.parse() { - Ok(ip) => SanType::IpAddress(ip), - Err(_) => SanType::DnsName(s), - }) - .collect::>(); - CertificateParams { - subject_alt_names, - ..Default::default() - } - } -} - /// The [NameConstraints extension](https://tools.ietf.org/html/rfc5280#section-4.2.1.10) /// (only relevant for CA certificates) #[derive(Debug, PartialEq, Eq, Clone)] @@ -1352,21 +1356,51 @@ impl CustomExtension { /// Method to generate key identifiers from public keys. /// -/// This allows choice over methods to generate key identifiers -/// as specified in RFC 7093 section 2. +/// Key identifiers should be derived from the public key data. [RFC 7093] defines +/// three methods to do so using a choice of SHA256 (method 1), SHA384 (method 2), or SHA512 +/// (method 3). In each case the first 160 bits of the hash are used as the key identifier +/// to match the output length that would be produced were SHA1 used (a legacy option defined +/// in RFC 5280). +/// +/// In addition to the RFC 7093 mechanisms, rcgen supports using a pre-specified key identifier. +/// This can be helpful when working with an existing `Certificate`. +/// +/// [RFC 7093]: https://www.rfc-editor.org/rfc/rfc7093 #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum KeyIdMethod { - /// RFC 7093 method 1 + /// RFC 7093 method 1 - a truncated SHA256 digest. Sha256, - /// RFC 7093 method 2 + /// RFC 7093 method 2 - a truncated SHA384 digest. Sha384, - /// RFC 7093 method 3 + /// RFC 7093 method 3 - a truncated SHA512 digest. Sha512, - /// Pre-specified identifier. + /// Pre-specified identifier. The exact given value is used as the key identifier. PreSpecified(Vec), } +impl KeyIdMethod { + /// Derive a key identifier for the provided subject public key info using the key ID method. + /// + /// Typically this is a truncated hash over the raw subject public key info, but may + /// be a pre-specified value. + /// + /// This key identifier is used in the SubjectKeyIdentifier and AuthorityKeyIdentifier + /// X.509v3 extensions. + pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec { + let digest_method = match &self { + Self::Sha256 => &digest::SHA256, + Self::Sha384 => &digest::SHA384, + Self::Sha512 => &digest::SHA512, + Self::PreSpecified(b) => { + return b.to_vec(); + }, + }; + let digest = digest::digest(digest_method, subject_public_key_info.as_ref()); + digest.as_ref()[0..20].to_vec() + } +} + /// Helper to obtain an `OffsetDateTime` from year, month, day values /// /// The year, month, day values are assumed to be in UTC. @@ -1477,21 +1511,100 @@ fn write_general_subtrees(writer: DERWriter, tag: u64, general_subtrees: &[Gener } impl Certificate { - /// Generates a new certificate from the given parameters. + /// Generates a new self-signed certificate from the given parameters. /// - /// If you want to control the [`KeyPair`] or the randomness used to generate it, set the [`CertificateParams::key_pair`] - /// field ahead of time before calling this function. - pub fn from_params(mut params: CertificateParams) -> Result { - let key_pair = if let Some(key_pair) = params.key_pair.take() { - if !key_pair.is_compatible(¶ms.alg) { - return Err(Error::CertificateKeyPairMismatch); - } - key_pair - } else { - KeyPair::generate(¶ms.alg)? - }; - - Ok(Certificate { params, key_pair }) + /// If [`CertificateParams::key_pair`] is not set to a pre-generated key, a new key pair + /// is generated using reasonable defaults. + /// + /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and + /// [`Certificate::pem`]. Similarly the private key of the returned [`KeyPair`] may be + /// serialized using [`KeyPair::serialize_der`] and [`KeyPair::serialize_pem`]. + pub fn generate_self_signed(mut params: CertificateParams) -> Result { + let key_pair = KeyPair::validate_or_generate(&mut params.key_pair, params.alg)?; + let subject_public_key_info = key_pair.public_key_der(); + let der = params.serialize_der_with_signer( + &key_pair, + params.alg, + &key_pair, + ¶ms.distinguished_name, + )?; + Ok(CertifiedKey { + cert: Certificate { + params, + subject_public_key_info, + der, + }, + key_pair, + }) + } + /// Generate a new certificate from the given parameters, signed by the provided issuer. + /// + /// If [`CertificateParams::key_pair`] is not set to a pre-generated key, a new key pair + /// is generated using reasonable defaults. + /// + /// The returned certificate will have its issuer field set to the subject of the + /// provided `issuer`, and the authority key identifier extension will be populated using + /// the subject public key of `issuer`. It will be signed by `issuer_key`. + /// + /// Note that no validation of the `issuer` certificate is performed. Rcgen will not require + /// the certificate to be a CA certificate, or have key usage extensions that allow signing. + /// + /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and + /// [`Certificate::pem`]. Similarly the private key of the returned [`KeyPair`] may be + /// serialized using [`KeyPair::serialize_der`] and [`KeyPair::serialize_pem`]. + pub fn generate( + mut params: CertificateParams, + issuer: &Certificate, + issuer_key: &KeyPair, + ) -> Result { + let key_pair = KeyPair::validate_or_generate(&mut params.key_pair, params.alg)?; + let subject_public_key_info = key_pair.public_key_der(); + let der = params.serialize_der_with_signer( + &key_pair, + &issuer.params.alg, + issuer_key, + &issuer.params.distinguished_name, + )?; + Ok(CertifiedKey { + cert: Certificate { + params, + subject_public_key_info, + der, + }, + key_pair, + }) + } + /// Generate a new certificate using the certificate signing request parameters, signed by + /// the provided issuer. + /// + /// The returned certificate will have its issuer field set to the subject of the provided + /// `issuer`, and the authority key identifier extension will be populated using the subject + /// public key of `issuer`. It will be signed by `issuer_key`. + /// + /// Note that no validation of the `issuer` certificate is performed. Rcgen will not require + /// the certificate to be a CA certificate, or have key usage extensions that allow signing. + /// + /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and + /// [`Certificate::pem`]. + pub fn from_request( + request: CertificateSigningRequestParams, + issuer: &Certificate, + issuer_key: &KeyPair, + ) -> Result { + let der = request.params.serialize_der_with_signer( + &request.public_key, + request.params.alg, + issuer_key, + &issuer.params.distinguished_name, + )?; + let subject_public_key_info = yasna::construct_der(|writer| { + request.public_key.serialize_public_key_der(writer); + }); + Ok(Certificate { + params: request.params, + subject_public_key_info, + der, + }) } /// Returns the certificate parameters pub fn get_params(&self) -> &CertificateParams { @@ -1500,22 +1613,34 @@ impl Certificate { /// Calculates a subject key identifier for the certificate subject's public key. /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. pub fn get_key_identifier(&self) -> Vec { - self.params.key_identifier(&self.key_pair) + self.params + .key_identifier_method + .derive(&self.subject_public_key_info) } - /// Serializes the certificate to the binary DER format - pub fn serialize_der(&self) -> Result, Error> { - self.serialize_der_with_signer(&self) + /// Get the certificate in DER encoded format. + pub fn der(&self) -> &[u8] { + &self.der } - /// Serializes the certificate, signed with another certificate's key, in binary DER format - pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, Error> { - self.params.serialize_der_with_signer(&self.key_pair, ca) + /// Get the certificate in PEM encoded format. + #[cfg(feature = "pem")] + pub fn pem(&self) -> String { + pem::encode_config(&Pem::new("CERTIFICATE", self.der()), ENCODE_CONFIG) } - /// Serializes a certificate signing request in binary DER format - pub fn serialize_request_der(&self) -> Result, Error> { + /// Generate and serialize a certificate signing request (CSR) in binary DER format. + /// + /// The constructed CSR will contain attributes based on the certificate parameters, + /// and include the subject public key information from `subject_key`. Additionally, + /// the CSR will be self-signed using the subject key. + /// + /// **Important**: since this function generates a new CSR for each invocation you + /// should not call `serialize_request_der` and then `serialize_request_pem`. This will + /// result in two different CSRs. Instead call only `serialize_request_pem` and base64 + /// decode the inner content to get the DER encoded CSR using a library like `pem`. + pub fn serialize_request_der(&self, subject_key: &KeyPair) -> Result, Error> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let cert_data = yasna::try_construct_der(|writer| { - self.params.write_request(&self.key_pair, writer) + self.params.write_request(subject_key, writer) })?; writer.next().write_der(&cert_data); @@ -1523,49 +1648,27 @@ impl Certificate { self.params.alg.write_alg_ident(writer.next()); // Write signature - self.key_pair.sign(&cert_data, writer.next())?; + subject_key.sign(&cert_data, writer.next())?; Ok(()) }) }) } - /// Return the certificate's key pair - pub fn get_key_pair(&self) -> &KeyPair { - &self.key_pair - } - /// Serializes the certificate to the ASCII PEM format - #[cfg(feature = "pem")] - pub fn serialize_pem(&self) -> Result { - let contents = self.serialize_der()?; - let p = Pem::new("CERTIFICATE", contents); - Ok(pem::encode_config(&p, ENCODE_CONFIG)) - } - /// Serializes the certificate, signed with another certificate's key, to the ASCII PEM format - #[cfg(feature = "pem")] - pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { - let contents = self.serialize_der_with_signer(ca)?; - let p = Pem::new("CERTIFICATE", contents); - Ok(pem::encode_config(&p, ENCODE_CONFIG)) - } - /// Serializes the certificate signing request to the ASCII PEM format - #[cfg(feature = "pem")] - pub fn serialize_request_pem(&self) -> Result { - let contents = self.serialize_request_der()?; - let p = Pem::new("CERTIFICATE REQUEST", contents); - Ok(pem::encode_config(&p, ENCODE_CONFIG)) - } - /// Serializes the private key in PKCS#8 format + /// Generate and serialize a certificate signing request (CSR) in binary DER format. /// - /// Panics if called on a remote key pair. - pub fn serialize_private_key_der(&self) -> Vec { - self.key_pair.serialize_der() - } - /// Serializes the private key in PEM format + /// The constructed CSR will contain attributes based on the certificate parameters, + /// and include the subject public key information from `subject_key`. Additionally, + /// the CSR will be self-signed using the subject key. /// - /// Panics if called on a remote key pair. + /// **Important**: since this function generates a new CSR for each invocation you + /// should not call `serialize_request_pem` and then `serialize_request_der`. This will + /// result in two different CSRs. Instead call only `serialize_request_pem` and base64 + /// decode the inner content to get the DER encoded CSR using a library like `pem`. #[cfg(feature = "pem")] - pub fn serialize_private_key_pem(&self) -> String { - self.key_pair.serialize_pem() + pub fn serialize_request_pem(&self, subject_key: &KeyPair) -> Result { + let contents = self.serialize_request_der(subject_key)?; + let p = Pem::new("CERTIFICATE REQUEST", contents); + Ok(pem::encode_config(&p, ENCODE_CONFIG)) } } @@ -1598,7 +1701,7 @@ fn write_x509_extension( } /// Serializes an X.509v3 authority key identifier extension according to RFC 5280. -fn write_x509_authority_key_identifier(writer: DERWriter, ca: &Certificate) { +fn write_x509_authority_key_identifier(writer: DERWriter, aki: Vec) { // Write Authority Key Identifier // RFC 5280 states: // 'The keyIdentifier field of the authorityKeyIdentifier extension MUST @@ -1613,9 +1716,7 @@ fn write_x509_authority_key_identifier(writer: DERWriter, ca: &Certificate) { writer.write_sequence(|writer| { writer .next() - .write_tagged_implicit(Tag::context(0), |writer| { - writer.write_bytes(ca.get_key_identifier().as_ref()) - }) + .write_tagged_implicit(Tag::context(0), |writer| writer.write_bytes(&aki)) }); }); } @@ -1628,15 +1729,7 @@ impl zeroize::Zeroize for KeyPair { } #[cfg(feature = "zeroize")] -impl zeroize::Zeroize for Certificate { - fn zeroize(&mut self) { - self.params.zeroize(); - self.key_pair.zeroize(); - } -} - -#[cfg(feature = "zeroize")] -impl zeroize::Zeroize for CertificateSigningRequest { +impl zeroize::Zeroize for CertificateSigningRequestParams { fn zeroize(&mut self) { self.params.zeroize(); } @@ -1760,13 +1853,10 @@ mod tests { params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); // Make the cert - let cert = Certificate::from_params(params).unwrap(); - - // Serialize it - let der = cert.serialize_der().unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Parse it - let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); // Check oid let key_usage_oid_str = "2.5.29.15"; @@ -1800,13 +1890,10 @@ mod tests { params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); // Make the cert - let cert = Certificate::from_params(params).unwrap(); - - // Serialize it - let der = cert.serialize_der().unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Parse it - let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); // Check oid let key_usage_oid_str = "2.5.29.15"; @@ -1837,13 +1924,10 @@ mod tests { params.extended_key_usages = vec![ExtendedKeyUsagePurpose::Any]; // Make the cert - let cert = Certificate::from_params(params).unwrap(); - - // Serialize it - let der = cert.serialize_der().unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Parse it - let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); // Ensure we found it. let maybe_extension = cert.extended_key_usage().unwrap(); @@ -1877,17 +1961,19 @@ mod tests { #[test] #[cfg(windows)] fn test_windows_line_endings() { - let cert = Certificate::from_params(CertificateParams::default()).unwrap(); - let pem = cert.serialize_pem().expect("Failed to serialize pem"); - assert!(pem.contains("\r\n")); + let cert = Certificate::generate_self_signed(CertificateParams::default()) + .unwrap() + .cert; + assert!(cert.pem().contains("\r\n")); } #[test] #[cfg(not(windows))] fn test_not_windows_line_endings() { - let cert = Certificate::from_params(CertificateParams::default()).unwrap(); - let pem = cert.serialize_pem().expect("Failed to serialize pem"); - assert!(!pem.contains('\r')); + let cert = Certificate::generate_self_signed(CertificateParams::default()) + .unwrap() + .cert; + assert!(!cert.pem().contains('\r')); } } @@ -2057,11 +2143,10 @@ PITGdT9dgN88nHPCle0B1+OY+OZ5 params.key_identifier_method ); - let ca_cert = Certificate::from_params(params).unwrap(); + let ca_cert = Certificate::generate_self_signed(params).unwrap().cert; assert_eq!(&expected_ski, &ca_cert.get_key_identifier()); - let ca_der = ca_cert.serialize_der().unwrap(); - let (_remainder, x509) = x509_parser::parse_x509_certificate(&ca_der).unwrap(); + let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert.der()).unwrap(); assert_eq!( &expected_ski, &x509 diff --git a/rcgen/tests/botan.rs b/rcgen/tests/botan.rs index e817420f..38666562 100644 --- a/rcgen/tests/botan.rs +++ b/rcgen/tests/botan.rs @@ -1,10 +1,10 @@ #![cfg(feature = "x509-parser")] -use rcgen::DnValue; use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa}; use rcgen::{ CertificateRevocationList, CertificateRevocationListParams, RevocationReason, RevokedCertParams, }; +use rcgen::{CertifiedKey, DnValue}; use rcgen::{KeyUsagePurpose, SerialNumber}; use time::{Duration, OffsetDateTime}; @@ -18,7 +18,7 @@ fn default_params() -> CertificateParams { } fn check_cert(cert_der: &[u8], cert: &Certificate) { - println!("{}", cert.serialize_pem().unwrap()); + println!("{}", cert.pem()); check_cert_ca(cert_der, cert, cert_der); } @@ -50,12 +50,10 @@ fn check_cert_ca(cert_der: &[u8], _cert: &Certificate, ca_der: &[u8]) { #[test] fn test_botan() { let params = default_params(); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -63,12 +61,10 @@ fn test_botan_256() { let mut params = default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -76,12 +72,10 @@ fn test_botan_384() { let mut params = default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -89,12 +83,10 @@ fn test_botan_25519() { let mut params = default_params(); params.alg = &rcgen::PKCS_ED25519; - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -105,12 +97,10 @@ fn test_botan_25519_v1_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -121,12 +111,10 @@ fn test_botan_25519_v2_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] @@ -137,21 +125,20 @@ fn test_botan_rsa_given() { let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert); + check_cert(cert.der(), &cert); } #[test] fn test_botan_separate_ca() { let mut params = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - - let ca_der = ca_cert.serialize_der().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -163,10 +150,10 @@ fn test_botan_separate_ca() { // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 1, 1); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); - - check_cert_ca(&cert_der, &cert, &ca_der); + let cert = Certificate::generate(params, &ca_cert, &ca_key) + .unwrap() + .cert; + check_cert_ca(cert.der(), &cert, ca_cert.der()); } #[cfg(feature = "x509-parser")] @@ -175,17 +162,20 @@ fn test_botan_imported_ca() { use std::convert::TryInto; let mut params = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: cert_key, + } = Certificate::generate_self_signed(params).unwrap(); - let (ca_cert_der, ca_key_der) = ( - ca_cert.serialize_der().unwrap(), - ca_cert.serialize_private_key_der(), - ); + let (ca_cert_der, ca_key_der) = (ca_cert.der(), cert_key.serialize_der()); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = - CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); - let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); + CertificateParams::from_ca_cert_der(ca_cert_der, ca_key_pair).unwrap(); + let CertifiedKey { + cert: imported_ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -196,10 +186,11 @@ fn test_botan_imported_ca() { .push(DnType::CommonName, "Dev domain"); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 1, 1); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); - check_cert_ca(&cert_der, &cert, &ca_cert_der); + let cert = Certificate::generate(params, &imported_ca_cert, &ca_key) + .unwrap() + .cert; + check_cert_ca(cert.der(), &cert, ca_cert_der); } #[cfg(feature = "x509-parser")] @@ -212,17 +203,20 @@ fn test_botan_imported_ca_with_printable_string() { DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); - let (ca_cert_der, ca_key_der) = ( - ca_cert.serialize_der().unwrap(), - ca_cert.serialize_private_key_der(), - ); + let (ca_cert_der, ca_key_der) = (ca_cert.der(), ca_key.serialize_der()); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = - CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); - let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); + CertificateParams::from_ca_cert_der(ca_cert_der, ca_key_pair).unwrap(); + let CertifiedKey { + cert: imported_ca_cert, + key_pair: imported_ca_key, + } = Certificate::generate_self_signed(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -233,10 +227,11 @@ fn test_botan_imported_ca_with_printable_string() { .push(DnType::CommonName, "Dev domain"); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 1, 1); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); + let cert = Certificate::generate(params, &imported_ca_cert, &imported_ca_key) + .unwrap() + .cert; - check_cert_ca(&cert_der, &cert, &ca_cert_der); + check_cert_ca(cert.der(), &cert, ca_cert_der); } #[test] @@ -251,7 +246,10 @@ fn test_botan_crl_parse() { KeyUsagePurpose::CrlSign, ]; issuer.alg = alg; - let issuer = Certificate::from_params(issuer).unwrap(); + let CertifiedKey { + cert: issuer, + key_pair: issuer_key, + } = Certificate::generate_self_signed(issuer).unwrap(); // Create an end entity cert issued by the issuer. let mut ee = util::default_params(); @@ -260,9 +258,10 @@ fn test_botan_crl_parse() { ee.serial_number = Some(SerialNumber::from(99999)); // Botan has a sanity check that enforces a maximum expiration date ee.not_after = rcgen::date_time_ymd(3016, 1, 1); - let ee = Certificate::from_params(ee).unwrap(); - let ee_der = ee.serialize_der_with_signer(&issuer).unwrap(); - let botan_ee = botan::Certificate::load(ee_der.as_ref()).unwrap(); + let ee = Certificate::generate(ee, &issuer, &issuer_key) + .unwrap() + .cert; + let botan_ee = botan::Certificate::load(ee.der()).unwrap(); // Generate a CRL with the issuer that revokes the EE cert. let now = OffsetDateTime::now_utc(); @@ -283,8 +282,8 @@ fn test_botan_crl_parse() { let crl = CertificateRevocationList::from_params(crl).unwrap(); // Serialize to both DER and PEM. - let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); - let crl_pem = crl.serialize_pem_with_signer(&issuer).unwrap(); + let crl_der = crl.serialize_der_with_signer(&issuer, &issuer_key).unwrap(); + let crl_pem = crl.serialize_pem_with_signer(&issuer, &issuer_key).unwrap(); // We should be able to load the CRL in both serializations. botan::CRL::load(crl_pem.as_ref()).unwrap(); diff --git a/rcgen/tests/generic.rs b/rcgen/tests/generic.rs index 582c6210..688bdd7e 100644 --- a/rcgen/tests/generic.rs +++ b/rcgen/tests/generic.rs @@ -43,7 +43,7 @@ mod test_key_params_mismatch { wrong_params.alg = *kalg_2; assert_eq!( - Certificate::from_params(wrong_params).err(), + Certificate::generate_self_signed(wrong_params).err(), Some(Error::CertificateKeyPairMismatch), "i: {} j: {}", i, @@ -75,15 +75,15 @@ mod test_convert_x509_subject_alternative_name { // Because we're using a function for CA certificates params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. - let ca_der = cert.serialize_der().unwrap(); + let ca_der = cert.der(); // Arbitrary key pair not used with the test, but required by the parsing function let key_pair = KeyPair::generate(&PKCS_ECDSA_P256_SHA256).unwrap(); - let actual = CertificateParams::from_ca_cert_der(&ca_der, key_pair).unwrap(); + let actual = CertificateParams::from_ca_cert_der(ca_der, key_pair).unwrap(); assert!(actual.subject_alt_names.contains(&ip_san)); } @@ -93,7 +93,7 @@ mod test_convert_x509_subject_alternative_name { mod test_x509_custom_ext { use crate::util; - use rcgen::{Certificate, CustomExtension}; + use rcgen::{Certificate, CertifiedKey, CustomExtension}; use x509_parser::oid_registry::asn1_rs; use x509_parser::prelude::{ FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest, @@ -118,9 +118,11 @@ mod test_x509_custom_ext { // Ensure the custom exts. being omitted into a CSR doesn't require SAN ext being present. // See https://github.com/rustls/rcgen/issues/122 params.subject_alt_names = Vec::default(); - let test_cert = Certificate::from_params(params).unwrap(); - let test_cert_der = test_cert.serialize_der().unwrap(); - let (_, x509_test_cert) = X509Certificate::from_der(&test_cert_der).unwrap(); + let CertifiedKey { + cert: test_cert, + key_pair: test_key, + } = Certificate::generate_self_signed(params).unwrap(); + let (_, x509_test_cert) = X509Certificate::from_der(test_cert.der()).unwrap(); // We should be able to find the extension by OID, with expected criticality and value. let favorite_drink_ext = x509_test_cert @@ -131,7 +133,7 @@ mod test_x509_custom_ext { assert_eq!(favorite_drink_ext.value, test_ext); // Generate a CSR with the custom extension, parse it with x509-parser. - let test_cert_csr_der = test_cert.serialize_request_der().unwrap(); + let test_cert_csr_der = test_cert.serialize_request_der(&test_key).unwrap(); let (_, x509_csr) = X509CertificationRequest::from_der(&test_cert_csr_der).unwrap(); // We should find that the CSR contains requested extensions. @@ -170,14 +172,13 @@ mod test_x509_parser_crl { #[test] fn parse_crl() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. - let (crl, issuer) = util::test_crl(); + let (crl, issuer, issuer_key) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); let revoked_cert_serial = BigUint::from_bytes_be(revoked_cert.serial_number.as_ref()); - let issuer_der = issuer.serialize_der().unwrap(); - let (_, x509_issuer) = X509Certificate::from_der(&issuer_der).unwrap(); + let (_, x509_issuer) = X509Certificate::from_der(issuer.der()).unwrap(); // Serialize the CRL signed by the issuer in DER form. - let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); + let crl_der = crl.serialize_der_with_signer(&issuer, &issuer_key).unwrap(); // We should be able to parse the CRL with x509-parser without error. let (_, x509_crl) = @@ -317,15 +318,15 @@ mod test_parse_ia5string_subject { email_address_dn_type.clone(), email_address_dn_value.clone(), ); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der().unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; + let cert_der = cert.der(); // We should be able to parse the certificate with x509-parser. - assert!(x509_parser::parse_x509_certificate(&cert_der).is_ok()); + assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); // We should be able to reconstitute params from the DER using x509-parser. let key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); - let params_from_cert = CertificateParams::from_ca_cert_der(&cert_der, key_pair).unwrap(); + let params_from_cert = CertificateParams::from_ca_cert_der(cert_der, key_pair).unwrap(); // We should find the expected distinguished name in the reconstituted params. let expected_names = &[(&email_address_dn_type, &email_address_dn_value)]; diff --git a/rcgen/tests/openssl.rs b/rcgen/tests/openssl.rs index 906f6c95..891d0b6d 100644 --- a/rcgen/tests/openssl.rs +++ b/rcgen/tests/openssl.rs @@ -7,8 +7,8 @@ use openssl::stack::Stack; use openssl::x509::store::{X509Store, X509StoreBuilder}; use openssl::x509::{CrlStatus, X509Crl, X509Req, X509StoreContext, X509}; use rcgen::{ - BasicConstraints, Certificate, CertificateParams, DnType, DnValue, GeneralSubtree, IsCa, - NameConstraints, + BasicConstraints, Certificate, CertificateParams, CertifiedKey, DnType, DnValue, + GeneralSubtree, IsCa, NameConstraints, }; use std::cell::RefCell; use std::io::{Error, ErrorKind, Read, Result as ioResult, Write}; @@ -17,7 +17,7 @@ use std::rc::Rc; mod util; fn verify_cert_basic(cert: &Certificate) { - let cert_pem = cert.serialize_pem().unwrap(); + let cert_pem = cert.pem(); println!("{cert_pem}"); let x509 = X509::from_pem(cert_pem.as_bytes()).unwrap(); @@ -89,12 +89,10 @@ impl Read for PipeEnd { } } -fn verify_cert(cert: &Certificate) { - verify_cert_basic(cert); - let cert_pem = cert.serialize_pem().unwrap(); - let key = cert.serialize_private_key_der(); - - verify_cert_ca(&cert_pem, &key, &cert_pem); +fn verify_cert(certified_key: &CertifiedKey) { + verify_cert_basic(&certified_key.cert); + let key = certified_key.key_pair.serialize_der(); + verify_cert_ca(&certified_key.cert.pem(), &key, &certified_key.cert.pem()); } fn verify_cert_ca(cert_pem: &str, key: &[u8], ca_cert_pem: &str) { @@ -162,10 +160,13 @@ fn verify_cert_ca(cert_pem: &str, key: &[u8], ca_cert_pem: &str) { // TODO read the data we just wrote from the streams } -fn verify_csr(cert: &Certificate) { - let csr = cert.serialize_request_pem().unwrap(); +fn verify_csr(certified_key: &CertifiedKey) { + let csr = certified_key + .cert + .serialize_request_pem(&certified_key.key_pair) + .unwrap(); println!("{csr}"); - let key = cert.serialize_private_key_der(); + let key = certified_key.key_pair.serialize_der(); let pkey = PKey::private_key_from_der(&key).unwrap(); let req = X509Req::from_pem(csr.as_bytes()).unwrap(); @@ -174,19 +175,12 @@ fn verify_csr(cert: &Certificate) { #[test] fn test_openssl() { - let params = util::default_params(); - let cert = Certificate::from_params(params).unwrap(); - - // Now verify the certificate. - verify_cert(&cert); + verify_cert(&Certificate::generate_self_signed(util::default_params()).unwrap()); } #[test] fn test_request() { - let params = util::default_params(); - let cert = Certificate::from_params(params).unwrap(); - - verify_csr(&cert); + verify_csr(&Certificate::generate_self_signed(util::default_params()).unwrap()); } #[test] @@ -194,11 +188,11 @@ fn test_openssl_256() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let cert = Certificate::from_params(params).unwrap(); + let certified_key = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - verify_cert(&cert); - verify_csr(&cert); + verify_cert(&certified_key); + verify_csr(&certified_key); } #[test] @@ -206,11 +200,11 @@ fn test_openssl_384() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; - let cert = Certificate::from_params(params).unwrap(); + let certified_key = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - verify_cert(&cert); - verify_csr(&cert); + verify_cert(&certified_key); + verify_csr(&certified_key); } #[test] @@ -218,7 +212,7 @@ fn test_openssl_25519() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. // TODO openssl doesn't support v2 keys (yet) @@ -236,17 +230,17 @@ fn test_openssl_25519_v1_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let certified_key = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate as well as CSR, // but only on OpenSSL >= 1.1.1 // On prior versions, only do basic verification #[allow(clippy::unusual_byte_groupings)] if openssl::version::number() >= 0x1_01_01_00_f { - verify_cert(&cert); - verify_csr(&cert); + verify_cert(&certified_key); + verify_csr(&certified_key); } else { - verify_cert_basic(&cert); + verify_cert_basic(&certified_key.cert); } } @@ -258,7 +252,7 @@ fn test_openssl_25519_v2_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let cert = Certificate::generate_self_signed(params).unwrap().cert; // Now verify the certificate. // TODO openssl doesn't support v2 keys (yet) @@ -276,11 +270,11 @@ fn test_openssl_rsa_given() { let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let certified_key = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - verify_cert(&cert); - verify_csr(&cert); + verify_cert(&certified_key); + verify_csr(&certified_key); } #[test] @@ -298,16 +292,16 @@ fn test_openssl_rsa_combinations_given() { let kp = rcgen::KeyPair::from_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, alg).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let certified_key = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. if i >= 4 { - verify_cert(&cert); - verify_csr(&cert); + verify_cert(&certified_key); + verify_csr(&certified_key); } else { // The PSS key types are not fully supported. // An attempt to use them gives a handshake error. - verify_cert_basic(&cert); + verify_cert_basic(&certified_key.cert); } } } @@ -316,8 +310,11 @@ fn test_openssl_rsa_combinations_given() { fn test_openssl_separate_ca() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - let ca_cert_pem = ca_cert.serialize_pem().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); + let ca_cert_pem = ca_cert.pem(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -326,11 +323,13 @@ fn test_openssl_separate_ca() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); - let key = cert.serialize_private_key_der(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); + let key = cert_key.serialize_der(); - verify_cert_ca(&cert_pem, &key, &ca_cert_pem); + verify_cert_ca(&cert.pem(), &key, &ca_cert_pem); } #[test] @@ -341,8 +340,10 @@ fn test_openssl_separate_ca_with_printable_string() { DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - let ca_cert_pem = ca_cert.serialize_pem().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -351,11 +352,13 @@ fn test_openssl_separate_ca_with_printable_string() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); - let key = cert.serialize_private_key_der(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); + let key = cert_key.serialize_der(); - verify_cert_ca(&cert_pem, &key, &ca_cert_pem); + verify_cert_ca(&cert.pem(), &key, &ca_cert.pem()); } #[test] @@ -363,8 +366,10 @@ fn test_openssl_separate_ca_with_other_signing_alg() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - let ca_cert_pem = ca_cert.serialize_pem().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; @@ -374,11 +379,13 @@ fn test_openssl_separate_ca_with_other_signing_alg() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); - let key = cert.serialize_private_key_der(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); + let key = cert_key.serialize_der(); - verify_cert_ca(&cert_pem, &key, &ca_cert_pem); + verify_cert_ca(&cert.pem(), &key, &ca_cert.pem()); } #[test] @@ -395,8 +402,10 @@ fn test_openssl_separate_ca_name_constraints() { //excluded_subtrees : vec![GeneralSubtree::DnsName(".v".to_string())], excluded_subtrees: Vec::new(), }); - let ca_cert = Certificate::from_params(params).unwrap(); - let ca_cert_pem = ca_cert.serialize_pem().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -405,23 +414,25 @@ fn test_openssl_separate_ca_name_constraints() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); - let key = cert.serialize_private_key_der(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); + let key = cert_key.serialize_der(); - verify_cert_ca(&cert_pem, &key, &ca_cert_pem); + verify_cert_ca(&cert.pem(), &key, &ca_cert.pem()); } #[test] fn test_openssl_crl_parse() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. - let (crl, issuer) = util::test_crl(); + let (crl, issuer, issuer_key) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); let revoked_cert_serial = &revoked_cert.serial_number; // Serialize the CRL signed by the issuer in both PEM and DER. - let crl_pem = crl.serialize_pem_with_signer(&issuer).unwrap(); - let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); + let crl_pem = crl.serialize_pem_with_signer(&issuer, &issuer_key).unwrap(); + let crl_der = crl.serialize_der_with_signer(&issuer, &issuer_key).unwrap(); // We should be able to parse the PEM form without error. assert!(X509Crl::from_pem(crl_pem.as_bytes()).is_ok()); @@ -430,7 +441,7 @@ fn test_openssl_crl_parse() { let openssl_crl = X509Crl::from_der(&crl_der).expect("failed to parse CRL DER"); // The properties of the CRL should match expected. - let openssl_issuer = X509::from_der(&issuer.serialize_der().unwrap()).unwrap(); + let openssl_issuer = X509::from_der(issuer.der()).unwrap(); let expected_last_update = Asn1Time::from_unix(crl.get_params().this_update.unix_timestamp()).unwrap(); assert!(openssl_crl.last_update().eq(&expected_last_update)); diff --git a/rcgen/tests/util.rs b/rcgen/tests/util.rs index 0ceba095..aea8f4d6 100644 --- a/rcgen/tests/util.rs +++ b/rcgen/tests/util.rs @@ -1,4 +1,4 @@ -use rcgen::{BasicConstraints, Certificate, CertificateParams}; +use rcgen::{BasicConstraints, Certificate, CertificateParams, CertifiedKey, KeyPair}; use rcgen::{ CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, }; @@ -80,7 +80,7 @@ pub fn default_params() -> CertificateParams { } #[allow(unused)] // Used by openssl + x509-parser features. -pub fn test_crl() -> (CertificateRevocationList, Certificate) { +pub fn test_crl() -> (CertificateRevocationList, Certificate, KeyPair) { let mut issuer = default_params(); issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); issuer.key_usages = vec![ @@ -88,7 +88,10 @@ pub fn test_crl() -> (CertificateRevocationList, Certificate) { KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign, ]; - let issuer = Certificate::from_params(issuer).unwrap(); + let CertifiedKey { + cert: issuer, + key_pair, + } = Certificate::generate_self_signed(issuer).unwrap(); let now = OffsetDateTime::now_utc(); let next_week = now + Duration::weeks(1); @@ -115,7 +118,7 @@ pub fn test_crl() -> (CertificateRevocationList, Certificate) { }; let crl = CertificateRevocationList::from_params(crl).unwrap(); - (crl, issuer) + (crl, issuer, key_pair) } #[allow(unused)] // Used by openssl + x509-parser features. @@ -133,6 +136,9 @@ pub fn cert_with_crl_dps() -> Vec { }, ]; - let cert = Certificate::from_params(params).unwrap(); - cert.serialize_der().unwrap() + Certificate::generate_self_signed(params) + .unwrap() + .cert + .der() + .to_vec() } diff --git a/rcgen/tests/webpki.rs b/rcgen/tests/webpki.rs index 2f074aec..30802f84 100644 --- a/rcgen/tests/webpki.rs +++ b/rcgen/tests/webpki.rs @@ -1,11 +1,12 @@ use rcgen::{ - BasicConstraints, Certificate, CertificateParams, DnType, Error, IsCa, KeyPair, RemoteKeyPair, + BasicConstraints, Certificate, CertificateParams, CertifiedKey, DnType, Error, IsCa, KeyPair, + RemoteKeyPair, }; use rcgen::{ CertificateRevocationList, CertificateRevocationListParams, RevocationReason, RevokedCertParams, }; #[cfg(feature = "x509-parser")] -use rcgen::{CertificateSigningRequest, DnValue}; +use rcgen::{CertificateSigningRequestParams, DnValue}; use rcgen::{ExtendedKeyUsagePurpose, KeyUsagePurpose, SerialNumber}; use webpki::SignatureAlgorithm; use webpki::{ @@ -23,8 +24,8 @@ use time::{Duration, OffsetDateTime}; mod util; -fn sign_msg_ecdsa(cert: &Certificate, msg: &[u8], alg: &'static EcdsaSigningAlgorithm) -> Vec { - let pk_der = cert.serialize_private_key_der(); +fn sign_msg_ecdsa(key_pair: &KeyPair, msg: &[u8], alg: &'static EcdsaSigningAlgorithm) -> Vec { + let pk_der = key_pair.serialize_der(); let key_pair = EcdsaKeyPair::from_pkcs8(alg, &pk_der, &ring::rand::SystemRandom::new()).unwrap(); let system_random = SystemRandom::new(); @@ -32,16 +33,16 @@ fn sign_msg_ecdsa(cert: &Certificate, msg: &[u8], alg: &'static EcdsaSigningAlgo signature.as_ref().to_vec() } -fn sign_msg_ed25519(cert: &Certificate, msg: &[u8]) -> Vec { - let pk_der = cert.serialize_private_key_der(); +fn sign_msg_ed25519(key_pair: &KeyPair, msg: &[u8]) -> Vec { + let pk_der = key_pair.serialize_der(); let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&pk_der).unwrap(); let signature = key_pair.sign(msg); signature.as_ref().to_vec() } #[cfg(feature = "pem")] -fn sign_msg_rsa(cert: &Certificate, msg: &[u8], encoding: &'static dyn RsaEncoding) -> Vec { - let pk_der = cert.serialize_private_key_der(); +fn sign_msg_rsa(key_pair: &KeyPair, msg: &[u8], encoding: &'static dyn RsaEncoding) -> Vec { + let pk_der = key_pair.serialize_der(); let key_pair = RsaKeyPair::from_pkcs8(&pk_der).unwrap(); let system_random = SystemRandom::new(); let mut signature = vec![0; key_pair.public().modulus_len()]; @@ -54,23 +55,24 @@ fn sign_msg_rsa(cert: &Certificate, msg: &[u8], encoding: &'static dyn RsaEncodi fn check_cert<'a, 'b>( cert_der: &[u8], cert: &'a Certificate, + cert_key: &'a KeyPair, alg: &SignatureAlgorithm, - sign_fn: impl FnOnce(&'a Certificate, &'b [u8]) -> Vec, + sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec, ) { #[cfg(feature = "pem")] { - println!("{}", cert.serialize_pem().unwrap()); + println!("{}", cert.pem()); } - check_cert_ca(cert_der, cert, cert_der, alg, alg, sign_fn); + check_cert_ca(cert_der, cert_key, cert_der, alg, alg, sign_fn); } fn check_cert_ca<'a, 'b>( cert_der: &[u8], - cert: &'a Certificate, + cert_key: &'a KeyPair, ca_der: &[u8], cert_alg: &SignatureAlgorithm, ca_alg: &SignatureAlgorithm, - sign_fn: impl FnOnce(&'a Certificate, &'b [u8]) -> Vec, + sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec, ) { let trust_anchor = TrustAnchor::try_from_cert_der(ca_der).unwrap(); let trust_anchor_list = &[trust_anchor]; @@ -99,7 +101,7 @@ fn check_cert_ca<'a, 'b>( // (3/3) Check that a message signed by the cert is valid. let msg = b"Hello, World! This message is signed."; - let signature = sign_fn(cert, msg); + let signature = sign_fn(cert_key, msg); end_entity_cert .verify_signature(cert_alg, msg, &signature) .expect("signature is valid"); @@ -108,13 +110,18 @@ fn check_cert_ca<'a, 'b>( #[test] fn test_webpki() { let params = util::default_params(); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); - check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); + let sign_fn = + |key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ECDSA_P256_SHA256, + sign_fn, + ); } #[test] @@ -122,13 +129,17 @@ fn test_webpki_256() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); - check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ECDSA_P256_SHA256, + sign_fn, + ); } #[test] @@ -136,13 +147,17 @@ fn test_webpki_384() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P384_SHA384_ASN1_SIGNING); - check_cert(&cert_der, &cert, &webpki::ECDSA_P384_SHA384, sign_fn); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ECDSA_P384_SHA384, + sign_fn, + ); } #[test] @@ -150,12 +165,16 @@ fn test_webpki_25519() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert, &webpki::ED25519, sign_msg_ed25519); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ED25519, + sign_msg_ed25519, + ); } #[cfg(feature = "pem")] @@ -167,12 +186,16 @@ fn test_webpki_25519_v1_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert, &webpki::ED25519, sign_msg_ed25519); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ED25519, + sign_msg_ed25519, + ); } #[cfg(feature = "pem")] @@ -184,12 +207,16 @@ fn test_webpki_25519_v2_given() { let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert, &webpki::ED25519, sign_msg_ed25519); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ED25519, + sign_msg_ed25519, + ); } #[cfg(feature = "pem")] @@ -201,14 +228,13 @@ fn test_webpki_rsa_given() { let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - check_cert( - &cert_der, + cert.der(), &cert, + &key_pair, &webpki::RSA_PKCS1_2048_8192_SHA256, |msg, cert| sign_msg_rsa(msg, cert, &signature::RSA_PKCS1_SHA256), ); @@ -241,12 +267,10 @@ fn test_webpki_rsa_combinations_given() { let kp = rcgen::KeyPair::from_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, c.0).unwrap(); params.key_pair = Some(kp); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - - check_cert(&cert_der, &cert, c.1, |msg, cert| { + check_cert(cert.der(), &cert, &key_pair, c.1, |msg, cert| { sign_msg_rsa(msg, cert, c.2) }); } @@ -256,9 +280,10 @@ fn test_webpki_rsa_combinations_given() { fn test_webpki_separate_ca() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - - let ca_der = ca_cert.serialize_der().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -268,14 +293,12 @@ fn test_webpki_separate_ca() { .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); - + let CertifiedKey { cert, key_pair } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( - &cert_der, - &cert, - &ca_der, + cert.der(), + &key_pair, + ca_cert.der(), &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, @@ -287,9 +310,10 @@ fn test_webpki_separate_ca_with_other_signing_alg() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; - let ca_cert = Certificate::from_params(params).unwrap(); - - let ca_der = ca_cert.serialize_der().unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params.alg = &rcgen::PKCS_ED25519; @@ -300,13 +324,11 @@ fn test_webpki_separate_ca_with_other_signing_alg() { .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); - + let CertifiedKey { cert, key_pair } = Certificate::generate(params, &ca_cert, &ca_key).unwrap(); check_cert_ca( - &cert_der, - &cert, - &ca_der, + cert.der(), + &key_pair, + ca_cert.der(), &webpki::ED25519, &webpki::ECDSA_P256_SHA256, sign_msg_ed25519, @@ -354,17 +376,24 @@ fn from_remote() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; params.key_pair = Some(remote); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { + cert, + key_pair: cert_kp, + } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - let sign_fn = move |_, msg| { let system_random = SystemRandom::new(); let signature = key_pair.sign(&system_random, msg).unwrap(); signature.as_ref().to_vec() }; - check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); + check_cert( + cert.der(), + &cert, + &cert_kp, + &webpki::ECDSA_P256_SHA256, + sign_fn, + ); } /* @@ -411,17 +440,20 @@ fn test_webpki_imported_ca() { use std::convert::TryInto; let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); - let (ca_cert_der, ca_key_der) = ( - ca_cert.serialize_der().unwrap(), - ca_cert.serialize_private_key_der(), - ); + let (ca_cert_der, ca_key_der) = (ca_cert.der(), ca_key.serialize_der()); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = - CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); - let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); + CertificateParams::from_ca_cert_der(ca_cert_der, ca_key_pair).unwrap(); + let CertifiedKey { + cert: imported_ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -430,14 +462,16 @@ fn test_webpki_imported_ca() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &imported_ca_cert, &ca_key).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( - &cert_der, - &cert, - &ca_cert_der, + cert.der(), + &cert_key, + ca_cert_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, @@ -454,17 +488,20 @@ fn test_webpki_imported_ca_with_printable_string() { DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); - let (ca_cert_der, ca_key_der) = ( - ca_cert.serialize_der().unwrap(), - ca_cert.serialize_private_key_der(), - ); + let (ca_cert_der, ca_key_der) = (ca_cert.der(), ca_key.serialize_der()); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = - CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); - let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); + CertificateParams::from_ca_cert_der(ca_cert_der, ca_key_pair).unwrap(); + let CertifiedKey { + cert: imported_ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params @@ -473,14 +510,16 @@ fn test_webpki_imported_ca_with_printable_string() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate(params, &imported_ca_cert, &ca_key).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( - &cert_der, - &cert, - &ca_cert_der, + cert.der(), + &cert_key, + ca_cert_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, @@ -497,21 +536,27 @@ fn test_certificate_from_csr() { params .distinguished_name .push(DnType::CommonName, "Dev domain"); - let cert = Certificate::from_params(params).unwrap(); - let csr_der = cert.serialize_request_der().unwrap(); - let csr = CertificateSigningRequest::from_der(&csr_der).unwrap(); + let CertifiedKey { + cert, + key_pair: cert_key, + } = Certificate::generate_self_signed(params).unwrap(); + let csr_der = cert.serialize_request_der(&cert_key).unwrap(); + let csr = CertificateSigningRequestParams::from_der(&csr_der).unwrap(); let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - let ca_cert = Certificate::from_params(params).unwrap(); - let ca_cert_der = ca_cert.serialize_der().unwrap(); - let cert_der = csr.serialize_der_with_signer(&ca_cert).unwrap(); - - let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); + let CertifiedKey { + cert: ca_cert, + key_pair: ca_key, + } = Certificate::generate_self_signed(params).unwrap(); + let cert = Certificate::from_request(csr, &ca_cert, &ca_key).unwrap(); + + let sign_fn = + |key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( - &cert_der, - &cert, - &ca_cert_der, + cert.der(), + &cert_key, + ca_cert.der(), &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, @@ -522,30 +567,34 @@ fn test_certificate_from_csr() { fn test_webpki_serial_number() { let mut params = util::default_params(); params.serial_number = Some(vec![0, 1, 2].into()); - let cert = Certificate::from_params(params).unwrap(); + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(params).unwrap(); // Now verify the certificate. - let cert_der = cert.serialize_der().unwrap(); - let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); - check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); + check_cert( + cert.der(), + &cert, + &key_pair, + &webpki::ECDSA_P256_SHA256, + sign_fn, + ); } #[test] fn test_webpki_crl_parse() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. - let (crl, issuer) = util::test_crl(); + let (crl, issuer, issuer_key) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); // Serialize the CRL signed by the issuer to DER. - let der = crl.serialize_der_with_signer(&issuer).unwrap(); + let der = crl.serialize_der_with_signer(&issuer, &issuer_key).unwrap(); // We should be able to parse the CRL DER without error. let webpki_crl = BorrowedCertRevocationList::from_der(&der).expect("failed to parse CRL DER"); // Webpki represents certificate SPKIs internally without the outer SEQUENCE. // We remove that here before calling verify_signature. - let issuer_spki = issuer.get_key_pair().public_key_der(); + let issuer_spki = issuer_key.public_key_der(); let raw_spki = yasna::parse_der(&issuer_spki, |reader| reader.read_tagged_der()).unwrap(); // We should be able to verify the CRL signature with the issuer's raw SPKI. @@ -584,8 +633,10 @@ fn test_webpki_crl_revoke() { KeyUsagePurpose::CrlSign, ]; issuer.alg = alg; - let issuer = Certificate::from_params(issuer).unwrap(); - let issuer_der = issuer.serialize_der().unwrap(); + let CertifiedKey { + cert: issuer, + key_pair: issuer_key, + } = Certificate::generate_self_signed(issuer).unwrap(); // Create an end entity cert issued by the issuer. let mut ee = util::default_params(); @@ -593,13 +644,14 @@ fn test_webpki_crl_revoke() { ee.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth]; ee.alg = alg; ee.serial_number = Some(SerialNumber::from(99999)); - let ee = Certificate::from_params(ee).unwrap(); - let ee_der = ee.serialize_der_with_signer(&issuer).unwrap(); + let ee = Certificate::generate(ee, &issuer, &issuer_key) + .unwrap() + .cert; // Set up webpki's verification requirements. - let trust_anchor = TrustAnchor::try_from_cert_der(issuer_der.as_ref()).unwrap(); + let trust_anchor = TrustAnchor::try_from_cert_der(issuer.der()).unwrap(); let trust_anchor_list = &[trust_anchor]; - let end_entity_cert = EndEntityCert::try_from(ee_der.as_ref()).unwrap(); + let end_entity_cert = EndEntityCert::try_from(ee.der()).unwrap(); let unix_time = 0x40_00_00_00; let time = Time::from_seconds_since_unix_epoch(unix_time); @@ -632,7 +684,7 @@ fn test_webpki_crl_revoke() { alg, }; let crl = CertificateRevocationList::from_params(crl).unwrap(); - let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); + let crl_der = crl.serialize_der_with_signer(&issuer, &issuer_key).unwrap(); let crl = BorrowedCertRevocationList::from_der(&crl_der).unwrap(); // The end entity cert should **not** validate when we provide a CRL that revokes the EE cert. diff --git a/rustls-cert-gen/src/cert.rs b/rustls-cert-gen/src/cert.rs index b28041cf..87e03c2e 100644 --- a/rustls-cert-gen/src/cert.rs +++ b/rustls-cert-gen/src/cert.rs @@ -1,7 +1,7 @@ use bpaf::Bpaf; use rcgen::{ - BasicConstraints, Certificate, CertificateParams, DistinguishedName, DnType, - DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyUsagePurpose, SanType, + BasicConstraints, Certificate, CertificateParams, CertifiedKey, DistinguishedName, DnType, + DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType, }; use std::{fmt, fs::File, io, path::Path}; @@ -103,26 +103,27 @@ impl CaBuilder { } /// build `Ca` Certificate. pub fn build(self) -> Result { - Ok(Ca { - cert: Certificate::from_params(self.params)?, - }) + let CertifiedKey { cert, key_pair } = Certificate::generate_self_signed(self.params)?; + Ok(Ca { cert, key_pair }) } } /// End-entity [Certificate] pub struct Ca { cert: Certificate, + key_pair: KeyPair, } impl Ca { /// Self-sign and serialize - pub fn serialize_pem(&self) -> Result { - Ok(PemCertifiedKey { - cert_pem: self.cert.serialize_pem()?, - private_key_pem: self.cert.serialize_private_key_pem(), - }) + pub fn serialize_pem(&self) -> PemCertifiedKey { + PemCertifiedKey { + cert_pem: self.cert.pem(), + private_key_pem: self.key_pair.serialize_pem(), + } } /// Return `&Certificate` + #[allow(dead_code)] pub fn cert(&self) -> &Certificate { &self.cert } @@ -131,15 +132,16 @@ impl Ca { /// End-entity [Certificate] pub struct EndEntity { cert: Certificate, + key_pair: KeyPair, } impl EndEntity { /// Sign with `signer` and serialize. - pub fn serialize_pem(&self, signer: &Certificate) -> Result { - Ok(PemCertifiedKey { - cert_pem: self.cert.serialize_pem_with_signer(signer)?, - private_key_pem: self.cert.serialize_private_key_pem(), - }) + pub fn serialize_pem(&self) -> PemCertifiedKey { + PemCertifiedKey { + cert_pem: self.cert.pem(), + private_key_pem: self.key_pair.serialize_pem(), + } } } @@ -188,10 +190,10 @@ impl EndEntityBuilder { self } /// build `EndEntity` Certificate. - pub fn build(self) -> Result { - Ok(EndEntity { - cert: Certificate::from_params(self.params)?, - }) + pub fn build(self, issuer: &Ca) -> Result { + let CertifiedKey { cert, key_pair } = + Certificate::generate(self.params, &issuer.cert, &issuer.key_pair)?; + Ok(EndEntity { cert, key_pair }) } } @@ -298,13 +300,13 @@ mod tests { let ca = CertificateBuilder::new().certificate_authority().build()?; let end_entity = CertificateBuilder::new() .end_entity() - .build()? - .serialize_pem(ca.cert())?; + .build(&ca)? + .serialize_pem(); let der = pem::parse(end_entity.cert_pem)?; let (_, cert) = X509Certificate::from_der(der.contents())?; - let issuer_der = pem::parse(ca.serialize_pem()?.cert_pem)?; + let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?; let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?; assert!(!cert.is_ca()); @@ -318,13 +320,13 @@ mod tests { let end_entity = CertificateBuilder::new() .signature_algorithm(&KeypairAlgorithm::EcdsaP384)? .end_entity() - .build()? - .serialize_pem(ca.cert())?; + .build(&ca)? + .serialize_pem(); let der = pem::parse(end_entity.cert_pem)?; let (_, cert) = X509Certificate::from_der(der.contents())?; - let issuer_der = pem::parse(ca.serialize_pem()?.cert_pem)?; + let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?; let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?; check_signature(&cert, &issuer); @@ -337,13 +339,13 @@ mod tests { let end_entity = CertificateBuilder::new() .signature_algorithm(&KeypairAlgorithm::Ed25519)? .end_entity() - .build()? - .serialize_pem(ca.cert())?; + .build(&ca)? + .serialize_pem(); let der = pem::parse(end_entity.cert_pem)?; let (_, cert) = X509Certificate::from_der(der.contents())?; - let issuer_der = pem::parse(ca.serialize_pem()?.cert_pem)?; + let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?; let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?; check_signature(&cert, &issuer); diff --git a/rustls-cert-gen/src/main.rs b/rustls-cert-gen/src/main.rs index a6dfa25c..8624a40a 100644 --- a/rustls-cert-gen/src/main.rs +++ b/rustls-cert-gen/src/main.rs @@ -30,12 +30,11 @@ fn main() -> anyhow::Result<()> { }; entity - .build()? - .serialize_pem(ca.cert())? + .build(&ca)? + .serialize_pem() .write(&opts.output, &opts.cert_file_name)?; - ca.serialize_pem()? - .write(&opts.output, &opts.ca_file_name)?; + ca.serialize_pem().write(&opts.output, &opts.ca_file_name)?; Ok(()) }