diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index 43b17a2a..5ebab44b 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -36,7 +36,7 @@ x509-parser = { workspace = true, features = ["verify"], optional = true } zeroize = { version = "1.2", optional = true } [features] -default = ["crypto", "pem", "ring"] +default = ["crypto", "pem", "ring", "x509-parser"] crypto = [] aws_lc_rs = ["crypto", "dep:aws-lc-rs", "aws-lc-rs/aws-lc-sys"] ring = ["crypto", "dep:ring"] diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index e00b8e8b..b1fa3908 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -147,9 +147,9 @@ impl CertificateParams { /// /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and /// [`Certificate::pem`]. - pub fn signed_by( + pub fn signed_by( self, - key_pair: &KeyPair, + key_pair: &K, issuer: &Certificate, issuer_key: &KeyPair, ) -> Result { @@ -160,7 +160,8 @@ impl CertificateParams { key_pair: issuer_key, }; - let subject_public_key_info = key_pair.public_key_der(); + let subject_public_key_info = + yasna::construct_der(|writer| key_pair.serialize_public_key_der(writer)); let der = self.serialize_der_with_signer(key_pair, issuer)?; Ok(Certificate { params: self, diff --git a/rcgen/src/error.rs b/rcgen/src/error.rs index 136d214b..9a369f35 100644 --- a/rcgen/src/error.rs +++ b/rcgen/src/error.rs @@ -45,6 +45,9 @@ pub enum Error { #[cfg(not(feature = "crypto"))] /// Missing serial number MissingSerialNumber, + /// X509 parsing error + #[cfg(feature = "x509-parser")] + X509, } impl fmt::Display for Error { @@ -91,6 +94,8 @@ impl fmt::Display for Error { )?, #[cfg(not(feature = "crypto"))] MissingSerialNumber => write!(f, "A serial number must be specified")?, + #[cfg(feature = "x509-parser")] + X509 => write!(f, "X509 error")?, }; Ok(()) } diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 6f5e7728..69a88660 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -57,6 +57,90 @@ impl fmt::Debug for KeyPairKind { } } +/// A public key +#[cfg(feature = "x509-parser")] +#[derive(Debug)] +pub struct SubjectPublicKey { + pub(crate) alg: &'static SignatureAlgorithm, + pub(crate) subject_public_key: Vec, +} + +#[cfg(feature = "x509-parser")] +impl SubjectPublicKey { + /// Create a `SubjectPublicKey` value from a PEM-encoded SubjectPublicKeyInfo string + pub fn from_pem(pem_str: &str) -> Result { + let spki_der = pem::parse(pem_str)._err()?.into_contents(); + Self::from_der(&spki_der) + } + + /// Create a `SubjectPublicKey` value from DER-encoded SubjectPublicKeyInfo bytes + pub fn from_der(spki_der: &[u8]) -> Result { + use x509_parser::{ + prelude::FromDer, + x509::{AlgorithmIdentifier, SubjectPublicKeyInfo}, + }; + + let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der).map_err(|_| Error::X509)?; + if !rem.is_empty() { + return Err(Error::X509); + } + let alg = SignatureAlgorithm::iter() + .find(|alg| { + let bytes = yasna::construct_der(|writer| { + alg.write_oids_sign_alg(writer); + }); + let Ok((rest, aid)) = AlgorithmIdentifier::from_der(&bytes) else { + return false; + }; + if !rest.is_empty() { + return false; + } + aid == spki.algorithm + }) + .ok_or(Error::UnsupportedSignatureAlgorithm)?; + Ok(Self { + alg, + subject_public_key: Vec::from(spki.subject_public_key.as_ref()), + }) + } +} + +#[cfg(feature = "x509-parser")] +impl PublicKeyData for SubjectPublicKey { + fn alg(&self) -> &SignatureAlgorithm { + self.alg + } + fn raw_bytes(&self) -> &[u8] { + &self.subject_public_key + } +} + +#[cfg(all(test, feature = "x509-parser", feature = "pem"))] +mod pkd_test { + use super::{KeyPair, PublicKeyData, SubjectPublicKey}; + use crate::{PKCS_ECDSA_P256_SHA256, PKCS_ECDSA_P384_SHA384, PKCS_ED25519}; + + #[test] + fn test_subject_public_key_parsing() { + // NOTE: the other algorithms supported by this crate don't support keygen + for alg in [ + &PKCS_ED25519, + &PKCS_ECDSA_P256_SHA256, + &PKCS_ECDSA_P384_SHA384, + ] { + let kp = KeyPair::generate_for(alg).expect("keygen"); + let pem = kp.public_key_pem(); + let der = kp.public_key_der(); + + let pkd_pem = SubjectPublicKey::from_pem(&pem).expect("from pem"); + assert_eq!(kp.raw_bytes(), pkd_pem.raw_bytes()); + + let pkd_der = SubjectPublicKey::from_der(&der).expect("from der"); + assert_eq!(kp.raw_bytes(), pkd_der.raw_bytes()); + } + } +} + /// A key pair used to sign certificates and CSRs /// /// Note that ring, the underlying library to handle RSA keys @@ -689,7 +773,7 @@ impl ExternalError for Result { } } -pub(crate) trait PublicKeyData { +pub trait PublicKeyData { fn alg(&self) -> &SignatureAlgorithm; fn raw_bytes(&self) -> &[u8]; diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 912e9d91..16ac0d0d 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -61,7 +61,7 @@ pub use error::{Error, InvalidAsn1String}; use key_pair::PublicKeyData; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub use key_pair::RsaKeySize; -pub use key_pair::{KeyPair, RemoteKeyPair}; +pub use key_pair::{KeyPair, RemoteKeyPair, SubjectPublicKey}; #[cfg(feature = "crypto")] use ring_like::digest; pub use sign_algo::algo::*;