From b0c2c7bebbde8a43b640bab1b6ce49363982bd29 Mon Sep 17 00:00:00 2001 From: jgreeer Date: Sun, 27 Sep 2026 22:59:14 +0000 Subject: [PATCH 1/4] sign_algo: extract SPKI algorithm identifier matching --- rcgen/src/key_pair.rs | 19 ++----------------- rcgen/src/sign_algo.rs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 7e6a2853..66a2f561 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -658,7 +658,7 @@ impl SubjectPublicKeyInfo { #[cfg(feature = "x509-parser")] pub fn from_der(spki_der: &[u8]) -> Result { use x509_parser::prelude::FromDer; - use x509_parser::x509::{AlgorithmIdentifier, SubjectPublicKeyInfo}; + use x509_parser::x509::SubjectPublicKeyInfo; let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der).map_err(|e| Error::X509(e.to_string()))?; @@ -668,23 +668,8 @@ impl SubjectPublicKeyInfo { )); } - 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, + alg: SignatureAlgorithm::from_alg_id(&spki.algorithm)?, subject_public_key: Vec::from(spki.subject_public_key.as_ref()), }) } diff --git a/rcgen/src/sign_algo.rs b/rcgen/src/sign_algo.rs index afd1b701..c63971c7 100644 --- a/rcgen/src/sign_algo.rs +++ b/rcgen/src/sign_algo.rs @@ -97,6 +97,26 @@ impl SignatureAlgorithm { } Err(Error::UnsupportedSignatureAlgorithm) } + + /// Retrieve the `SignatureAlgorithm` matching a `subjectPublicKeyInfo` algorithm identifier + #[cfg(feature = "x509-parser")] + pub(crate) fn from_alg_id( + alg_id: &x509_parser::x509::AlgorithmIdentifier<'_>, + ) -> Result<&'static Self, Error> { + use x509_parser::prelude::FromDer; + + Self::iter() + .find(|alg| { + let der = yasna::construct_der(|writer| alg.write_oids_sign_alg(writer)); + let Ok((rest, parsed)) = x509_parser::x509::AlgorithmIdentifier::from_der(&der) + else { + return false; + }; + rest.is_empty() && &parsed == alg_id + }) + .copied() + .ok_or(Error::UnsupportedSignatureAlgorithm) + } } /// The list of supported signature algorithms From 4a75decfb0050bf2aaace5c05bec880213fda60d Mon Sep 17 00:00:00 2001 From: jgreeer Date: Sun, 27 Sep 2026 22:59:14 +0000 Subject: [PATCH 2/4] sign_algo: add a public key algorithm type --- rcgen/src/lib.rs | 25 +++++- rcgen/src/sign_algo.rs | 194 +++++++++++++++++++++++++++++++++++------ 2 files changed, 193 insertions(+), 26 deletions(-) diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 77978434..e3cd35ce 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -83,7 +83,7 @@ pub use key_pair::{PublicKeyData, SigningKey, SubjectPublicKeyInfo}; mod sign_algo; pub use sign_algo::algo::*; -pub use sign_algo::SignatureAlgorithm; +pub use sign_algo::{key_alg, PublicKeyAlgorithm, SignatureAlgorithm}; mod oid; @@ -679,4 +679,27 @@ mod tests { } } } + + #[test] + fn algo_hash_agrees_with_eq() { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn hash(value: &impl Hash) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + for alg_i in SignatureAlgorithm::iter() { + for alg_j in SignatureAlgorithm::iter() { + assert!(alg_i != alg_j || hash(alg_i) == hash(alg_j)); + } + } + for alg_i in PublicKeyAlgorithm::iter() { + for alg_j in PublicKeyAlgorithm::iter() { + assert!(alg_i != alg_j || hash(alg_i) == hash(alg_j)); + } + } + } } diff --git a/rcgen/src/sign_algo.rs b/rcgen/src/sign_algo.rs index c63971c7..bb2fddb0 100644 --- a/rcgen/src/sign_algo.rs +++ b/rcgen/src/sign_algo.rs @@ -30,11 +30,158 @@ pub(crate) enum SignatureAlgorithmParams { Null, } +/// The parameters of a public key's `AlgorithmIdentifier` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum PublicKeyParameters { + /// Omit the parameters + Absent, + /// Write null parameters + Null, + /// Write a named curve OID + NamedCurve(&'static [u64]), +} + +/// The algorithm of a public key, as identified in a `SubjectPublicKeyInfo` +#[derive(Clone)] +pub struct PublicKeyAlgorithm { + name: &'static str, + oid_components: &'static [u64], + params: PublicKeyParameters, +} + +impl PublicKeyAlgorithm { + #[cfg(test)] + pub(crate) fn iter() -> std::slice::Iter<'static, &'static PublicKeyAlgorithm> { + use key_alg::*; + static ALGORITHMS: &[&PublicKeyAlgorithm] = &[ + &RSA, + &ECDSA_P256, + &ECDSA_P384, + #[cfg(feature = "aws_lc_rs")] + &ECDSA_P521, + &ED25519, + #[cfg(feature = "aws_lc_rs")] + &ML_DSA_44, + #[cfg(feature = "aws_lc_rs")] + &ML_DSA_65, + #[cfg(feature = "aws_lc_rs")] + &ML_DSA_87, + ]; + ALGORITHMS.iter() + } + + /// Writes the algorithm identifier as it appears inside a `SubjectPublicKeyInfo` + pub(crate) fn write_alg_id(&self, writer: DERWriter) { + writer.write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(self.oid_components)); + match self.params { + PublicKeyParameters::Absent => {}, + PublicKeyParameters::Null => writer.next().write_null(), + PublicKeyParameters::NamedCurve(curve) => writer + .next() + .write_oid(&ObjectIdentifier::from_slice(curve)), + } + }); + } +} + +impl fmt::Debug for PublicKeyAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl PartialEq for PublicKeyAlgorithm { + fn eq(&self, other: &Self) -> bool { + (self.oid_components, self.params) == (other.oid_components, other.params) + } +} + +impl Eq for PublicKeyAlgorithm {} + +/// The `Hash` trait is not derived, but implemented according to impl of the `PartialEq` trait +impl Hash for PublicKeyAlgorithm { + fn hash(&self, state: &mut H) { + (self.oid_components, self.params).hash(state); + } +} + +/// The list of supported public key algorithms +pub mod key_alg { + use super::{PublicKeyAlgorithm, PublicKeyParameters}; + use crate::oid::*; + + /// RSA public keys, as per [RFC 4055](https://tools.ietf.org/html/rfc4055) + pub static RSA: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "RSA", + oid_components: RSA_ENCRYPTION, + params: PublicKeyParameters::Null, + }; + + /// ECDSA public keys on the P-256 curve, as per [RFC 5480](https://tools.ietf.org/html/rfc5480) + pub static ECDSA_P256: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ECDSA_P256", + oid_components: EC_PUBLIC_KEY, + params: PublicKeyParameters::NamedCurve(EC_SECP_256_R1), + }; + + /// ECDSA public keys on the P-384 curve, as per [RFC 5480](https://tools.ietf.org/html/rfc5480) + pub static ECDSA_P384: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ECDSA_P384", + oid_components: EC_PUBLIC_KEY, + params: PublicKeyParameters::NamedCurve(EC_SECP_384_R1), + }; + + /// ECDSA public keys on the P-521 curve, as per [RFC 5480](https://tools.ietf.org/html/rfc5480) + /// + /// Only supported with the `aws_lc_rs` backend. + #[cfg(feature = "aws_lc_rs")] + pub static ECDSA_P521: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ECDSA_P521", + oid_components: EC_PUBLIC_KEY, + params: PublicKeyParameters::NamedCurve(EC_SECP_521_R1), + }; + + /// Ed25519 public keys, as per [RFC 8410](https://tools.ietf.org/html/rfc8410) + pub static ED25519: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ED25519", + // id-Ed25519 in RFC 8410 + oid_components: &[1, 3, 101, 112], + params: PublicKeyParameters::Absent, + }; + + /// ML-DSA-44 public keys, as per [RFC 9881](https://www.rfc-editor.org/rfc/rfc9881) + #[cfg(feature = "aws_lc_rs")] + pub static ML_DSA_44: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ML_DSA_44", + oid_components: crate::oid::ML_DSA_44, + params: PublicKeyParameters::Absent, + }; + + /// ML-DSA-65 public keys, as per [RFC 9881](https://www.rfc-editor.org/rfc/rfc9881) + #[cfg(feature = "aws_lc_rs")] + pub static ML_DSA_65: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ML_DSA_65", + oid_components: crate::oid::ML_DSA_65, + params: PublicKeyParameters::Absent, + }; + + /// ML-DSA-87 public keys, as per [RFC 9881](https://www.rfc-editor.org/rfc/rfc9881) + #[cfg(feature = "aws_lc_rs")] + pub static ML_DSA_87: PublicKeyAlgorithm = PublicKeyAlgorithm { + name: "ML_DSA_87", + oid_components: crate::oid::ML_DSA_87, + params: PublicKeyParameters::Absent, + }; +} + /// Signature algorithm type #[derive(Clone)] pub struct SignatureAlgorithm { name: &'static str, - oids_sign_alg: &'static [&'static [u64]], + key_alg: &'static PublicKeyAlgorithm, #[cfg(feature = "crypto")] pub(crate) sign_alg: SignAlgo, oid_components: &'static [u64], @@ -49,7 +196,7 @@ impl fmt::Debug for SignatureAlgorithm { impl PartialEq for SignatureAlgorithm { fn eq(&self, other: &Self) -> bool { - (self.oids_sign_alg, self.oid_components) == (other.oids_sign_alg, other.oid_components) + (self.key_alg, self.oid_components) == (other.key_alg, other.oid_components) } } @@ -58,8 +205,8 @@ impl Eq for SignatureAlgorithm {} /// The `Hash` trait is not derived, but implemented according to impl of the `PartialEq` trait impl Hash for SignatureAlgorithm { fn hash(&self, state: &mut H) { - // see SignatureAlgorithm::eq(), just this field is compared - self.oids_sign_alg.hash(state); + // see SignatureAlgorithm::eq(), just these fields are compared + (self.key_alg, self.oid_components).hash(state); } } impl SignatureAlgorithm { @@ -117,17 +264,21 @@ impl SignatureAlgorithm { .copied() .ok_or(Error::UnsupportedSignatureAlgorithm) } + + /// The algorithm of a public key that produces signatures with this algorithm + pub fn public_key_algorithm(&self) -> &'static PublicKeyAlgorithm { + self.key_alg + } } /// The list of supported signature algorithms pub(crate) mod algo { use super::*; - use crate::oid::*; /// RSA signing with PKCS#1 1.5 padding and SHA-256 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static RSA_PKCS1_SHA256: SignatureAlgorithm = SignatureAlgorithm { name: "RSA_PKCS1_SHA256", - oids_sign_alg: &[RSA_ENCRYPTION], + key_alg: &key_alg::RSA, #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA256), // sha256WithRSAEncryption in RFC 4055 @@ -138,7 +289,7 @@ pub(crate) mod algo { /// RSA signing with PKCS#1 1.5 padding and SHA-384 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static RSA_PKCS1_SHA384: SignatureAlgorithm = SignatureAlgorithm { name: "RSA_PKCS1_SHA384", - oids_sign_alg: &[RSA_ENCRYPTION], + key_alg: &key_alg::RSA, #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA384), // sha384WithRSAEncryption in RFC 4055 @@ -149,7 +300,7 @@ pub(crate) mod algo { /// RSA signing with PKCS#1 1.5 padding and SHA-512 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static RSA_PKCS1_SHA512: SignatureAlgorithm = SignatureAlgorithm { name: "RSA_PKCS1_SHA512", - oids_sign_alg: &[RSA_ENCRYPTION], + key_alg: &key_alg::RSA, #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA512), // sha512WithRSAEncryption in RFC 4055 @@ -160,7 +311,7 @@ pub(crate) mod algo { /// ECDSA signing using the P-256 curves and SHA-256 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) pub static ECDSA_P256_SHA256: SignatureAlgorithm = SignatureAlgorithm { name: "ECDSA_P256_SHA256", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_256_R1], + key_alg: &key_alg::ECDSA_P256, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P256_SHA256_ASN1_SIGNING), // ecdsa-with-SHA256 in RFC 5758 @@ -171,7 +322,7 @@ pub(crate) mod algo { /// ECDSA signing using the P-384 curves and SHA-384 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) pub static ECDSA_P384_SHA384: SignatureAlgorithm = SignatureAlgorithm { name: "ECDSA_P384_SHA384", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_384_R1], + key_alg: &key_alg::ECDSA_P384, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P384_SHA384_ASN1_SIGNING), // ecdsa-with-SHA384 in RFC 5758 @@ -187,7 +338,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ECDSA_P521_SHA256: SignatureAlgorithm = SignatureAlgorithm { name: "ECDSA_P521_SHA256", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], + key_alg: &key_alg::ECDSA_P521, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA256_ASN1_SIGNING), // ecdsa-with-SHA256 in RFC 5758 @@ -203,7 +354,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ECDSA_P521_SHA384: SignatureAlgorithm = SignatureAlgorithm { name: "ECDSA_P521_SHA384", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], + key_alg: &key_alg::ECDSA_P521, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA384_ASN1_SIGNING), // ecdsa-with-SHA384 in RFC 5758 @@ -217,7 +368,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ECDSA_P521_SHA512: SignatureAlgorithm = SignatureAlgorithm { name: "ECDSA_P521_SHA512", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], + key_alg: &key_alg::ECDSA_P521, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA512_ASN1_SIGNING), // ecdsa-with-SHA512 in RFC 5758 @@ -228,8 +379,7 @@ pub(crate) mod algo { /// ED25519 curve signing as per [RFC 8410](https://tools.ietf.org/html/rfc8410) pub static ED25519: SignatureAlgorithm = SignatureAlgorithm { name: "ED25519", - // id-Ed25519 in RFC 8410 - oids_sign_alg: &[&[1, 3, 101, 112]], + key_alg: &key_alg::ED25519, #[cfg(feature = "crypto")] sign_alg: SignAlgo::EdDsa(&signature::ED25519), // id-Ed25519 in RFC 8410 @@ -241,7 +391,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ML_DSA_44: SignatureAlgorithm = SignatureAlgorithm { name: "ML_DSA_44", - oids_sign_alg: &[crate::oid::ML_DSA_44], + key_alg: &key_alg::ML_DSA_44, #[cfg(feature = "crypto")] sign_alg: SignAlgo::PqDsa(&ML_DSA_44_SIGNING), oid_components: crate::oid::ML_DSA_44, @@ -252,7 +402,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ML_DSA_65: SignatureAlgorithm = SignatureAlgorithm { name: "ML_DSA_65", - oids_sign_alg: &[crate::oid::ML_DSA_65], + key_alg: &key_alg::ML_DSA_65, #[cfg(feature = "crypto")] sign_alg: SignAlgo::PqDsa(&ML_DSA_65_SIGNING), oid_components: crate::oid::ML_DSA_65, @@ -263,7 +413,7 @@ pub(crate) mod algo { #[cfg(feature = "aws_lc_rs")] pub static ML_DSA_87: SignatureAlgorithm = SignatureAlgorithm { name: "ML_DSA_87", - oids_sign_alg: &[crate::oid::ML_DSA_87], + key_alg: &key_alg::ML_DSA_87, #[cfg(feature = "crypto")] sign_alg: SignAlgo::PqDsa(&ML_DSA_87_SIGNING), oid_components: crate::oid::ML_DSA_87, @@ -292,12 +442,6 @@ impl SignatureAlgorithm { } /// Writes the algorithm identifier as it appears inside subjectPublicKeyInfo pub(crate) fn write_oids_sign_alg(&self, writer: DERWriter) { - writer.write_sequence(|writer| { - for oid in self.oids_sign_alg { - let oid = ObjectIdentifier::from_slice(oid); - writer.next().write_oid(&oid); - } - self.write_params(writer); - }); + self.key_alg.write_alg_id(writer) } } From 1d19d734a7d1f9b041bb49dd949b88167935d549 Mon Sep 17 00:00:00 2001 From: jgreeer Date: Sun, 27 Sep 2026 22:59:14 +0000 Subject: [PATCH 3/4] Separate signature and public key algorithms in key traits --- rcgen/src/crl.rs | 3 ++- rcgen/src/csr.rs | 7 +++--- rcgen/src/error.rs | 7 ++++++ rcgen/src/key_pair.rs | 44 +++++++++++++++++++++------------ rcgen/src/sign_algo.rs | 46 ++++++++++++++++------------------- rustls-cert-gen/src/cert.rs | 2 ++ verify-tests/tests/generic.rs | 2 +- verify-tests/tests/webpki.rs | 8 ++++-- 8 files changed, 71 insertions(+), 48 deletions(-) diff --git a/rcgen/src/crl.rs b/rcgen/src/crl.rs index a75b468e..f5b607f0 100644 --- a/rcgen/src/crl.rs +++ b/rcgen/src/crl.rs @@ -28,11 +28,12 @@ use crate::{ /// #[cfg(not(feature = "crypto"))] /// impl SigningKey for MyKeyPair { /// fn sign(&self, _: &[u8]) -> Result, rcgen::Error> { Ok(vec![]) } +/// fn algorithm(&self) -> &'static SignatureAlgorithm { &ED25519 } /// } /// #[cfg(not(feature = "crypto"))] /// impl PublicKeyData for MyKeyPair { /// fn der_bytes(&self) -> &[u8] { &self.public_key } -/// fn algorithm(&self) -> &'static SignatureAlgorithm { &ED25519 } +/// fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { &key_alg::ED25519 } /// } /// # fn main () { /// // Generate a CRL issuer. diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 33f1e8df..52942856 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, PublicKeyData, SignatureAlgorithm, SigningKey, + Certificate, CertificateParams, Error, Issuer, PublicKeyAlgorithm, PublicKeyData, + SignatureAlgorithm, SigningKey, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, ExtendedKeyUsagePurpose, GeneralName, IsCa, KeyUsagePurpose}; @@ -31,8 +32,8 @@ impl PublicKeyData for PublicKey { &self.raw } - fn algorithm(&self) -> &'static SignatureAlgorithm { - self.alg + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + self.alg.public_key_algorithm() } } diff --git a/rcgen/src/error.rs b/rcgen/src/error.rs index 9ba0b30e..c414c10c 100644 --- a/rcgen/src/error.rs +++ b/rcgen/src/error.rs @@ -28,6 +28,8 @@ pub enum Error { UnsupportedExtension, /// The requested signature algorithm is not supported UnsupportedSignatureAlgorithm, + /// The public key algorithm is not supported + UnsupportedPublicKeyAlgorithm, /// Unspecified `ring` error RingUnspecified, /// The `ring` library rejected the key upon loading @@ -84,6 +86,11 @@ impl fmt::Display for Error { "The requested signature algorithm \ is not supported" )?, + UnsupportedPublicKeyAlgorithm => write!( + f, + "The public key algorithm \ + is not supported" + )?, #[cfg(feature = "x509-parser")] UnsupportedExtension => write!(f, "Unsupported extension requested in CSR")?, RingUnspecified => write!(f, "Unspecified ring error")?, diff --git a/rcgen/src/key_pair.rs b/rcgen/src/key_pair.rs index 66a2f561..c8622165 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -26,9 +26,9 @@ use crate::ring_like::{ }, {ecdsa_from_pkcs8, rsa_key_pair_public_modulus_len}, }; -use crate::sign_algo::SignatureAlgorithm; #[cfg(feature = "crypto")] use crate::sign_algo::{algo::*, SignAlgo}; +use crate::sign_algo::{PublicKeyAlgorithm, SignatureAlgorithm}; use crate::Error; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; @@ -181,11 +181,6 @@ impl KeyPair { )) } - /// Returns the key pair's signature algorithm - pub fn algorithm(&self) -> &'static SignatureAlgorithm { - self.alg - } - /// Parses the key pair from the ASCII PEM format /// /// If `aws_lc_rs` feature is used, then the key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958, SEC1/RFC 5915, or PKCS#1/RFC 3447; @@ -448,6 +443,10 @@ impl SigningKey for KeyPair { }, }) } + + fn algorithm(&self) -> &'static SignatureAlgorithm { + self.alg + } } #[cfg(feature = "crypto")] @@ -462,8 +461,8 @@ impl PublicKeyData for KeyPair { } } - fn algorithm(&self) -> &'static SignatureAlgorithm { - self.alg + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + self.alg.public_key_algorithm() } } @@ -611,12 +610,19 @@ impl SigningKey for &S { fn sign(&self, msg: &[u8]) -> Result, Error> { (*self).sign(msg) } + + fn algorithm(&self) -> &'static SignatureAlgorithm { + (*self).algorithm() + } } /// A key that can be used to sign messages pub trait SigningKey: PublicKeyData { /// Signs `msg` using the selected algorithm fn sign(&self, msg: &[u8]) -> Result, Error>; + + /// The algorithm of the signatures this key produces + fn algorithm(&self) -> &'static SignatureAlgorithm; } #[cfg(feature = "crypto")] @@ -643,7 +649,7 @@ impl ExternalError for Result { /// A public key #[derive(Clone, Debug, Eq, PartialEq)] pub struct SubjectPublicKeyInfo { - pub(crate) alg: &'static SignatureAlgorithm, + pub(crate) alg: &'static PublicKeyAlgorithm, pub(crate) subject_public_key: Vec, } @@ -669,7 +675,7 @@ impl SubjectPublicKeyInfo { } Ok(Self { - alg: SignatureAlgorithm::from_alg_id(&spki.algorithm)?, + alg: PublicKeyAlgorithm::from_alg_id(&spki.algorithm)?, subject_public_key: Vec::from(spki.subject_public_key.as_ref()), }) } @@ -680,7 +686,7 @@ impl PublicKeyData for SubjectPublicKeyInfo { &self.subject_public_key } - fn algorithm(&self) -> &'static SignatureAlgorithm { + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { self.alg } } @@ -690,8 +696,8 @@ impl PublicKeyData for &K { (*self).der_bytes() } - fn algorithm(&self) -> &'static SignatureAlgorithm { - (*self).algorithm() + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + (*self).key_algorithm() } } @@ -708,8 +714,8 @@ pub trait PublicKeyData { /// The public key in DER format fn der_bytes(&self) -> &[u8]; - /// The algorithm used by the key pair - fn algorithm(&self) -> &'static SignatureAlgorithm; + /// The algorithm of the public key + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm; } /// Serialize private key to PEM format @@ -728,7 +734,7 @@ pub fn serialize_private_key_pem(key: &PrivateKeyDer<'_>) -> Result std::slice::Iter<'static, &'static PublicKeyAlgorithm> { use key_alg::*; static ALGORITHMS: &[&PublicKeyAlgorithm] = &[ @@ -70,6 +70,26 @@ impl PublicKeyAlgorithm { ALGORITHMS.iter() } + /// Retrieve the `PublicKeyAlgorithm` matching a parsed `AlgorithmIdentifier` + #[cfg(feature = "x509-parser")] + pub(crate) fn from_alg_id( + alg_id: &x509_parser::x509::AlgorithmIdentifier<'_>, + ) -> Result<&'static Self, Error> { + use x509_parser::prelude::FromDer; + + Self::iter() + .find(|alg| { + let der = yasna::construct_der(|writer| alg.write_alg_id(writer)); + let Ok((rest, parsed)) = x509_parser::x509::AlgorithmIdentifier::from_der(&der) + else { + return false; + }; + rest.is_empty() && &parsed == alg_id + }) + .copied() + .ok_or(Error::UnsupportedPublicKeyAlgorithm) + } + /// Writes the algorithm identifier as it appears inside a `SubjectPublicKeyInfo` pub(crate) fn write_alg_id(&self, writer: DERWriter) { writer.write_sequence(|writer| { @@ -245,26 +265,6 @@ impl SignatureAlgorithm { Err(Error::UnsupportedSignatureAlgorithm) } - /// Retrieve the `SignatureAlgorithm` matching a `subjectPublicKeyInfo` algorithm identifier - #[cfg(feature = "x509-parser")] - pub(crate) fn from_alg_id( - alg_id: &x509_parser::x509::AlgorithmIdentifier<'_>, - ) -> Result<&'static Self, Error> { - use x509_parser::prelude::FromDer; - - Self::iter() - .find(|alg| { - let der = yasna::construct_der(|writer| alg.write_oids_sign_alg(writer)); - let Ok((rest, parsed)) = x509_parser::x509::AlgorithmIdentifier::from_der(&der) - else { - return false; - }; - rest.is_empty() && &parsed == alg_id - }) - .copied() - .ok_or(Error::UnsupportedSignatureAlgorithm) - } - /// The algorithm of a public key that produces signatures with this algorithm pub fn public_key_algorithm(&self) -> &'static PublicKeyAlgorithm { self.key_alg @@ -440,8 +440,4 @@ impl SignatureAlgorithm { self.write_params(writer); }); } - /// Writes the algorithm identifier as it appears inside subjectPublicKeyInfo - pub(crate) fn write_oids_sign_alg(&self, writer: DERWriter) { - self.key_alg.write_alg_id(writer) - } } diff --git a/rustls-cert-gen/src/cert.rs b/rustls-cert-gen/src/cert.rs index 115e10c1..94fd950e 100644 --- a/rustls-cert-gen/src/cert.rs +++ b/rustls-cert-gen/src/cert.rs @@ -480,6 +480,8 @@ mod tests { #[test] fn key_pair_algorithm_to_keypair() -> anyhow::Result<()> { + use rcgen::SigningKey; + let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::Ed25519.into())?; assert_eq!(format!("{:?}", keypair.algorithm()), "ED25519"); diff --git a/verify-tests/tests/generic.rs b/verify-tests/tests/generic.rs index e4ae1fc9..3ef9dfa8 100644 --- a/verify-tests/tests/generic.rs +++ b/verify-tests/tests/generic.rs @@ -519,7 +519,7 @@ mod test_csr_extension_request { mod test_csr { use rcgen::{ CertificateParams, CertificateSigningRequestParams, ExtendedKeyUsagePurpose, KeyPair, - KeyUsagePurpose, + KeyUsagePurpose, SigningKey, }; #[test] diff --git a/verify-tests/tests/webpki.rs b/verify-tests/tests/webpki.rs index 4ccfadc7..d88887b7 100644 --- a/verify-tests/tests/webpki.rs +++ b/verify-tests/tests/webpki.rs @@ -393,6 +393,10 @@ fn from_remote() { .map(|s| s.as_ref().to_owned()) .map_err(|_| Error::RingUnspecified) } + + fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { + &rcgen::ECDSA_P256_SHA256 + } } impl PublicKeyData for Remote { @@ -400,8 +404,8 @@ fn from_remote() { self.0.public_key().as_ref() } - fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { - &rcgen::ECDSA_P256_SHA256 + fn key_algorithm(&self) -> &'static rcgen::PublicKeyAlgorithm { + &rcgen::key_alg::ECDSA_P256 } } From 8d48370a9a740f9e6243b1418fe4540fca3b3340 Mon Sep 17 00:00:00 2001 From: jgreeer Date: Sun, 27 Sep 2026 22:59:14 +0000 Subject: [PATCH 4/4] csr: read the public key algorithm from the SPKI --- rcgen/src/csr.rs | 30 ++++++++++-------------- rcgen/src/sign_algo.rs | 12 ++-------- verify-tests/tests/generic.rs | 4 ++-- verify-tests/tests/openssl.rs | 44 +++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 52942856..7fbdcbea 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -7,8 +7,7 @@ use pki_types::CertificateSigningRequestDer; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ - Certificate, CertificateParams, Error, Issuer, PublicKeyAlgorithm, PublicKeyData, - SignatureAlgorithm, SigningKey, + Certificate, CertificateParams, Error, Issuer, PublicKeyAlgorithm, PublicKeyData, SigningKey, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, ExtendedKeyUsagePurpose, GeneralName, IsCa, KeyUsagePurpose}; @@ -17,12 +16,12 @@ use crate::{DistinguishedName, ExtendedKeyUsagePurpose, GeneralName, IsCa, KeyUs #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PublicKey { raw: Vec, - alg: &'static SignatureAlgorithm, + alg: &'static PublicKeyAlgorithm, } impl PublicKey { - /// The algorithm used to generate the public key and sign the CSR. - pub fn algorithm(&self) -> &SignatureAlgorithm { + /// The algorithm of the public key + pub fn algorithm(&self) -> &'static PublicKeyAlgorithm { self.alg } } @@ -33,7 +32,7 @@ impl PublicKeyData for PublicKey { } fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { - self.alg.public_key_algorithm() + self.alg } } @@ -117,20 +116,18 @@ impl CertificateSigningRequestParams { .1; csr.verify_signature() .map_err(|_| Error::InvalidCertificationRequestSignature)?; - let alg_oid = csr - .signature_algorithm - .algorithm - .iter() - .ok_or(Error::CouldNotParseCertificationRequest)? - .collect::>(); - let alg = SignatureAlgorithm::from_oid(&alg_oid)?; let info = &csr.certification_request_info; + + let public_key = PublicKey { + raw: info.subject_pki.subject_public_key.data.to_vec(), + alg: PublicKeyAlgorithm::from_alg_id(&info.subject_pki.algorithm)?, + }; + let mut params = CertificateParams { distinguished_name: DistinguishedName::from_name(&info.subject)?, ..CertificateParams::default() }; - let raw = info.subject_pki.subject_public_key.data.to_vec(); if let Some(extensions) = csr.requested_extensions() { for ext in extensions { @@ -187,10 +184,7 @@ impl CertificateSigningRequestParams { // * name_constraints // and any other extensions. - Ok(Self { - params, - public_key: PublicKey { alg, raw }, - }) + Ok(Self { params, public_key }) } /// Generate a new certificate based on the requested parameters, signed by the provided diff --git a/rcgen/src/sign_algo.rs b/rcgen/src/sign_algo.rs index f8b97970..4843727a 100644 --- a/rcgen/src/sign_algo.rs +++ b/rcgen/src/sign_algo.rs @@ -10,6 +10,7 @@ use yasna::DERWriter; #[cfg(feature = "crypto")] use crate::ring_like::signature::{self, EcdsaSigningAlgorithm, EdDSAParameters, RsaEncoding}; +#[cfg(feature = "x509-parser")] use crate::Error; #[cfg(feature = "crypto")] @@ -230,6 +231,7 @@ impl Hash for SignatureAlgorithm { } } impl SignatureAlgorithm { + #[cfg(test)] pub(crate) fn iter() -> std::slice::Iter<'static, &'static SignatureAlgorithm> { use algo::*; static ALGORITHMS: &[&SignatureAlgorithm] = &[ @@ -255,16 +257,6 @@ impl SignatureAlgorithm { ALGORITHMS.iter() } - /// Retrieve the SignatureAlgorithm for the provided OID - pub fn from_oid(oid: &[u64]) -> Result<&'static SignatureAlgorithm, Error> { - for algo in Self::iter() { - if algo.oid_components == oid { - return Ok(algo); - } - } - Err(Error::UnsupportedSignatureAlgorithm) - } - /// The algorithm of a public key that produces signatures with this algorithm pub fn public_key_algorithm(&self) -> &'static PublicKeyAlgorithm { self.key_alg diff --git a/verify-tests/tests/generic.rs b/verify-tests/tests/generic.rs index 3ef9dfa8..037f37fa 100644 --- a/verify-tests/tests/generic.rs +++ b/verify-tests/tests/generic.rs @@ -519,7 +519,7 @@ mod test_csr_extension_request { mod test_csr { use rcgen::{ CertificateParams, CertificateSigningRequestParams, ExtendedKeyUsagePurpose, KeyPair, - KeyUsagePurpose, SigningKey, + KeyUsagePurpose, PublicKeyData, }; #[test] @@ -575,7 +575,7 @@ mod test_csr { let csrp = CertificateSigningRequestParams::from_der(csr.der()).unwrap(); // Ensure algorithms match. - assert_eq!(key_pair.algorithm(), csrp.public_key.algorithm()); + assert_eq!(key_pair.key_algorithm(), csrp.public_key.algorithm()); // Assert that our parsed parameters match our initial parameters assert_eq!(*params, csrp.params); } diff --git a/verify-tests/tests/openssl.rs b/verify-tests/tests/openssl.rs index 03aa6719..d8ee0bec 100644 --- a/verify-tests/tests/openssl.rs +++ b/verify-tests/tests/openssl.rs @@ -560,3 +560,47 @@ fn test_openssl_pkcs1_and_sec1_keys() { let pkcs8_ec_key_der = PrivateKeyDer::try_from(ec_key.private_key_to_pkcs8().unwrap()).unwrap(); KeyPair::try_from(&pkcs8_ec_key_der).unwrap(); } + +/// Tests that a CSR's subject public key survives issuance when the request's signature +/// algorithm names a different curve than the key. +/// +/// Deriving the key's algorithm from the signature algorithm produced a certificate whose +/// `subjectPublicKeyInfo` claimed secp384r1 over a P-256 point, which OpenSSL rejects with +/// `X509_PUBKEY_get0: decode error`. +#[test] +fn test_openssl_csr_signature_algorithm_curve_mismatch() { + use openssl::nid::Nid; + use rcgen::CertificateSigningRequestParams; + + let csr = CertificateSigningRequestParams::from_pem(CSR_TEST_MISMATCHED_CURVE_PEM).unwrap(); + + let (ca_params, ca_key, _) = util::default_params(); + let ca = Issuer::new(ca_params, ca_key); + let cert = csr.signed_by(&ca).unwrap(); + + let cert = X509::from_der(cert.der()).expect("failed to parse cert DER"); + let public_key = cert + .public_key() + .expect("openssl could not load the certificate's public key"); + assert_eq!( + public_key.ec_key().unwrap().group().curve_name(), + Some(Nid::X9_62_PRIME256V1) + ); +} + +/* +Generated by: openssl ecparam -name prime256v1 -genkey -noout -out ./tmp.key + openssl req -new -key ./tmp.key -sha384 -subj /CN=example.com +A P-256 key signed with ecdsa-with-SHA384. RFC 5480 ยง4 recommends pairing the hash with the +curve, but does not require it. + */ +const CSR_TEST_MISMATCHED_CURVE_PEM: &str = r#" +-----BEGIN CERTIFICATE REQUEST----- +MIH6MIGhAgEAMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAE6Wy5AqJM0KdIsDgAP6zen9Z0kYxeEHlRBs3wPDU11tVULVgW +rZcJTov0WfHDpkfK7y9e+ks2THTkl3BTbEAAj6ApMCcGCSqGSIb3DQEJDjEaMBgw +FgYDVR0RBA8wDYILZXhhbXBsZS5jb20wCgYIKoZIzj0EAwMDSAAwRQIgNtNWHO04 +J/j8QiTSHTuy42BCMWui/G3rTLv5vhMNH4ICIQC6zMMT2hw4RqAw9Re9rlS2qyVl +u3t5ErXRu09rSxEQrA== +-----END CERTIFICATE REQUEST----- +"#;