diff --git a/Cargo.lock b/Cargo.lock index e0ff710f..5c0c5022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -986,6 +986,7 @@ dependencies = [ "rand", "rcgen", "ring", + "rustls-pki-types", "x509-parser", ] diff --git a/Cargo.toml b/Cargo.toml index a6af529d..b8645bff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" [workspace.dependencies] pem = "3.0.2" +pki-types = { package = "rustls-pki-types", version = "1.3.0" } rand = "0.8" ring = "0.17" x509-parser = "0.16" diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index 79ff4d9f..6e8993d3 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -30,6 +30,7 @@ aws-lc-rs = { version = "1.6.0", optional = true } yasna = { version = "0.5.2", features = ["time", "std"] } ring = { workspace = true, optional = true } pem = { workspace = true, optional = true } +pki-types = { workspace = true } time = { version = "0.3.6", default-features = false } x509-parser = { workspace = true, features = ["verify"], optional = true } zeroize = { version = "1.2", optional = true } @@ -48,7 +49,8 @@ features = ["x509-parser"] [package.metadata.cargo_check_external_types] allowed_external_types = [ "time::offset_date_time::OffsetDateTime", - "zeroize::Zeroize" + "zeroize::Zeroize", + "rustls_pki_types::*" ] [dev-dependencies] diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index a6311e7f..aa801585 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -3,6 +3,7 @@ use std::str::FromStr; #[cfg(feature = "pem")] use pem::Pem; +use pki_types::{CertificateDer, CertificateSigningRequestDer}; use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; use yasna::models::ObjectIdentifier; use yasna::{DERWriter, Tag}; @@ -23,7 +24,7 @@ use crate::{ pub struct Certificate { pub(crate) params: CertificateParams, pub(crate) subject_public_key_info: Vec, - pub(crate) der: Vec, + pub(crate) der: CertificateDer<'static>, } impl Certificate { @@ -39,13 +40,16 @@ impl Certificate { .derive(&self.subject_public_key_info) } /// Get the certificate in DER encoded format. - pub fn der(&self) -> &[u8] { + /// + /// As the return type implements `Deref`, in can easily be saved + /// to a file like a byte slice. + pub fn der(&self) -> &CertificateDer<'static> { &self.der } /// 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) + pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG) } /// Generate and serialize a certificate signing request (CSR) in binary DER format. /// @@ -57,7 +61,13 @@ impl Certificate { /// 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> { + /// + /// As the return type implements `Deref`, in can easily be saved + /// to a file like a byte slice. + 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| { @@ -74,6 +84,7 @@ impl Certificate { Ok(()) }) }) + .map(CertificateSigningRequestDer::from) } /// Generate and serialize a certificate signing request (CSR) in binary DER format. /// @@ -88,7 +99,7 @@ impl Certificate { #[cfg(feature = "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); + let p = Pem::new("CERTIFICATE REQUEST", contents.to_vec()); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } } @@ -218,7 +229,7 @@ impl CertificateParams { #[cfg(all(feature = "pem", feature = "x509-parser"))] pub fn from_ca_cert_pem(pem_str: &str) -> Result { let certificate = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificate))?; - Self::from_ca_cert_der(certificate.contents()) + Self::from_ca_cert_der(&certificate.contents().into()) } /// Parses an existing ca certificate from the DER format. @@ -236,8 +247,14 @@ impl CertificateParams { /// 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. + /// + /// You can use [`rustls_pemfile::certs`] to get the `ca_cert` input. If + /// you have already a byte slice, just calling `into()` and taking a reference + /// will convert it to [`CertificateDer`]. + /// + /// [`rustls_pemfile::certs`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.certs.html #[cfg(feature = "x509-parser")] - pub fn from_ca_cert_der(ca_cert: &[u8]) -> Result { + pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) .or(Err(Error::CouldNotParseCertificate))?; @@ -802,12 +819,17 @@ impl CertificateParams { Ok(()) }) } + + /// Generate and serialize a certificate signed by the given issuer. + /// + /// As the return type implements `Deref`, in can easily be saved + /// to a file like a byte slice. pub(crate) fn serialize_der_with_signer( &self, pub_key: &K, issuer: &KeyPair, issuer_name: &DistinguishedName, - ) -> Result, Error> { + ) -> Result, Error> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let tbs_cert_list_serialized = yasna::try_construct_der(|writer| { @@ -826,6 +848,7 @@ impl CertificateParams { Ok(()) }) }) + .map(CertificateDer::from) } } diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 8dee3452..dbff00e6 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "x509-parser")] +use pki_types::CertificateSigningRequestDer; + use std::hash::Hash; use crate::{Certificate, CertificateParams, Error, KeyPair, PublicKeyData, SignatureAlgorithm}; @@ -36,15 +39,21 @@ impl CertificateSigningRequestParams { #[cfg(all(feature = "pem", feature = "x509-parser"))] pub fn from_pem(pem_str: &str) -> Result { let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?; - Self::from_der(csr.contents()) + Self::from_der(&csr.contents().into()) } /// Parse a certificate signing request from DER-encoded bytes /// /// Currently, this only supports the `Subject Alternative Name` extension. /// On encountering other extensions, this function will return an error. + /// + /// You can use [`rustls_pemfile::csr`] to get the `csr` input. If + /// you have already a byte slice, just calling `into()` and taking a reference + /// will convert it to [`CertificateSigningRequestDer`]. + /// + /// [`rustls_pemfile::csr`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html #[cfg(feature = "x509-parser")] - pub fn from_der(csr: &[u8]) -> Result { + pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result { use x509_parser::prelude::FromDer; let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr) .map_err(|_| Error::CouldNotParseCertificationRequest)? diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 01d1e26e..fdbb983d 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -1,5 +1,7 @@ #[cfg(feature = "pem")] use pem::Pem; +#[cfg(feature = "crypto")] +use pki_types::PrivatePkcs8KeyDer; use std::fmt; use yasna::DERWriter; @@ -158,8 +160,14 @@ impl KeyPair { /// Parses the key pair from the DER format /// /// Equivalent to using the [`TryFrom`] implementation. + /// + /// You can use [`rustls_pemfile::private_key`] to get the `der` input. If + /// you have already a byte slice, just calling `into()` and taking a reference + /// will convert it to a [`PrivatePkcs8KeyDer`]. + /// + /// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html #[cfg(feature = "crypto")] - pub fn from_der(der: &[u8]) -> Result { + pub fn from_der(der: &PrivatePkcs8KeyDer<'_>) -> Result { der.try_into() } @@ -169,11 +177,15 @@ impl KeyPair { } /// Parses the key pair from the ASCII PEM format + /// + /// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; + /// + /// Appears as "PRIVATE KEY" in PEM files. #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pem(pem_str: &str) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key_der: &[_] = private_key.contents(); - private_key_der.try_into() + Self::from_der(&private_key_der.into()) } /// Obtains the key pair from a raw public key and a remote private key @@ -188,6 +200,9 @@ impl KeyPair { /// Obtains the key pair from a DER formatted key /// using the specified [`SignatureAlgorithm`] /// + /// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; + /// + /// Appears as "PRIVATE KEY" in PEM files /// Same as [from_pem_and_sign_algo](Self::from_pem_and_sign_algo). #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pem_and_sign_algo( @@ -196,7 +211,7 @@ impl KeyPair { ) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key_der: &[_] = private_key.contents(); - Self::from_der_and_sign_algo(private_key_der, alg) + Self::from_der_and_sign_algo(&PrivatePkcs8KeyDer::from(private_key_der), alg) } /// Obtains the key pair from a DER formatted key @@ -208,39 +223,45 @@ impl KeyPair { /// key pair. However, sometimes multiple signature algorithms fit for the /// same der key. In that instance, you can use this function to precisely /// specify the `SignatureAlgorithm`. + /// + /// You can use [`rustls_pemfile::private_key`] to get the `pkcs8` input. If + /// you have already a byte slice, just calling `into()` and taking a reference + /// will convert it to a [`PrivatePkcs8KeyDer`]. + /// + /// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html #[cfg(feature = "crypto")] pub fn from_der_and_sign_algo( - pkcs8: &[u8], + pkcs8: &PrivatePkcs8KeyDer<'_>, alg: &'static SignatureAlgorithm, ) -> Result { let rng = &SystemRandom::new(); - let pkcs8_vec = pkcs8.to_vec(); + let serialized_der = pkcs8.secret_pkcs8_der().to_vec(); let kind = if alg == &PKCS_ED25519 { - KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8)._err()?) + KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) } else if alg == &PKCS_ECDSA_P256_SHA256 { KeyPairKind::Ec(ecdsa_from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - pkcs8, + &serialized_der, rng, )?) } else if alg == &PKCS_ECDSA_P384_SHA384 { KeyPairKind::Ec(ecdsa_from_pkcs8( &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - pkcs8, + &serialized_der, rng, )?) } else if alg == &PKCS_RSA_SHA256 { - let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?; + let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) } else if alg == &PKCS_RSA_SHA384 { - let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?; + let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) } else if alg == &PKCS_RSA_SHA512 { - let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?; + let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) } else if alg == &PKCS_RSA_PSS_SHA256 { - let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?; + let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PSS_SHA256) } else { panic!("Unknown SignatureAlgorithm specified!"); @@ -249,14 +270,22 @@ impl KeyPair { Ok(KeyPair { kind, alg, - serialized_der: pkcs8_vec, + serialized_der, }) } + /// Parses the key pair from the DER format + /// + /// You can use [`rustls_pemfile::private_key`] to get the `pkcs8` input. If + /// you have already a byte slice, just calling `into()` and taking a reference + /// will convert it to a [`PrivatePkcs8KeyDer`]. + /// + /// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html #[cfg(feature = "crypto")] pub(crate) fn from_raw( - pkcs8: &[u8], + pkcs8: &PrivatePkcs8KeyDer, ) -> Result<(KeyPairKind, &'static SignatureAlgorithm), Error> { + let pkcs8 = pkcs8.secret_pkcs8_der(); let rng = SystemRandom::new(); let (kind, alg) = if let Ok(edkp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8) { (KeyPairKind::Ed(edkp), &PKCS_ED25519) @@ -399,7 +428,7 @@ impl TryFrom<&[u8]> for KeyPair { type Error = Error; fn try_from(pkcs8: &[u8]) -> Result { - let (kind, alg) = KeyPair::from_raw(pkcs8)?; + let (kind, alg) = KeyPair::from_raw(&pkcs8.into())?; Ok(KeyPair { kind, alg, @@ -413,7 +442,7 @@ impl TryFrom> for KeyPair { type Error = Error; fn try_from(pkcs8: Vec) -> Result { - let (kind, alg) = KeyPair::from_raw(pkcs8.as_slice())?; + let (kind, alg) = KeyPair::from_raw(&pkcs8.as_slice().into())?; Ok(KeyPair { kind, alg, @@ -422,6 +451,20 @@ impl TryFrom> for KeyPair { } } +#[cfg(feature = "crypto")] +impl TryFrom<&PrivatePkcs8KeyDer<'_>> for KeyPair { + type Error = Error; + + fn try_from(pkcs8: &PrivatePkcs8KeyDer) -> Result { + let (kind, alg) = KeyPair::from_raw(pkcs8)?; + Ok(KeyPair { + kind, + alg, + serialized_der: pkcs8.secret_pkcs8_der().into(), + }) + } +} + /// The key size used for RSA key generation #[cfg(all(feature = "crypto", feature = "aws_lc_rs", not(feature = "ring")))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/rcgen/tests/webpki.rs b/rcgen/tests/webpki.rs index 93c2a9f9..0e9944e4 100644 --- a/rcgen/tests/webpki.rs +++ b/rcgen/tests/webpki.rs @@ -54,7 +54,7 @@ fn sign_msg_rsa(key_pair: &KeyPair, msg: &[u8], encoding: &'static dyn RsaEncodi } fn check_cert<'a, 'b>( - cert_der: &[u8], + cert_der: &CertificateDer<'_>, cert: &'a Certificate, cert_key: &'a KeyPair, alg: &dyn SignatureVerificationAlgorithm, @@ -68,18 +68,16 @@ fn check_cert<'a, 'b>( } fn check_cert_ca<'a, 'b>( - cert_der: &[u8], + cert_der: &CertificateDer<'_>, cert_key: &'a KeyPair, - ca_der: &[u8], + ca_der: &CertificateDer<'_>, cert_alg: &dyn SignatureVerificationAlgorithm, ca_alg: &dyn SignatureVerificationAlgorithm, sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec, ) { - let ca_der = CertificateDer::from(ca_der); - let trust_anchor = anchor_from_trusted_cert(&ca_der).unwrap(); + let trust_anchor = anchor_from_trusted_cert(ca_der).unwrap(); let trust_anchor_list = &[trust_anchor]; - let cert_der = CertificateDer::from(cert_der); - let end_entity_cert = EndEntityCert::try_from(&cert_der).unwrap(); + let end_entity_cert = EndEntityCert::try_from(cert_der).unwrap(); // Set time to Jan 10, 2004 let time = UnixTime::since_unix_epoch(StdDuration::from_secs(0x40_00_00_00)); @@ -592,11 +590,9 @@ fn test_webpki_crl_revoke() { let ee = ee.signed_by(&ee_key, &issuer, &issuer_key).unwrap(); // Set up webpki's verification requirements. - let ca_der = CertificateDer::from(issuer.der()); - let trust_anchor = anchor_from_trusted_cert(&ca_der).unwrap(); + let trust_anchor = anchor_from_trusted_cert(issuer.der()).unwrap(); let trust_anchor_list = &[trust_anchor]; - let ee_der = CertificateDer::from(ee.der()); - let end_entity_cert = EndEntityCert::try_from(&ee_der).unwrap(); + let end_entity_cert = EndEntityCert::try_from(ee.der()).unwrap(); let unix_time = 0x40_00_00_00; let time = UnixTime::since_unix_epoch(StdDuration::from_secs(unix_time)); diff --git a/rustls-cert-gen/Cargo.toml b/rustls-cert-gen/Cargo.toml index ba29e89b..b84cf465 100644 --- a/rustls-cert-gen/Cargo.toml +++ b/rustls-cert-gen/Cargo.toml @@ -10,6 +10,7 @@ keywords.workspace = true rcgen = { path = "../rcgen", default-features = false, features = ["pem", "ring"] } bpaf = { version = "0.9.5", features = ["derive"] } pem = { workspace = true } +pki-types = { workspace = true } ring = { workspace = true } rand = { workspace = true } anyhow = "1.0.75" diff --git a/rustls-cert-gen/src/cert.rs b/rustls-cert-gen/src/cert.rs index 3cb88f1c..714e0da6 100644 --- a/rustls-cert-gen/src/cert.rs +++ b/rustls-cert-gen/src/cert.rs @@ -238,7 +238,7 @@ impl KeyPairAlgorithm { let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng).or(Err(rcgen::Error::RingUnspecified))?; - rcgen::KeyPair::from_der_and_sign_algo(pkcs8_bytes.as_ref(), alg) + rcgen::KeyPair::from_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg) }, KeyPairAlgorithm::EcdsaP256 => { use ring::signature::EcdsaKeyPair; @@ -249,7 +249,7 @@ impl KeyPairAlgorithm { let pkcs8_bytes = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng) .or(Err(rcgen::Error::RingUnspecified))?; - rcgen::KeyPair::from_der_and_sign_algo(pkcs8_bytes.as_ref(), alg) + rcgen::KeyPair::from_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg) }, KeyPairAlgorithm::EcdsaP384 => { use ring::signature::EcdsaKeyPair; @@ -261,7 +261,7 @@ impl KeyPairAlgorithm { EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_ASN1_SIGNING, &rng) .or(Err(rcgen::Error::RingUnspecified))?; - rcgen::KeyPair::from_der_and_sign_algo(pkcs8_bytes.as_ref(), alg) + rcgen::KeyPair::from_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg) }, } }