diff --git a/rcgen/src/crl.rs b/rcgen/src/crl.rs index df90cc44..4e852eff 100644 --- a/rcgen/src/crl.rs +++ b/rcgen/src/crl.rs @@ -26,11 +26,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 { &sig_alg::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 ad62f97a..990df28b 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -1,5 +1,3 @@ -use std::hash::Hash; - #[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateSigningRequestDer; @@ -9,34 +7,9 @@ use crate::ENCODE_CONFIG; #[cfg(feature = "x509-parser")] use crate::{ BasicConstraints, DistinguishedName, ExtendedKeyUsagePurpose, GeneralName, KeyUsagePurpose, + PublicKeyAlgorithm, SignatureAlgorithm, }; -use crate::{ - Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey, -}; - -/// A public key, extracted from a CSR -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct PublicKey { - raw: Vec, - alg: &'static SignatureAlgorithm, -} - -impl PublicKey { - /// The algorithm used to generate the public key and sign the CSR. - pub fn algorithm(&self) -> &SignatureAlgorithm { - self.alg - } -} - -impl PublicKeyData for PublicKey { - fn der_bytes(&self) -> &[u8] { - &self.raw - } - - fn algorithm(&self) -> &'static SignatureAlgorithm { - self.alg - } -} +use crate::{Certificate, CertificateParams, Error, Issuer, SigningKey, SubjectPublicKeyInfo}; /// A certificate signing request (CSR) that can be encoded to PEM or DER. /// @@ -79,7 +52,7 @@ pub struct CertificateSigningRequestParams { /// Parameters for the certificate to be signed. pub params: CertificateParams, /// Public key to include in the certificate signing request. - pub public_key: PublicKey, + pub public_key: SubjectPublicKeyInfo, } impl CertificateSigningRequestParams { @@ -116,22 +89,30 @@ impl CertificateSigningRequestParams { let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr) .map_err(|_| Error::CouldNotParseCertificationRequest)? .1; - csr.verify_signature() - .map_err(|_| Error::InvalidCertificationRequestSignature)?; - let alg_oid = csr + let sig_alg_oid = csr .signature_algorithm .algorithm .iter() .ok_or(Error::CouldNotParseCertificationRequest)? .collect::>(); - let alg = SignatureAlgorithm::from_oid(&alg_oid)?; + if !SignatureAlgorithm::supports_oid(&sig_alg_oid) { + return Err(Error::UnsupportedSignatureAlgorithm); + } + + csr.verify_signature() + .map_err(|_| Error::InvalidCertificationRequestSignature)?; let info = &csr.certification_request_info; + + let public_key = SubjectPublicKeyInfo { + alg: PublicKeyAlgorithm::from_alg_id(&info.subject_pki.algorithm)?, + subject_public_key: info.subject_pki.subject_public_key.data.to_vec(), + }; + 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 { @@ -189,10 +170,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 2949178b..2a5d9bf1 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 @@ -88,6 +90,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..f5b6b1e0 100644 --- a/rcgen/src/key_pair.rs +++ b/rcgen/src/key_pair.rs @@ -2,7 +2,7 @@ use std::fmt; #[cfg(feature = "aws_lc_rs")] -use aws_lc_rs::signature::PqdsaKeyPair; +use aws_lc_rs::signature::{PqdsaKeyPair, ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING}; #[cfg(feature = "pem")] use pem::Pem; #[cfg(any(feature = "crypto", feature = "pem"))] @@ -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::{algo::*, SignAlgo, SigningKeyAlgorithm}; +use crate::sign_algo::{PublicKeyAlgorithm, SignatureAlgorithm}; use crate::Error; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; @@ -61,12 +61,67 @@ impl fmt::Debug for KeyPairKind { } } +/// The backend primitive implementing `alg`, or an error if the backend doesn't support it +#[cfg(feature = "crypto")] +fn signing_alg(alg: &SigningKeyAlgorithm) -> Result { + static SUPPORTED: &[(&SigningKeyAlgorithm, SignAlgo)] = &[ + ( + &RSA_PKCS1_SHA256, + SignAlgo::Rsa(&signature::RSA_PKCS1_SHA256), + ), + ( + &RSA_PKCS1_SHA384, + SignAlgo::Rsa(&signature::RSA_PKCS1_SHA384), + ), + ( + &RSA_PKCS1_SHA512, + SignAlgo::Rsa(&signature::RSA_PKCS1_SHA512), + ), + ( + &ECDSA_P256_SHA256, + SignAlgo::EcDsa(&signature::ECDSA_P256_SHA256_ASN1_SIGNING), + ), + ( + &ECDSA_P384_SHA384, + SignAlgo::EcDsa(&signature::ECDSA_P384_SHA384_ASN1_SIGNING), + ), + #[cfg(feature = "aws_lc_rs")] + ( + &ECDSA_P521_SHA256, + SignAlgo::EcDsa(&signature::ECDSA_P521_SHA256_ASN1_SIGNING), + ), + #[cfg(feature = "aws_lc_rs")] + ( + &ECDSA_P521_SHA384, + SignAlgo::EcDsa(&signature::ECDSA_P521_SHA384_ASN1_SIGNING), + ), + #[cfg(feature = "aws_lc_rs")] + ( + &ECDSA_P521_SHA512, + SignAlgo::EcDsa(&signature::ECDSA_P521_SHA512_ASN1_SIGNING), + ), + (&ED25519, SignAlgo::EdDsa(&signature::ED25519)), + #[cfg(feature = "aws_lc_rs")] + (&ML_DSA_44, SignAlgo::PqDsa(&ML_DSA_44_SIGNING)), + #[cfg(feature = "aws_lc_rs")] + (&ML_DSA_65, SignAlgo::PqDsa(&ML_DSA_65_SIGNING)), + #[cfg(feature = "aws_lc_rs")] + (&ML_DSA_87, SignAlgo::PqDsa(&ML_DSA_87_SIGNING)), + ]; + + SUPPORTED + .iter() + .find(|(supported, _)| *supported == alg) + .map(|(_, sign_alg)| *sign_alg) + .ok_or(Error::UnsupportedSignatureAlgorithm) +} + /// A key pair used to sign certificates and CSRs #[cfg(feature = "crypto")] #[derive(Debug)] pub struct KeyPair { pub(crate) kind: KeyPairKind, - pub(crate) alg: &'static SignatureAlgorithm, + pub(crate) alg: &'static SigningKeyAlgorithm, } #[cfg(feature = "crypto")] @@ -77,19 +132,19 @@ impl KeyPair { Self::generate_for(&ECDSA_P256_SHA256) } - /// Generate a new random key pair for the specified signature algorithm + /// Generate a new random key pair for the specified signing key algorithm /// /// If you're not sure which algorithm to use, [`ECDSA_P256_SHA256`] is a good choice. - /// If passed an RSA signature algorithm, it depends on the backend whether we return + /// If passed an RSA algorithm, it depends on the backend whether we return /// a generated key or an error for key generation being unavailable. /// Currently, only `aws-lc-rs` supports RSA key generation. #[cfg(feature = "crypto")] pub fn generate_for( - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, ) -> Result<(Self, PrivateKeyDer<'static>), Error> { let rng = &SystemRandom::new(); - match alg.sign_alg { + match signing_alg(alg)? { SignAlgo::EcDsa(sign_alg) => { let key_pair_doc = EcdsaKeyPair::generate_pkcs8(sign_alg, rng)._err()?; Ok(( @@ -141,14 +196,14 @@ impl KeyPair { /// Generates a new random RSA key pair for the specified key size /// - /// If passed a signature algorithm that is not RSA, it will return + /// If passed an algorithm that is not RSA, it will return /// [`Error::KeyGenerationUnavailable`]. #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub fn generate_rsa_for( - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, key_size: RsaKeySize, ) -> Result<(Self, PrivateKeyDer<'static>), Error> { - match alg.sign_alg { + match signing_alg(alg)? { SignAlgo::Rsa(sign_alg) => { let key_size = match key_size { RsaKeySize::_2048 => KeySize::Rsa2048, @@ -163,7 +218,7 @@ impl KeyPair { #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] fn generate_rsa_inner( - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, sign_alg: &'static dyn RsaEncoding, key_size: KeySize, ) -> Result<(Self, PrivateKeyDer<'static>), Error> { @@ -181,11 +236,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; @@ -200,7 +250,7 @@ impl KeyPair { } /// Obtains the key pair from a DER formatted key - /// using the specified [`SignatureAlgorithm`] + /// using the specified [`SigningKeyAlgorithm`] /// /// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; /// @@ -209,19 +259,19 @@ impl KeyPair { #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pkcs8_pem_and_sign_algo( pem_str: &str, - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, ) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key_der: &[_] = private_key.contents(); Self::from_pkcs8_der_and_sign_algo(&PrivatePkcs8KeyDer::from(private_key_der), alg) } - /// Obtains the key pair from a DER formatted key using the specified [`SignatureAlgorithm`] + /// Obtains the key pair from a DER formatted key using the specified [`SigningKeyAlgorithm`] /// /// If you have a [`PrivatePkcs8KeyDer`], you can usually rely on the [`TryFrom`] implementation - /// to obtain a [`KeyPair`] -- it will determine the correct [`SignatureAlgorithm`] for you. - /// However, sometimes multiple signature algorithms fit for the same DER key. In those instances, - /// you can use this function to precisely specify the `SignatureAlgorithm`. + /// to obtain a [`KeyPair`] -- it will determine the correct [`SigningKeyAlgorithm`] for you. + /// However, sometimes multiple signing key algorithms fit for the same DER key. In those instances, + /// you can use this function to precisely specify the `SigningKeyAlgorithm`. /// /// [`rustls_pemfile::private_key()`] is often used to obtain a [`PrivateKeyDer`] from PEM /// input. If the obtained [`PrivateKeyDer`] is a `Pkcs8` variant, you can use its contents @@ -233,67 +283,30 @@ impl KeyPair { #[cfg(feature = "crypto")] pub fn from_pkcs8_der_and_sign_algo( pkcs8: &PrivatePkcs8KeyDer<'_>, - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, ) -> Result { let rng = &SystemRandom::new(); let serialized_der = pkcs8.secret_pkcs8_der().to_vec(); - let kind = if alg == &ED25519 { - KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) - } else if alg == &ECDSA_P256_SHA256 { - KeyPairKind::Ec(ecdsa_from_pkcs8( - &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - &serialized_der, - rng, - )?) - } else if alg == &ECDSA_P384_SHA384 { - KeyPairKind::Ec(ecdsa_from_pkcs8( - &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - &serialized_der, - rng, - )?) - } else if alg == &RSA_PKCS1_SHA256 { - let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) - } else if alg == &RSA_PKCS1_SHA384 { - let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) - } else if alg == &RSA_PKCS1_SHA512 { - let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) - } else { + let kind = match signing_alg(alg)? { + SignAlgo::EdDsa(_) => { + KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) + }, + SignAlgo::EcDsa(sign_alg) => { + KeyPairKind::Ec(ecdsa_from_pkcs8(sign_alg, &serialized_der, rng)?) + }, + SignAlgo::Rsa(sign_alg) => { + KeyPairKind::Rsa(RsaKeyPair::from_pkcs8(&serialized_der)._err()?, sign_alg) + }, #[cfg(feature = "aws_lc_rs")] - if alg == &ECDSA_P521_SHA256 { - KeyPairKind::Ec(ecdsa_from_pkcs8( - &signature::ECDSA_P521_SHA256_ASN1_SIGNING, - &serialized_der, - rng, - )?) - } else if alg == &ECDSA_P521_SHA384 { - KeyPairKind::Ec(ecdsa_from_pkcs8( - &signature::ECDSA_P521_SHA384_ASN1_SIGNING, - &serialized_der, - rng, - )?) - } else if alg == &ECDSA_P521_SHA512 { - KeyPairKind::Ec(ecdsa_from_pkcs8( - &signature::ECDSA_P521_SHA512_ASN1_SIGNING, - &serialized_der, - rng, - )?) - } else { - panic!("Unknown SignatureAlgorithm specified!"); - } - - #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] - panic!("Unknown SignatureAlgorithm specified!"); + SignAlgo::PqDsa(_) => return Err(Error::UnsupportedSignatureAlgorithm), }; Ok(KeyPair { kind, alg }) } /// Obtains the key pair from a PEM formatted key - /// using the specified [`SignatureAlgorithm`] + /// using the specified [`SigningKeyAlgorithm`] /// /// 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; /// Appears as "PRIVATE KEY", "RSA PRIVATE KEY", or "EC PRIVATE KEY" in PEM files. @@ -305,7 +318,7 @@ impl KeyPair { #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pem_and_sign_algo( pem_str: &str, - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, ) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key: &[_] = private_key.contents(); @@ -316,15 +329,15 @@ impl KeyPair { } /// Obtains the key pair from a DER formatted key - /// using the specified [`SignatureAlgorithm`] + /// using the specified [`SigningKeyAlgorithm`] /// /// Note that using the `ring` feature, this function only support [`PrivateKeyDer::Pkcs8`] variant. /// Consider using the `aws_lc_rs` features to support [`PrivateKeyDer`] fully. /// /// If you have a [`PrivateKeyDer`], you can usually rely on the [`TryFrom`] implementation - /// to obtain a [`KeyPair`] -- it will determine the correct [`SignatureAlgorithm`] for you. - /// However, sometimes multiple signature algorithms fit for the same DER key. In those instances, - /// you can use this function to precisely specify the `SignatureAlgorithm`. + /// to obtain a [`KeyPair`] -- it will determine the correct [`SigningKeyAlgorithm`] for you. + /// However, sometimes multiple signing key algorithms fit for the same DER key. In those instances, + /// you can use this function to precisely specify the `SigningKeyAlgorithm`. /// /// You can use [`rustls_pemfile::private_key`] to get the `key` input. If /// you have already a byte slice, just calling `try_into()` will convert it to a [`PrivateKeyDer`]. @@ -333,7 +346,7 @@ impl KeyPair { #[cfg(feature = "crypto")] pub fn from_der_and_sign_algo( key: &PrivateKeyDer<'_>, - alg: &'static SignatureAlgorithm, + alg: &'static SigningKeyAlgorithm, ) -> Result { #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] { @@ -355,34 +368,17 @@ impl KeyPair { let serialized_der = key.secret_der().to_vec(); - let kind = if alg == &ED25519 { - KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) - } else if alg == &ECDSA_P256_SHA256 { - KeyPairKind::Ec(ecdsa_from_private_key_der( - &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - &serialized_der, - )?) - } else if alg == &ECDSA_P384_SHA384 { - KeyPairKind::Ec(ecdsa_from_private_key_der( - &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - &serialized_der, - )?) - } else if alg == &ECDSA_P521_SHA512 { - KeyPairKind::Ec(ecdsa_from_private_key_der( - &signature::ECDSA_P521_SHA512_ASN1_SIGNING, - &serialized_der, - )?) - } else if alg == &RSA_PKCS1_SHA256 { - let rsakp = rsa_key_pair_from(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) - } else if alg == &RSA_PKCS1_SHA384 { - let rsakp = rsa_key_pair_from(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) - } else if alg == &RSA_PKCS1_SHA512 { - let rsakp = rsa_key_pair_from(&serialized_der)._err()?; - KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) - } else { - panic!("Unknown SignatureAlgorithm specified!"); + let kind = match signing_alg(alg)? { + SignAlgo::EdDsa(_) => KeyPairKind::Ed( + Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?, + ), + SignAlgo::EcDsa(sign_alg) => { + KeyPairKind::Ec(ecdsa_from_private_key_der(sign_alg, &serialized_der)?) + }, + SignAlgo::Rsa(sign_alg) => { + KeyPairKind::Rsa(rsa_key_pair_from(&serialized_der)._err()?, sign_alg) + }, + SignAlgo::PqDsa(_) => return Err(Error::UnsupportedSignatureAlgorithm), }; Ok(KeyPair { kind, alg }) @@ -401,14 +397,14 @@ impl KeyPair { self.der_bytes() } - /// Check if this key pair can be used with the given signature algorithm - pub fn is_compatible(&self, signature_algorithm: &SignatureAlgorithm) -> bool { - self.alg == signature_algorithm + /// Check if this key pair can be used with the given signing key algorithm + pub fn is_compatible(&self, alg: &SigningKeyAlgorithm) -> bool { + self.alg == alg } - /// Returns (possibly multiple) compatible [`SignatureAlgorithm`]'s + /// Returns (possibly multiple) compatible [`SigningKeyAlgorithm`]'s /// that the key can be used with - pub fn compatible_algs(&self) -> impl Iterator { + pub fn compatible_algs(&self) -> impl Iterator { std::iter::once(self.alg) } @@ -448,6 +444,10 @@ impl SigningKey for KeyPair { }, }) } + + fn algorithm(&self) -> &'static SignatureAlgorithm { + self.alg.signature_algorithm() + } } #[cfg(feature = "crypto")] @@ -462,8 +462,8 @@ impl PublicKeyData for KeyPair { } } - fn algorithm(&self) -> &'static SignatureAlgorithm { - self.alg + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + self.alg.key_algorithm() } } @@ -611,12 +611,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 +650,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 +665,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 +675,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 +687,7 @@ impl PublicKeyData for SubjectPublicKeyInfo { &self.subject_public_key } - fn algorithm(&self) -> &'static SignatureAlgorithm { + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { self.alg } } @@ -705,8 +697,8 @@ impl PublicKeyData for &K { (*self).der_bytes() } - fn algorithm(&self) -> &'static SignatureAlgorithm { - (*self).algorithm() + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + (*self).key_algorithm() } } @@ -723,8 +715,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 @@ -743,7 +735,7 @@ pub fn serialize_private_key_pem(key: &PrivateKeyDer<'_>) -> Result for SerialNumber { #[cfg(test)] mod tests { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; use std::panic::catch_unwind; use time::{Date, Month, PrimitiveDateTime}; @@ -649,4 +653,29 @@ mod tests { } } } + + #[test] + fn algo_hash_agrees_with_eq() { + 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)); + } + } + for alg_i in SigningKeyAlgorithm::iter() { + for alg_j in SigningKeyAlgorithm::iter() { + assert!(alg_i != alg_j || hash(alg_i) == hash(alg_j)); + } + } + } } diff --git a/rcgen/src/oid.rs b/rcgen/src/oid.rs index d61e858f..ba7a8a0e 100644 --- a/rcgen/src/oid.rs +++ b/rcgen/src/oid.rs @@ -21,15 +21,10 @@ pub(crate) const EC_SECP_256_R1: &[u64] = &[1, 2, 840, 10045, 3, 1, 7]; /// secp384r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub(crate) const EC_SECP_384_R1: &[u64] = &[1, 3, 132, 0, 34]; /// secp521r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) -/// Currently this is only supported with the `aws_lc_rs` feature -#[cfg(feature = "aws_lc_rs")] pub(crate) const EC_SECP_521_R1: &[u64] = &[1, 3, 132, 0, 35]; -#[cfg(feature = "aws_lc_rs")] pub(crate) const ML_DSA_44: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; -#[cfg(feature = "aws_lc_rs")] pub(crate) const ML_DSA_65: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; -#[cfg(feature = "aws_lc_rs")] pub(crate) const ML_DSA_87: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; /// rsaEncryption in [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055#section-6) diff --git a/rcgen/src/sign_algo.rs b/rcgen/src/sign_algo.rs index afd1b701..aead334b 100644 --- a/rcgen/src/sign_algo.rs +++ b/rcgen/src/sign_algo.rs @@ -2,14 +2,13 @@ use std::fmt; use std::hash::{Hash, Hasher}; #[cfg(feature = "aws_lc_rs")] -use aws_lc_rs::signature::{ - PqdsaSigningAlgorithm, ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING, -}; +use aws_lc_rs::signature::PqdsaSigningAlgorithm; use yasna::models::ObjectIdentifier; use yasna::DERWriter; #[cfg(feature = "crypto")] -use crate::ring_like::signature::{self, EcdsaSigningAlgorithm, EdDSAParameters, RsaEncoding}; +use crate::ring_like::signature::{EcdsaSigningAlgorithm, EdDSAParameters, RsaEncoding}; +#[cfg(feature = "x509-parser")] use crate::Error; #[cfg(feature = "crypto")] @@ -30,13 +29,177 @@ pub(crate) enum SignatureAlgorithmParams { Null, } +/// The parameters of a public key's `AlgorithmIdentifier` +#[derive(Clone, Copy, PartialEq, 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 { + /// 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) + } + + #[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, + &ECDSA_P521, + &ED25519, + &ML_DSA_44, + &ML_DSA_65, + &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 { + f.debug_struct("PublicKeyAlgorithm") + .field("name", &self.name) + .finish_non_exhaustive() + } +} + +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) + /// + /// Signing with such a key is only supported with the `aws_lc_rs` backend. + 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) + /// + /// Signing with such a key is only supported with the `aws_lc_rs` backend. + 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) + /// + /// Signing with such a key is only supported with the `aws_lc_rs` backend. + 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) + /// + /// Signing with such a key is only supported with the `aws_lc_rs` backend. + 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]], - #[cfg(feature = "crypto")] - pub(crate) sign_alg: SignAlgo, oid_components: &'static [u64], params: SignatureAlgorithmParams, } @@ -49,7 +212,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.oid_components == other.oid_components } } @@ -58,58 +221,43 @@ 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.oid_components.hash(state); } } impl SignatureAlgorithm { + #[cfg(any(feature = "x509-parser", test))] pub(crate) fn iter() -> std::slice::Iter<'static, &'static SignatureAlgorithm> { - use algo::*; + use sig_alg::*; static ALGORITHMS: &[&SignatureAlgorithm] = &[ &RSA_PKCS1_SHA256, &RSA_PKCS1_SHA384, &RSA_PKCS1_SHA512, - &ECDSA_P256_SHA256, - &ECDSA_P384_SHA384, - #[cfg(feature = "aws_lc_rs")] - &ECDSA_P521_SHA256, - #[cfg(feature = "aws_lc_rs")] - &ECDSA_P521_SHA384, - #[cfg(feature = "aws_lc_rs")] - &ECDSA_P521_SHA512, + &ECDSA_SHA256, + &ECDSA_SHA384, + &ECDSA_SHA512, &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 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) + /// Whether any supported signature algorithm is identified by `oid` + #[cfg(feature = "x509-parser")] + pub(crate) fn supports_oid(oid: &[u64]) -> bool { + Self::iter().any(|alg| alg.oid_components == oid) } } /// The list of supported signature algorithms -pub(crate) mod algo { - use super::*; - use crate::oid::*; +pub mod sig_alg { + use super::{SignatureAlgorithm, SignatureAlgorithmParams}; /// 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], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA256), // sha256WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 11], params: SignatureAlgorithmParams::Null, @@ -118,9 +266,6 @@ 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], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA384), // sha384WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 12], params: SignatureAlgorithmParams::Null, @@ -129,50 +274,176 @@ 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], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA512), // sha512WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 13], params: SignatureAlgorithmParams::Null, }; - /// 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], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P256_SHA256_ASN1_SIGNING), + /// ECDSA signing with SHA-256 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) + pub static ECDSA_SHA256: SignatureAlgorithm = SignatureAlgorithm { + name: "ECDSA_SHA256", // ecdsa-with-SHA256 in RFC 5758 oid_components: &[1, 2, 840, 10045, 4, 3, 2], params: SignatureAlgorithmParams::None, }; - /// 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], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P384_SHA384_ASN1_SIGNING), + /// ECDSA signing with SHA-384 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) + pub static ECDSA_SHA384: SignatureAlgorithm = SignatureAlgorithm { + name: "ECDSA_SHA384", // ecdsa-with-SHA384 in RFC 5758 oid_components: &[1, 2, 840, 10045, 4, 3, 3], params: SignatureAlgorithmParams::None, }; + /// ECDSA signing with SHA-512 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) + pub static ECDSA_SHA512: SignatureAlgorithm = SignatureAlgorithm { + name: "ECDSA_SHA512", + // ecdsa-with-SHA512 in RFC 5758 + oid_components: &[1, 2, 840, 10045, 4, 3, 4], + params: SignatureAlgorithmParams::None, + }; + + /// 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 + oid_components: &[1, 3, 101, 112], + params: SignatureAlgorithmParams::None, + }; + + /// ML-DSA-44 signing as per . + pub static ML_DSA_44: SignatureAlgorithm = SignatureAlgorithm { + name: "ML_DSA_44", + oid_components: crate::oid::ML_DSA_44, + params: SignatureAlgorithmParams::None, + }; + + /// ML-DSA-65 signing as per . + pub static ML_DSA_65: SignatureAlgorithm = SignatureAlgorithm { + name: "ML_DSA_65", + oid_components: crate::oid::ML_DSA_65, + params: SignatureAlgorithmParams::None, + }; + + /// ML-DSA-87 signing as per . + pub static ML_DSA_87: SignatureAlgorithm = SignatureAlgorithm { + name: "ML_DSA_87", + oid_components: crate::oid::ML_DSA_87, + params: SignatureAlgorithmParams::None, + }; +} + +/// A public key algorithm paired with the signature algorithm a key of that type signs with +/// +/// Keys are generated and loaded for one of these, and keep signing with its signature algorithm. +#[derive(Clone)] +pub struct SigningKeyAlgorithm { + name: &'static str, + key: &'static PublicKeyAlgorithm, + signature: &'static SignatureAlgorithm, +} + +impl SigningKeyAlgorithm { + #[cfg(test)] + pub(crate) fn iter() -> std::slice::Iter<'static, &'static SigningKeyAlgorithm> { + use algo::*; + static ALGORITHMS: &[&SigningKeyAlgorithm] = &[ + &RSA_PKCS1_SHA256, + &RSA_PKCS1_SHA384, + &RSA_PKCS1_SHA512, + &ECDSA_P256_SHA256, + &ECDSA_P384_SHA384, + &ECDSA_P521_SHA256, + &ECDSA_P521_SHA384, + &ECDSA_P521_SHA512, + &ED25519, + &ML_DSA_44, + &ML_DSA_65, + &ML_DSA_87, + ]; + ALGORITHMS.iter() + } + + /// The algorithm of the key + pub fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + self.key + } + + /// The algorithm of the signatures the key produces + pub fn signature_algorithm(&self) -> &'static SignatureAlgorithm { + self.signature + } +} + +impl fmt::Debug for SigningKeyAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl PartialEq for SigningKeyAlgorithm { + fn eq(&self, other: &Self) -> bool { + (self.key, self.signature) == (other.key, other.signature) + } +} + +impl Eq for SigningKeyAlgorithm {} + +/// The `Hash` trait is not derived, but implemented according to impl of the `PartialEq` trait +impl Hash for SigningKeyAlgorithm { + fn hash(&self, state: &mut H) { + (self.key, self.signature).hash(state); + } +} + +/// The list of supported signing key algorithms +pub(crate) mod algo { + use super::*; + + /// 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: SigningKeyAlgorithm = SigningKeyAlgorithm { + name: "RSA_PKCS1_SHA256", + key: &key_alg::RSA, + signature: &sig_alg::RSA_PKCS1_SHA256, + }; + + /// 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: SigningKeyAlgorithm = SigningKeyAlgorithm { + name: "RSA_PKCS1_SHA384", + key: &key_alg::RSA, + signature: &sig_alg::RSA_PKCS1_SHA384, + }; + + /// 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: SigningKeyAlgorithm = SigningKeyAlgorithm { + name: "RSA_PKCS1_SHA512", + key: &key_alg::RSA, + signature: &sig_alg::RSA_PKCS1_SHA512, + }; + + /// 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: SigningKeyAlgorithm = SigningKeyAlgorithm { + name: "ECDSA_P256_SHA256", + key: &key_alg::ECDSA_P256, + signature: &sig_alg::ECDSA_SHA256, + }; + + /// 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: SigningKeyAlgorithm = SigningKeyAlgorithm { + name: "ECDSA_P384_SHA384", + key: &key_alg::ECDSA_P384, + signature: &sig_alg::ECDSA_SHA384, + }; + /// ECDSA signing using the P-521 curves and SHA-256 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) /// /// Note that this algorithm is not widely supported, and is not supported in TLS 1.3. /// /// Only supported with the `aws_lc_rs` backend. - #[cfg(feature = "aws_lc_rs")] - pub static ECDSA_P521_SHA256: SignatureAlgorithm = SignatureAlgorithm { + pub static ECDSA_P521_SHA256: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ECDSA_P521_SHA256", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA256_ASN1_SIGNING), - // ecdsa-with-SHA256 in RFC 5758 - oid_components: &[1, 2, 840, 10045, 4, 3, 2], - params: SignatureAlgorithmParams::None, + key: &key_alg::ECDSA_P521, + signature: &sig_alg::ECDSA_SHA256, }; /// ECDSA signing using the P-521 curves and SHA-384 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) @@ -180,74 +451,53 @@ pub(crate) mod algo { /// Note that this algorithm is not widely supported, and is not supported in TLS 1.3. /// /// Only supported with the `aws_lc_rs` backend. - #[cfg(feature = "aws_lc_rs")] - pub static ECDSA_P521_SHA384: SignatureAlgorithm = SignatureAlgorithm { + pub static ECDSA_P521_SHA384: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ECDSA_P521_SHA384", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA384_ASN1_SIGNING), - // ecdsa-with-SHA384 in RFC 5758 - oid_components: &[1, 2, 840, 10045, 4, 3, 3], - params: SignatureAlgorithmParams::None, + key: &key_alg::ECDSA_P521, + signature: &sig_alg::ECDSA_SHA384, }; /// ECDSA signing using the P-521 curves and SHA-512 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) /// /// Only supported with the `aws_lc_rs` backend. - #[cfg(feature = "aws_lc_rs")] - pub static ECDSA_P521_SHA512: SignatureAlgorithm = SignatureAlgorithm { + pub static ECDSA_P521_SHA512: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ECDSA_P521_SHA512", - oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA512_ASN1_SIGNING), - // ecdsa-with-SHA512 in RFC 5758 - oid_components: &[1, 2, 840, 10045, 4, 3, 4], - params: SignatureAlgorithmParams::None, + key: &key_alg::ECDSA_P521, + signature: &sig_alg::ECDSA_SHA512, }; /// ED25519 curve signing as per [RFC 8410](https://tools.ietf.org/html/rfc8410) - pub static ED25519: SignatureAlgorithm = SignatureAlgorithm { + pub static ED25519: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ED25519", - // id-Ed25519 in RFC 8410 - oids_sign_alg: &[&[1, 3, 101, 112]], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::EdDsa(&signature::ED25519), - // id-Ed25519 in RFC 8410 - oid_components: &[1, 3, 101, 112], - params: SignatureAlgorithmParams::None, + key: &key_alg::ED25519, + signature: &sig_alg::ED25519, }; /// ML-DSA-44 signing as per . - #[cfg(feature = "aws_lc_rs")] - pub static ML_DSA_44: SignatureAlgorithm = SignatureAlgorithm { + /// + /// Only supported with the `aws_lc_rs` backend. + pub static ML_DSA_44: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ML_DSA_44", - oids_sign_alg: &[crate::oid::ML_DSA_44], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::PqDsa(&ML_DSA_44_SIGNING), - oid_components: crate::oid::ML_DSA_44, - params: SignatureAlgorithmParams::None, + key: &key_alg::ML_DSA_44, + signature: &sig_alg::ML_DSA_44, }; /// ML-DSA-65 signing as per . - #[cfg(feature = "aws_lc_rs")] - pub static ML_DSA_65: SignatureAlgorithm = SignatureAlgorithm { + /// + /// Only supported with the `aws_lc_rs` backend. + pub static ML_DSA_65: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ML_DSA_65", - oids_sign_alg: &[crate::oid::ML_DSA_65], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::PqDsa(&ML_DSA_65_SIGNING), - oid_components: crate::oid::ML_DSA_65, - params: SignatureAlgorithmParams::None, + key: &key_alg::ML_DSA_65, + signature: &sig_alg::ML_DSA_65, }; /// ML-DSA-87 signing as per . - #[cfg(feature = "aws_lc_rs")] - pub static ML_DSA_87: SignatureAlgorithm = SignatureAlgorithm { + /// + /// Only supported with the `aws_lc_rs` backend. + pub static ML_DSA_87: SigningKeyAlgorithm = SigningKeyAlgorithm { name: "ML_DSA_87", - oids_sign_alg: &[crate::oid::ML_DSA_87], - #[cfg(feature = "crypto")] - sign_alg: SignAlgo::PqDsa(&ML_DSA_87_SIGNING), - oid_components: crate::oid::ML_DSA_87, - params: SignatureAlgorithmParams::None, + key: &key_alg::ML_DSA_87, + signature: &sig_alg::ML_DSA_87, }; } // Signature algorithm IDs as per https://tools.ietf.org/html/rfc4055 @@ -270,14 +520,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 1161eacf..3d86b687 100644 --- a/rustls-cert-gen/src/cert.rs +++ b/rustls-cert-gen/src/cert.rs @@ -9,7 +9,7 @@ use rcgen::DnValue::PrintableString; use rcgen::{ serialize_private_key_pem, BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType, Error, ExtendedKeyUsagePurpose, GeneralName, KeyPair, - KeyUsagePurpose, PathLenConstraint, SignatureAlgorithm, + KeyUsagePurpose, PathLenConstraint, SigningKeyAlgorithm, }; /// Builder to configure TLS [CertificateParams] to be finalized @@ -210,7 +210,7 @@ pub enum KeyPairAlgorithm { MlDsa87, } -impl From for &'static SignatureAlgorithm { +impl From for &'static SigningKeyAlgorithm { fn from(alg: KeyPairAlgorithm) -> Self { match alg { KeyPairAlgorithm::Rsa => &rcgen::RSA_PKCS1_SHA256, @@ -484,18 +484,18 @@ mod tests { #[test] fn key_pair_algorithm_to_keypair() -> anyhow::Result<()> { let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::Ed25519.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ED25519"); + assert!(keypair.is_compatible(&rcgen::ED25519)); let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::EcdsaP256.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ECDSA_P256_SHA256"); + assert!(keypair.is_compatible(&rcgen::ECDSA_P256_SHA256)); let (keypair, _) = KeyPair::generate_for(KeyPairAlgorithm::EcdsaP384.into())?; - assert_eq!(format!("{:?}", keypair.algorithm()), "ECDSA_P384_SHA384"); + assert!(keypair.is_compatible(&rcgen::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!(keypair.is_compatible(&rcgen::ECDSA_P521_SHA512)); } Ok(()) diff --git a/verify-tests/tests/generic.rs b/verify-tests/tests/generic.rs index a821219e..3986d408 100644 --- a/verify-tests/tests/generic.rs +++ b/verify-tests/tests/generic.rs @@ -170,6 +170,72 @@ mod test_csr_custom_attributes { } } +#[cfg(feature = "x509-parser")] +mod test_csr_signature_algorithm { + use rcgen::{CertificateSigningRequestParams, Error}; + + /// Tests that a CSR signed with an algorithm rcgen does not support is rejected. + /// + /// The subject public key is read from the request's `subjectPublicKeyInfo`, which says + /// nothing about how the request was signed, so the signature algorithm needs checking + /// separately. + #[test] + fn test_csr_unsupported_signature_algorithm() { + for pem in [CSR_TEST_SHA1_PEM, CSR_TEST_RSA_PSS_PEM] { + assert!(matches!( + CertificateSigningRequestParams::from_pem(pem), + Err(Error::UnsupportedSignatureAlgorithm) + )); + } + } + + /* + Generated by: openssl req -new -key ./tmp.key -sha1 -subj "/CN=test.local" + Where `verify_tests::RSA_TEST_KEY_PAIR_PEM`'s content is stored in ./tmp.key + */ + const CSR_TEST_SHA1_PEM: &str = r#" +-----BEGIN CERTIFICATE REQUEST----- +MIICWjCCAUICAQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANiOaDJXf8tKLMletisBNmus+vYR2jzKI6AEsWaL +iyB4Z1DmfqeGNMZ8EQg/YhrrP6FuIY4ydxpsQFzOR0wT4kVhUJKRLuviK8w7OnZ6 +DEuBw8px6sGhcjwNRseEhH3Pz2UeI8cMm/f53QBzVv1vP1vw4B5laJCYW+aOltzY +N+FDY4XHYzAVkJgXX3qutc5zr9JHZ6xlVGuRbbZEEoVODPoYgDkD/lVYoghJKoQH +WA5wzPaKrn3zsjbz4TPitwtnaUHtxntNs3GQDC3R88v4S7I/tc7NsiPj+RICVTnF +/A0RFrcES44WujiLkSZIOP6VHnF1GkWfHSPnM6jNQvaUcb0CAwEAAaAAMA0GCSqG +SIb3DQEBBQUAA4IBAQDSJ2gLNFOPWnV3BwME4Y5abQK7+TvWbtttRBFMeX5xOBLB +nwWCQJ/gpaYENBciSfI3Z3i7/eGgj29EtnrzkOmgt5R3Db/TZINSe5lONAnetVV/ +lRsErhp+x8UP4f9Rl4g4E9bGRj+O2FVACodNGP0Rcn5XOaPkcOTPdyHFES0M3o1r +dsXijMtj0MZXW538A0YkNQAWJbBQrou59ftpJbs04sapqSLLR8ry3jNk0EIuH1Q0 +8Q1DzffvheEwZHTdBaMJnJTO2EE9IDbZu8YVh/K6SkAU/cQi/8VBwgKkNehabc2P +fpln/4yqQowGGU239OYyZJHpZrzXVzXzVIFYxDTv +-----END CERTIFICATE REQUEST----- +"#; + + /* + Generated by: openssl req -new -key ./tmp.key -sha256 \ + -sigopt rsa_padding_mode:pss -subj "/CN=test.local" + Where `verify_tests::RSA_TEST_KEY_PAIR_PEM`'s content is stored in ./tmp.key + */ + const CSR_TEST_RSA_PSS_PEM: &str = r#" +-----BEGIN CERTIFICATE REQUEST----- +MIICjzCCAUICAQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANiOaDJXf8tKLMletisBNmus+vYR2jzKI6AEsWaL +iyB4Z1DmfqeGNMZ8EQg/YhrrP6FuIY4ydxpsQFzOR0wT4kVhUJKRLuviK8w7OnZ6 +DEuBw8px6sGhcjwNRseEhH3Pz2UeI8cMm/f53QBzVv1vP1vw4B5laJCYW+aOltzY +N+FDY4XHYzAVkJgXX3qutc5zr9JHZ6xlVGuRbbZEEoVODPoYgDkD/lVYoghJKoQH +WA5wzPaKrn3zsjbz4TPitwtnaUHtxntNs3GQDC3R88v4S7I/tc7NsiPj+RICVTnF +/A0RFrcES44WujiLkSZIOP6VHnF1GkWfHSPnM6jNQvaUcb0CAwEAAaAAMEIGCSqG +SIb3DQEBCjA1oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI +AWUDBAIBBQCiBAICAN4DggEBAHgFHc6e6lYvH1rb2cIiJ3jVKGL3DfdPoFiVNWwr +IPn/trqbZCtzP1J5JcOjftoCKzSVS4z32xpQvc+l32AhX2JM36hVK8Sb2hciN+cZ +gZpYYxhAvgGli7QMaWeOH1ykdn7TBObf+ex2oxZaWUpfUbHF4l9Xw/v/V4VPkCAe +cqjfcN2llOnyQ+dufIZ+4IWNnuCNtz4DuW9cCHJkzgeX8cNXi+M9Mga7hr8aYLFh +AMPtpabiKra+E5iOknJHe1GPSL9DJQAzTUxlpOx5E5zc6DXZ+ExcyJ6DEK1X1fRP +C/78yir2lL9me+9fgvh7GjpebVyz9bDydTa/NZ6Pk8o7cQs= +-----END CERTIFICATE REQUEST----- +"#; +} + #[cfg(feature = "x509-parser")] mod test_csr_basic_constraints { use rcgen::{BasicConstraints, CertificateSigningRequestParams, Error, PathLenConstraint}; @@ -522,7 +588,7 @@ mod test_csr_extension_request { mod test_csr { use rcgen::{ CertificateParams, CertificateSigningRequestParams, ExtendedKeyUsagePurpose, KeyPair, - KeyUsagePurpose, + KeyUsagePurpose, PublicKeyData, }; #[test] @@ -578,7 +644,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.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 db03541a..84880525 100644 --- a/verify-tests/tests/openssl.rs +++ b/verify-tests/tests/openssl.rs @@ -563,3 +563,115 @@ 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 does not imply the key's curve. +/// +/// `ecdsa-with-SHA384` names a hash and no curve, so a P-256 key may be used with it. +/// Inferring 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_spki_not_inferred_from_signature_algorithm() { + use openssl::nid::Nid; + use rcgen::CertificateSigningRequestParams; + + let csr = CertificateSigningRequestParams::from_pem(CSR_TEST_UNMATCHED_HASH_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_UNMATCHED_HASH_PEM: &str = r#" +-----BEGIN CERTIFICATE REQUEST----- +MIH6MIGhAgEAMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAE6Wy5AqJM0KdIsDgAP6zen9Z0kYxeEHlRBs3wPDU11tVULVgW +rZcJTov0WfHDpkfK7y9e+ks2THTkl3BTbEAAj6ApMCcGCSqGSIb3DQEJDjEaMBgw +FgYDVR0RBA8wDYILZXhhbXBsZS5jb20wCgYIKoZIzj0EAwMDSAAwRQIgNtNWHO04 +J/j8QiTSHTuy42BCMWui/G3rTLv5vhMNH4ICIQC6zMMT2hw4RqAw9Re9rlS2qyVl +u3t5ErXRu09rSxEQrA== +-----END CERTIFICATE REQUEST----- +"#; + +/// A P-256 key may sign with `ecdsa-with-SHA384`, which names no curve. +#[test] +fn test_openssl_p256_key_signing_with_sha384() { + use openssl::bn::BigNumContext; + use openssl::ec::{EcGroup, EcKey, PointConversionForm}; + use openssl::hash::MessageDigest; + use openssl::nid::Nid; + use openssl::pkey::Private; + use openssl::sign::Signer; + use rcgen::{ + key_alg, sig_alg, PublicKeyAlgorithm, PublicKeyData, SignatureAlgorithm, SigningKey, + }; + + struct P256WithSha384 { + key: PKey, + public_key: Vec, + } + + impl PublicKeyData for P256WithSha384 { + fn der_bytes(&self) -> &[u8] { + &self.public_key + } + + fn key_algorithm(&self) -> &'static PublicKeyAlgorithm { + &key_alg::ECDSA_P256 + } + } + + impl SigningKey for P256WithSha384 { + fn sign(&self, msg: &[u8]) -> Result, rcgen::Error> { + let mut signer = Signer::new(MessageDigest::sha384(), &self.key).unwrap(); + signer.update(msg).unwrap(); + Ok(signer.sign_to_vec().unwrap()) + } + + fn algorithm(&self) -> &'static SignatureAlgorithm { + &sig_alg::ECDSA_SHA384 + } + } + + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let mut ctx = BigNumContext::new().unwrap(); + let public_key = ec_key + .public_key() + .to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx) + .unwrap(); + let key = P256WithSha384 { + key: PKey::from_ec_key(ec_key).unwrap(), + public_key, + }; + + let cert = CertificateParams::default().self_signed(&key).unwrap(); + + let cert = X509::from_der(cert.der()).unwrap(); + let public_key = cert.public_key().unwrap(); + assert_eq!( + public_key.ec_key().unwrap().group().curve_name(), + Some(Nid::X9_62_PRIME256V1) + ); + assert_eq!( + cert.signature_algorithm().object().nid(), + Nid::ECDSA_WITH_SHA384 + ); + assert!(cert.verify(&public_key).unwrap()); +} diff --git a/verify-tests/tests/webpki.rs b/verify-tests/tests/webpki.rs index 03a9c942..198e787a 100644 --- a/verify-tests/tests/webpki.rs +++ b/verify-tests/tests/webpki.rs @@ -270,7 +270,7 @@ fn test_webpki_ml_dsa() { #[cfg(feature = "aws_lc_rs")] const ML_DSA_ALGS: &[( - &rcgen::SignatureAlgorithm, + &rcgen::SigningKeyAlgorithm, &dyn SignatureVerificationAlgorithm, &PqdsaSigningAlgorithm, )] = &[ @@ -394,6 +394,10 @@ fn from_remote() { .map(|s| s.as_ref().to_owned()) .map_err(|_| Error::RingUnspecified) } + + fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { + &rcgen::sig_alg::ECDSA_SHA256 + } } impl PublicKeyData for Remote { @@ -401,8 +405,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 } }