From dcaca981be2e3ac3f032eb1680f5108db93cf7fa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 24 Sep 2024 12:03:24 +0200 Subject: [PATCH 1/6] Clarify PublicKeyData trait method names --- rcgen/src/certificate.rs | 2 +- rcgen/src/csr.rs | 4 ++-- rcgen/src/key_pair.rs | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 1d634c37..643051db 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -607,7 +607,7 @@ impl CertificateParams { } else { #[cfg(feature = "crypto")] { - let hash = digest::digest(&digest::SHA256, pub_key.raw_bytes()); + let hash = digest::digest(&digest::SHA256, pub_key.der_bytes()); // RFC 5280 specifies at most 20 bytes for a serial number let mut sl = hash.as_ref()[0..20].to_vec(); sl[0] &= 0x7f; // MSB must be 0 to ensure encoding bignum in 20 bytes diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 79fab8e2..7ba2ce7b 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -27,11 +27,11 @@ impl PublicKey { } impl PublicKeyData for PublicKey { - fn alg(&self) -> &SignatureAlgorithm { + fn algorithm(&self) -> &SignatureAlgorithm { self.alg } - fn raw_bytes(&self) -> &[u8] { + fn der_bytes(&self) -> &[u8] { &self.raw } } diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 6f5e7728..83011abf 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -385,7 +385,7 @@ impl KeyPair { /// would output, and how [`ring::signature::UnparsedPublicKey::verify`] /// would accept. pub fn public_key_raw(&self) -> &[u8] { - self.raw_bytes() + self.der_bytes() } /// Check if this key pair can be used with the given signature algorithm @@ -637,10 +637,10 @@ pub enum RsaKeySize { } impl PublicKeyData for KeyPair { - fn alg(&self) -> &SignatureAlgorithm { + fn algorithm(&self) -> &SignatureAlgorithm { self.alg } - fn raw_bytes(&self) -> &[u8] { + fn der_bytes(&self) -> &[u8] { match &self.kind { #[cfg(feature = "crypto")] KeyPairKind::Ec(kp) => kp.public_key().as_ref(), @@ -690,14 +690,14 @@ impl ExternalError for Result { } pub(crate) trait PublicKeyData { - fn alg(&self) -> &SignatureAlgorithm; + fn algorithm(&self) -> &SignatureAlgorithm; - fn raw_bytes(&self) -> &[u8]; + fn der_bytes(&self) -> &[u8]; fn serialize_public_key_der(&self, writer: DERWriter) { writer.write_sequence(|writer| { - self.alg().write_oids_sign_alg(writer.next()); - let pk = self.raw_bytes(); + self.algorithm().write_oids_sign_alg(writer.next()); + let pk = self.der_bytes(); writer.next().write_bitvec_bytes(pk, pk.len() * 8); }) } From ecf817a986c77d7c4956524db8e36b6f8d4116a3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 24 Sep 2024 12:05:08 +0200 Subject: [PATCH 2/6] Change method order in PublicKeyData --- rcgen/src/csr.rs | 8 ++++---- rcgen/src/key_pair.rs | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 7ba2ce7b..5a8cd971 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -27,13 +27,13 @@ impl PublicKey { } impl PublicKeyData for PublicKey { - fn algorithm(&self) -> &SignatureAlgorithm { - self.alg - } - fn der_bytes(&self) -> &[u8] { &self.raw } + + fn algorithm(&self) -> &SignatureAlgorithm { + self.alg + } } /// A certificate signing request (CSR) that can be encoded to PEM or DER. diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 83011abf..50886bcc 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -637,9 +637,6 @@ pub enum RsaKeySize { } impl PublicKeyData for KeyPair { - fn algorithm(&self) -> &SignatureAlgorithm { - self.alg - } fn der_bytes(&self) -> &[u8] { match &self.kind { #[cfg(feature = "crypto")] @@ -651,6 +648,10 @@ impl PublicKeyData for KeyPair { KeyPairKind::Remote(kp) => kp.public_key(), } } + + fn algorithm(&self) -> &SignatureAlgorithm { + self.alg + } } /// A private key that is not directly accessible, but can be used to sign messages @@ -690,10 +691,6 @@ impl ExternalError for Result { } pub(crate) trait PublicKeyData { - fn algorithm(&self) -> &SignatureAlgorithm; - - fn der_bytes(&self) -> &[u8]; - fn serialize_public_key_der(&self, writer: DERWriter) { writer.write_sequence(|writer| { self.algorithm().write_oids_sign_alg(writer.next()); @@ -701,6 +698,10 @@ pub(crate) trait PublicKeyData { writer.next().write_bitvec_bytes(pk, pk.len() * 8); }) } + + fn der_bytes(&self) -> &[u8]; + + fn algorithm(&self) -> &SignatureAlgorithm; } #[cfg(all(test, feature = "crypto"))] From 90bd8c1f3f15909b2b2b45516524fdeb4f921281 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 24 Sep 2024 12:11:06 +0200 Subject: [PATCH 3/6] Extract serialize_public_key_der() from PublicKeyData --- rcgen/src/certificate.rs | 8 ++++---- rcgen/src/csr.rs | 5 +++-- rcgen/src/key_pair.rs | 18 +++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 643051db..8d4646ac 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -10,7 +10,7 @@ use yasna::{DERWriter, Tag}; use crate::crl::CrlDistributionPoint; use crate::csr::CertificateSigningRequest; -use crate::key_pair::PublicKeyData; +use crate::key_pair::{serialize_public_key_der, PublicKeyData}; #[cfg(feature = "crypto")] use crate::ring_like::digest; #[cfg(feature = "pem")] @@ -547,7 +547,7 @@ impl CertificateParams { // Write subject name write_distinguished_name(writer.next(), distinguished_name); // Write subjectPublicKeyInfo - subject_key.serialize_public_key_der(writer.next()); + serialize_public_key_der(subject_key, writer.next()); // Write extensions // According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag writer.next().write_tagged(Tag::context(0), |writer| { @@ -596,7 +596,7 @@ impl CertificateParams { ) -> Result, Error> { let der = issuer.key_pair.sign_der(|writer| { let pub_key_spki = - yasna::construct_der(|writer| pub_key.serialize_public_key_der(writer)); + yasna::construct_der(|writer| serialize_public_key_der(pub_key, writer)); // Write version writer.next().write_tagged(Tag::context(0), |writer| { writer.write_u8(2); @@ -633,7 +633,7 @@ impl CertificateParams { // Write subject write_distinguished_name(writer.next(), &self.distinguished_name); // Write subjectPublicKeyInfo - pub_key.serialize_public_key_der(writer.next()); + serialize_public_key_der(pub_key, writer.next()); // write extensions let should_write_exts = self.use_authority_key_identifier_extension || !self.subject_alt_names.is_empty() diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 5a8cd971..78cf9c0d 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -7,7 +7,8 @@ use pki_types::CertificateSigningRequestDer; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ - Certificate, CertificateParams, Error, Issuer, KeyPair, PublicKeyData, SignatureAlgorithm, + key_pair::serialize_public_key_der, Certificate, CertificateParams, Error, Issuer, KeyPair, + PublicKeyData, SignatureAlgorithm, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, SanType}; @@ -214,7 +215,7 @@ impl CertificateSigningRequestParams { .params .serialize_der_with_signer(&self.public_key, issuer)?; let subject_public_key_info = yasna::construct_der(|writer| { - self.public_key.serialize_public_key_der(writer); + serialize_public_key_der(&self.public_key, writer); }); Ok(Certificate { params: self.params, diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 50886bcc..b1d41f25 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -457,7 +457,7 @@ impl KeyPair { /// X.509. /// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1). pub fn public_key_der(&self) -> Vec { - yasna::construct_der(|writer| self.serialize_public_key_der(writer)) + yasna::construct_der(|writer| serialize_public_key_der(self, writer)) } /// Return the key pair's public key in PEM format @@ -691,19 +691,19 @@ impl ExternalError for Result { } pub(crate) trait PublicKeyData { - fn serialize_public_key_der(&self, writer: DERWriter) { - writer.write_sequence(|writer| { - self.algorithm().write_oids_sign_alg(writer.next()); - let pk = self.der_bytes(); - writer.next().write_bitvec_bytes(pk, pk.len() * 8); - }) - } - fn der_bytes(&self) -> &[u8]; fn algorithm(&self) -> &SignatureAlgorithm; } +pub(crate) fn serialize_public_key_der(key: &impl PublicKeyData, writer: DERWriter) { + writer.write_sequence(|writer| { + key.algorithm().write_oids_sign_alg(writer.next()); + let pk = key.der_bytes(); + writer.next().write_bitvec_bytes(pk, pk.len() * 8); + }) +} + #[cfg(all(test, feature = "crypto"))] mod test { use super::*; From b3ce6edab21387713c743ae9867b7bfc466a0fa4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 24 Sep 2024 12:12:45 +0200 Subject: [PATCH 4/6] Make PublicKeyData public --- rcgen/src/key_pair.rs | 5 ++++- rcgen/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index b1d41f25..3e039723 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -690,9 +690,12 @@ impl ExternalError for Result { } } -pub(crate) trait PublicKeyData { +/// The public key data of a key pair +pub trait PublicKeyData { + /// The public key in DER format fn der_bytes(&self) -> &[u8]; + /// The algorithm used by the key pair fn algorithm(&self) -> &SignatureAlgorithm; } diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 0caecbeb..4a1c18bc 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -58,7 +58,7 @@ pub use crl::{ }; pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey}; pub use error::{Error, InvalidAsn1String}; -use key_pair::PublicKeyData; +pub use key_pair::PublicKeyData; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub use key_pair::RsaKeySize; pub use key_pair::{KeyPair, RemoteKeyPair}; From afd74ae293906b8d284a2a1f5b521dc86159f425 Mon Sep 17 00:00:00 2001 From: kwantam Date: Tue, 24 Sep 2024 12:21:37 +0200 Subject: [PATCH 5/6] Enable signing with PublicKeyData --- rcgen/src/certificate.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 8d4646ac..dd62a362 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -149,7 +149,7 @@ impl CertificateParams { /// [`Certificate::pem`]. pub fn signed_by( self, - key_pair: &KeyPair, + public_key: &impl PublicKeyData, issuer: &Certificate, issuer_key: &KeyPair, ) -> Result { @@ -160,8 +160,9 @@ impl CertificateParams { key_pair: issuer_key, }; - let subject_public_key_info = key_pair.public_key_der(); - let der = self.serialize_der_with_signer(key_pair, issuer)?; + let subject_public_key_info = + yasna::construct_der(|writer| serialize_public_key_der(public_key, writer)); + let der = self.serialize_der_with_signer(public_key, issuer)?; Ok(Certificate { params: self, subject_public_key_info, From eb1266690c219badc0d65fb3f386a0788a3e4be6 Mon Sep 17 00:00:00 2001 From: kwantam Date: Tue, 24 Sep 2024 12:22:28 +0200 Subject: [PATCH 6/6] Add SubjectPublicKeyInfo type --- rcgen/src/error.rs | 5 +++ rcgen/src/key_pair.rs | 86 +++++++++++++++++++++++++++++++++++++++++++ rcgen/src/lib.rs | 2 +- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/rcgen/src/error.rs b/rcgen/src/error.rs index 136d214b..b33ecc0c 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(String), } 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(e) => write!(f, "X.509 parsing error: {e}")?, }; Ok(()) } diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 3e039723..d008b85d 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -690,6 +690,68 @@ impl ExternalError for Result { } } +/// A public key +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SubjectPublicKeyInfo { + pub(crate) alg: &'static SignatureAlgorithm, + pub(crate) subject_public_key: Vec, +} + +impl SubjectPublicKeyInfo { + /// Create a `SubjectPublicKey` value from a PEM-encoded SubjectPublicKeyInfo string + #[cfg(all(feature = "x509-parser", feature = "pem"))] + pub fn from_pem(pem_str: &str) -> Result { + Self::from_der(&pem::parse(pem_str)._err()?.into_contents()) + } + + /// Create a `SubjectPublicKey` value from DER-encoded SubjectPublicKeyInfo bytes + #[cfg(feature = "x509-parser")] + 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(|e| Error::X509(e.to_string()))?; + if !rem.is_empty() { + return Err(Error::X509( + "trailing bytes in SubjectPublicKeyInfo".to_string(), + )); + } + + 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()), + }) + } +} + +impl PublicKeyData for SubjectPublicKeyInfo { + fn der_bytes(&self) -> &[u8] { + &self.subject_public_key + } + + fn algorithm(&self) -> &SignatureAlgorithm { + self.alg + } +} + /// The public key data of a key pair pub trait PublicKeyData { /// The public key in DER format @@ -716,6 +778,30 @@ mod test { signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}, }; + #[cfg(all(feature = "x509-parser", feature = "pem"))] + #[test] + fn test_subject_public_key_parsing() { + for alg in [ + &PKCS_ED25519, + &PKCS_ECDSA_P256_SHA256, + &PKCS_ECDSA_P384_SHA384, + #[cfg(feature = "aws_lc_rs")] + &PKCS_ECDSA_P521_SHA512, + #[cfg(feature = "aws_lc_rs")] + &PKCS_RSA_SHA256, + ] { + let kp = KeyPair::generate_for(alg).expect("keygen"); + let pem = kp.public_key_pem(); + let der = kp.public_key_der(); + + let pkd_pem = SubjectPublicKeyInfo::from_pem(&pem).expect("from pem"); + assert_eq!(kp.der_bytes(), pkd_pem.der_bytes()); + + let pkd_der = SubjectPublicKeyInfo::from_der(&der).expect("from der"); + assert_eq!(kp.der_bytes(), pkd_der.der_bytes()); + } + } + #[test] fn test_algorithm() { let rng = SystemRandom::new(); diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 4a1c18bc..6aea3f2d 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -61,7 +61,7 @@ pub use error::{Error, InvalidAsn1String}; pub 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, SubjectPublicKeyInfo}; #[cfg(feature = "crypto")] use ring_like::digest; pub use sign_algo::algo::*;