From 957b1be3ac9d50eb695566f4c7b6add12bd8fa0a Mon Sep 17 00:00:00 2001 From: jgreeer Date: Tue, 22 Sep 2026 21:15:37 +0000 Subject: [PATCH] add public key algorithm --- rcgen/src/certificate.rs | 2 +- rcgen/src/crl.rs | 5 +- rcgen/src/csr.rs | 29 ++--- rcgen/src/error.rs | 7 ++ rcgen/src/key_pair.rs | 63 +++++----- rcgen/src/lib.rs | 25 +++- rcgen/src/sign_algo.rs | 224 ++++++++++++++++++++++++++++------ rustls-cert-gen/src/cert.rs | 19 ++- verify-tests/tests/generic.rs | 2 +- verify-tests/tests/openssl.rs | 44 +++++++ verify-tests/tests/webpki.rs | 8 +- 11 files changed, 331 insertions(+), 97 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 71dc7843..316002a1 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -395,7 +395,7 @@ impl CertificateParams { // Write signature algorithm issuer .signing_key - .algorithm() + .signature_algorithm() .write_alg_ident(writer.next()); // Write issuer name write_distinguished_name(writer.next(), issuer.distinguished_name.as_ref()); diff --git a/rcgen/src/crl.rs b/rcgen/src/crl.rs index a75b468e..39cee8ab 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 signature_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 algorithm(&self) -> &'static PublicKeyAlgorithm { &key_alg::ED25519 } /// } /// # fn main () { /// // Generate a CRL issuer. @@ -203,7 +204,7 @@ impl CertificateRevocationListParams { // signatureAlgorithm field in the sequence CertificateList issuer .signing_key - .algorithm() + .signature_algorithm() .write_alg_ident(writer.next()); // Write issuer. diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index 33f1e8df..d8a014dc 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -7,7 +7,7 @@ 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, SigningKey, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, ExtendedKeyUsagePurpose, GeneralName, IsCa, KeyUsagePurpose}; @@ -16,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 } } @@ -31,7 +31,7 @@ impl PublicKeyData for PublicKey { &self.raw } - fn algorithm(&self) -> &'static SignatureAlgorithm { + fn algorithm(&self) -> &'static PublicKeyAlgorithm { self.alg } } @@ -116,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 { @@ -186,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/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 7e6a2853..842cc4ad 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 signature_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 algorithm(&self) -> &'static PublicKeyAlgorithm { + self.alg.public_key_algorithm() } } @@ -595,7 +594,7 @@ pub(crate) fn sign_der( writer.next().write_der(&data); // Write signatureAlgorithm - key.algorithm().write_alg_ident(writer.next()); + key.signature_algorithm().write_alg_ident(writer.next()); // Write signature let sig = key.sign(&data)?; @@ -611,12 +610,19 @@ impl SigningKey for &S { fn sign(&self, msg: &[u8]) -> Result, Error> { (*self).sign(msg) } + + fn signature_algorithm(&self) -> &'static SignatureAlgorithm { + (*self).signature_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 signature_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, } @@ -658,7 +664,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 +674,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: PublicKeyAlgorithm::from_alg_id(&spki.algorithm)?, subject_public_key: Vec::from(spki.subject_public_key.as_ref()), }) } @@ -695,7 +686,7 @@ impl PublicKeyData for SubjectPublicKeyInfo { &self.subject_public_key } - fn algorithm(&self) -> &'static SignatureAlgorithm { + fn algorithm(&self) -> &'static PublicKeyAlgorithm { self.alg } } @@ -705,7 +696,7 @@ impl PublicKeyData for &K { (*self).der_bytes() } - fn algorithm(&self) -> &'static SignatureAlgorithm { + fn algorithm(&self) -> &'static PublicKeyAlgorithm { (*self).algorithm() } } @@ -723,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 algorithm(&self) -> &'static PublicKeyAlgorithm; } /// Serialize private key to PEM format @@ -743,7 +734,7 @@ pub fn serialize_private_key_pem(key: &PrivateKeyDer<'_>) -> Result 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 afd1b701..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")] @@ -30,11 +31,178 @@ 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(any(feature = "x509-parser", 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() + } + + /// 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| { + 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 +217,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,11 +226,12 @@ 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 { + #[cfg(test)] pub(crate) fn iter() -> std::slice::Iter<'static, &'static SignatureAlgorithm> { use algo::*; static ALGORITHMS: &[&SignatureAlgorithm] = &[ @@ -88,26 +257,20 @@ 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 } } /// 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 @@ -118,7 +281,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 @@ -129,7 +292,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 @@ -140,7 +303,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 @@ -151,7 +314,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 @@ -167,7 +330,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 @@ -183,7 +346,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 @@ -197,7 +360,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 @@ -208,8 +371,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 @@ -221,7 +383,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, @@ -232,7 +394,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, @@ -243,7 +405,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, @@ -270,14 +432,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) { - 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); - }); - } } diff --git a/rustls-cert-gen/src/cert.rs b/rustls-cert-gen/src/cert.rs index 115e10c1..5c9a9717 100644 --- a/rustls-cert-gen/src/cert.rs +++ b/rustls-cert-gen/src/cert.rs @@ -480,19 +480,30 @@ 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"); + assert_eq!(format!("{:?}", keypair.signature_algorithm()), "ED25519"); let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::EcdsaP256.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ECDSA_P256_SHA256"); + assert_eq!( + format!("{:?}", keypair.signature_algorithm()), + "ECDSA_P256_SHA256" + ); let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::EcdsaP384.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ECDSA_P384_SHA384"); + assert_eq!( + format!("{:?}", keypair.signature_algorithm()), + "ECDSA_P384_SHA384" + ); #[cfg(feature = "aws_lc_rs")] { let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::EcdsaP521.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ECDSA_P521_SHA512"); + assert_eq!( + format!("{:?}", keypair.signature_algorithm()), + "ECDSA_P521_SHA512" + ); } Ok(()) diff --git a/verify-tests/tests/generic.rs b/verify-tests/tests/generic.rs index e4ae1fc9..b7b59c7a 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, PublicKeyData, }; #[test] 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----- +"#; diff --git a/verify-tests/tests/webpki.rs b/verify-tests/tests/webpki.rs index 4ccfadc7..efe75bf7 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 signature_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 algorithm(&self) -> &'static rcgen::PublicKeyAlgorithm { + &rcgen::key_alg::ECDSA_P256 } }