From b5dc812ed75f2543c1bedb806ea48cec714aa044 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 8 Jan 2024 09:56:31 -0800 Subject: [PATCH] rework CA builder profiles --- x509-cert/src/builder.rs | 189 +---------------- x509-cert/src/builder/profile.rs | 234 ++++++++++++++++++++++ x509-cert/src/builder/profile/cabf.rs | 105 ++++++++++ x509-cert/src/builder/profile/cabf/tls.rs | 231 +++++++++++++++++++++ 4 files changed, 581 insertions(+), 178 deletions(-) create mode 100644 x509-cert/src/builder/profile.rs create mode 100644 x509-cert/src/builder/profile/cabf.rs create mode 100644 x509-cert/src/builder/profile/cabf/tls.rs diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index a95e2584b..1a63916af 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -1,7 +1,7 @@ //! X509 Certificate builder use alloc::vec; -use core::fmt; +use core::{fmt, marker::PhantomData}; use der::{asn1::BitString, referenced::OwnedToRef, Encode}; use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer}; use spki::{ @@ -23,6 +23,10 @@ use crate::{ time::Validity, }; +pub mod profile; + +use self::profile::Profile; + /// Error type #[derive(Debug)] #[non_exhaustive] @@ -70,180 +74,6 @@ impl From for Error { type Result = core::result::Result; -/// The type of certificate to build -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Profile { - /// Build a root CA certificate - Root, - /// Build an intermediate sub CA certificate - SubCA { - /// issuer Name, - /// represents the name signing the certificate - issuer: Name, - /// pathLenConstraint INTEGER (0..MAX) OPTIONAL - /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9]. - path_len_constraint: Option, - }, - /// Build an end certificate - Leaf { - /// issuer Name, - /// represents the name signing the certificate - issuer: Name, - /// Usage of the leaf certificate - usage: Usage, - /// should the subject key identifier extension be included - /// - /// From [RFC 5280 Section 4.2.1.2]: - /// For end entity certificates, subject key identifiers SHOULD be - /// derived from the public key. Two common methods for generating key - /// identifiers from the public key are identified above. - #[cfg(feature = "hazmat")] - include_subject_key_identifier: bool, - }, - #[cfg(feature = "hazmat")] - /// Opt-out of the default extensions - Manual { - /// issuer Name, - /// represents the name signing the certificate - /// A `None` will make it a self-signed certificate - issuer: Option, - }, -} - -impl Profile { - fn get_issuer(&self, subject: &Name) -> Name { - match self { - Profile::Root => subject.clone(), - Profile::SubCA { issuer, .. } => issuer.clone(), - Profile::Leaf { issuer, .. } => issuer.clone(), - #[cfg(feature = "hazmat")] - Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(), - } - } - - fn build_extensions( - &self, - spk: SubjectPublicKeyInfoRef<'_>, - issuer_spk: SubjectPublicKeyInfoRef<'_>, - tbs: &TbsCertificate, - ) -> Result> { - #[cfg(feature = "hazmat")] - // User opted out of default extensions set. - if let Profile::Manual { .. } = self { - return Ok(vec::Vec::default()); - } - - let mut extensions: vec::Vec = vec::Vec::new(); - - match self { - #[cfg(feature = "hazmat")] - Profile::Leaf { - include_subject_key_identifier: false, - .. - } => {} - _ => extensions.push( - SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?, - ), - } - - // Build Authority Key Identifier - match self { - Profile::Root => {} - _ => { - extensions.push( - AuthorityKeyIdentifier::try_from(issuer_spk.clone())? - .to_extension(&tbs.subject, &extensions)?, - ); - } - } - - // Build Basic Contraints extensions - extensions.push(match self { - Profile::Root => BasicConstraints { - ca: true, - path_len_constraint: None, - } - .to_extension(&tbs.subject, &extensions)?, - Profile::SubCA { - path_len_constraint, - .. - } => BasicConstraints { - ca: true, - path_len_constraint: *path_len_constraint, - } - .to_extension(&tbs.subject, &extensions)?, - Profile::Leaf { .. } => BasicConstraints { - ca: false, - path_len_constraint: None, - } - .to_extension(&tbs.subject, &extensions)?, - #[cfg(feature = "hazmat")] - Profile::Manual { .. } => unreachable!(), - }); - - // Build Key Usage extension - match self { - Profile::Root | Profile::SubCA { .. } => { - extensions.push( - KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign) - .to_extension(&tbs.subject, &extensions)?, - ); - } - Profile::Leaf { usage, .. } => { - let key_usage = usage.key_usage().into(); - - extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); - } - #[cfg(feature = "hazmat")] - Profile::Manual { .. } => unreachable!(), - } - - Ok(extensions) - } -} - -/// [`Usage`] describes the usage of a Leaf certificate. -/// -/// This is designed in accordance with [ETSI EN 319 412-2 § 4.3.2 Key usage]. -/// -/// The various fields will refer to [RFC 5280 § 4.2.1.3 Key Usage] definitions. -/// -/// [RFC 5280 § 4.2.1.3 Key Usage]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 -/// [ETSI EN 319 412-2 § 4.3.2 Key usage]: https://www.etsi.org/deliver/etsi_en/319400_319499/31941202/02.03.01_60/en_31941202v020301p.pdf#page=11 -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Usage { - /// [`Usage::NonRepudiation`] will set the NonRepudiation (also known as - /// contentCommitment) bit of KeyUsage. - NonRepudiation, - /// [`Usage::DigitalSignature`] will set the digitalSignature bit. - /// - /// This is meant to be used in an entity authentication service, a data - /// origin authentication service, and/or an integrity service. - DigitalSignature, - /// [`Usage::KeyAgreement`] will set the `keyAgreement` bit. - /// - /// This is meant to be used on Certificates when a Diffie-Hellman key is - /// to be used for key management. - KeyAgreement, - /// [`Usage::KeyEncipherment`] will set the `keyEncipherment` bit. - /// - /// This is meant to be used on Certificates when an RSA public - /// key is to be used for encrypting a symmetric content-decryption - /// key or an asymmetric private key. - KeyEncipherment, -} - -impl Usage { - fn key_usage(&self) -> KeyUsages { - match self { - Self::NonRepudiation => KeyUsages::NonRepudiation, - Self::DigitalSignature => KeyUsages::DigitalSignature, - Self::KeyAgreement => KeyUsages::KeyAgreement, - Self::KeyEncipherment => KeyUsages::KeyEncipherment, - } - } -} - /// X509 Certificate builder /// /// ``` @@ -297,14 +127,17 @@ where S::VerifyingKey: EncodePublicKey, { /// Creates a new certificate builder - pub fn new( - profile: Profile, + pub fn new

( + profile: P, serial_number: SerialNumber, mut validity: Validity, subject: Name, subject_public_key_info: SubjectPublicKeyInfoOwned, cert_signer: &'s S, - ) -> Result { + ) -> Result + where + P: Profile, + { let verifying_key = cert_signer.verifying_key(); let signer_pub = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; diff --git a/x509-cert/src/builder/profile.rs b/x509-cert/src/builder/profile.rs new file mode 100644 index 000000000..1b8bf7da6 --- /dev/null +++ b/x509-cert/src/builder/profile.rs @@ -0,0 +1,234 @@ +use alloc::vec; +use spki::{ + DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding, + SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, +}; + +use crate::{ + builder::Result, + certificate::{Certificate, TbsCertificate, Version}, + ext::{ + pkix::{ + AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier, + }, + AsExtension, Extension, Extensions, + }, + name::Name, + request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq}, + serial_number::SerialNumber, + time::Validity, +}; + +pub mod cabf; +pub mod piv; + +pub(crate) mod sealed { + use alloc::vec; + use spki::{ + DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding, + SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, + }; + + use crate::{ + builder::Result, + certificate::{Certificate, TbsCertificate, Version}, + ext::{ + pkix::{ + AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier, + }, + AsExtension, Extension, Extensions, + }, + name::Name, + request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq}, + serial_number::SerialNumber, + time::Validity, + }; + + pub trait Profile { + fn get_issuer(&self, subject: &Name) -> Name; + + fn build_extensions( + &self, + spk: SubjectPublicKeyInfoRef<'_>, + issuer_spk: SubjectPublicKeyInfoRef<'_>, + tbs: &TbsCertificate, + ) -> Result>; + } +} + +#[cfg(feature = "hazmat")] +pub use sealed::Profile; + +///// The type of certificate to build +//#[derive(Clone, Debug, Eq, PartialEq)] +//pub enum Profile { +// /// Build a root CA certificate +// Root, +// /// Build an intermediate sub CA certificate +// SubCA { +// /// issuer Name, +// /// represents the name signing the certificate +// issuer: Name, +// /// pathLenConstraint INTEGER (0..MAX) OPTIONAL +// /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9]. +// path_len_constraint: Option, +// }, +// /// Build an end certificate +// Leaf { +// /// issuer Name, +// /// represents the name signing the certificate +// issuer: Name, +// /// Usage of the leaf certificate +// usage: Usage, +// /// should the subject key identifier extension be included +// /// +// /// From [RFC 5280 Section 4.2.1.2]: +// /// For end entity certificates, subject key identifiers SHOULD be +// /// derived from the public key. Two common methods for generating key +// /// identifiers from the public key are identified above. +// #[cfg(feature = "hazmat")] +// include_subject_key_identifier: bool, +// }, +// #[cfg(feature = "hazmat")] +// /// Opt-out of the default extensions +// Manual { +// /// issuer Name, +// /// represents the name signing the certificate +// /// A `None` will make it a self-signed certificate +// issuer: Option, +// }, +//} +// +//impl Profile { +// pub(crate) fn get_issuer(&self, subject: &Name) -> Name { +// match self { +// Profile::Root => subject.clone(), +// Profile::SubCA { issuer, .. } => issuer.clone(), +// Profile::Leaf { issuer, .. } => issuer.clone(), +// #[cfg(feature = "hazmat")] +// Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(), +// } +// } +// +// pub(crate) fn build_extensions( +// &self, +// spk: SubjectPublicKeyInfoRef<'_>, +// issuer_spk: SubjectPublicKeyInfoRef<'_>, +// tbs: &TbsCertificate, +// ) -> Result> { +// #[cfg(feature = "hazmat")] +// // User opted out of default extensions set. +// if let Profile::Manual { .. } = self { +// return Ok(vec::Vec::default()); +// } +// +// let mut extensions: vec::Vec = vec::Vec::new(); +// +// match self { +// #[cfg(feature = "hazmat")] +// Profile::Leaf { +// include_subject_key_identifier: false, +// .. +// } => {} +// _ => extensions.push( +// SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?, +// ), +// } +// +// // Build Authority Key Identifier +// match self { +// Profile::Root => {} +// _ => { +// extensions.push( +// AuthorityKeyIdentifier::try_from(issuer_spk.clone())? +// .to_extension(&tbs.subject, &extensions)?, +// ); +// } +// } +// +// // Build Basic Contraints extensions +// extensions.push(match self { +// Profile::Root => BasicConstraints { +// ca: true, +// path_len_constraint: None, +// } +// .to_extension(&tbs.subject, &extensions)?, +// Profile::SubCA { +// path_len_constraint, +// .. +// } => BasicConstraints { +// ca: true, +// path_len_constraint: *path_len_constraint, +// } +// .to_extension(&tbs.subject, &extensions)?, +// Profile::Leaf { .. } => BasicConstraints { +// ca: false, +// path_len_constraint: None, +// } +// .to_extension(&tbs.subject, &extensions)?, +// #[cfg(feature = "hazmat")] +// Profile::Manual { .. } => unreachable!(), +// }); +// +// // Build Key Usage extension +// match self { +// Profile::Root | Profile::SubCA { .. } => { +// extensions.push( +// KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign) +// .to_extension(&tbs.subject, &extensions)?, +// ); +// } +// Profile::Leaf { usage, .. } => { +// let key_usage = usage.key_usage().into(); +// +// extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); +// } +// #[cfg(feature = "hazmat")] +// Profile::Manual { .. } => unreachable!(), +// } +// +// Ok(extensions) +// } +//} +// +///// [`Usage`] describes the usage of a Leaf certificate. +///// +///// This is designed in accordance with [ETSI EN 319 412-2 § 4.3.2 Key usage]. +///// +///// The various fields will refer to [RFC 5280 § 4.2.1.3 Key Usage] definitions. +///// +///// [RFC 5280 § 4.2.1.3 Key Usage]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 +///// [ETSI EN 319 412-2 § 4.3.2 Key usage]: https://www.etsi.org/deliver/etsi_en/319400_319499/31941202/02.03.01_60/en_31941202v020301p.pdf#page=11 +//#[derive(Clone, Debug, PartialEq, Eq)] +//pub enum Usage { +// /// [`Usage::NonRepudiation`] will set the NonRepudiation (also known as +// /// contentCommitment) bit of KeyUsage. +// NonRepudiation, +// /// [`Usage::DigitalSignature`] will set the digitalSignature bit. +// /// +// /// This is meant to be used in an entity authentication service, a data +// /// origin authentication service, and/or an integrity service. +// DigitalSignature, +// /// [`Usage::KeyAgreement`] will set the `keyAgreement` bit. +// /// +// /// This is meant to be used on Certificates when a Diffie-Hellman key is +// /// to be used for key management. +// KeyAgreement, +// /// [`Usage::KeyEncipherment`] will set the `keyEncipherment` bit. +// /// +// /// This is meant to be used on Certificates when an RSA public +// /// key is to be used for encrypting a symmetric content-decryption +// /// key or an asymmetric private key. +// KeyEncipherment, +//} +// +//impl Usage { +// fn key_usage(&self) -> KeyUsages { +// match self { +// Self::NonRepudiation => KeyUsages::NonRepudiation, +// Self::DigitalSignature => KeyUsages::DigitalSignature, +// Self::KeyAgreement => KeyUsages::KeyAgreement, +// Self::KeyEncipherment => KeyUsages::KeyEncipherment, +// } +// } +//} diff --git a/x509-cert/src/builder/profile/cabf.rs b/x509-cert/src/builder/profile/cabf.rs new file mode 100644 index 000000000..2caa5b96a --- /dev/null +++ b/x509-cert/src/builder/profile/cabf.rs @@ -0,0 +1,105 @@ +//! https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf +use alloc::vec; +use core::marker::PhantomData; + +use crate::{ + builder::{Profile, Result}, + certificate::{Certificate, TbsCertificate, Version}, + ext::{ + pkix::{ + AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier, + }, + AsExtension, Extension, Extensions, + }, + name::Name, +}; +use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef}; + +pub struct Root { + pub emits_ocsp_response: bool, +} + +impl Default for Root { + fn default() -> Self { + Self { + emits_ocsp_response: false, + } + } +} + +impl Profile for Root { + fn get_issuer(&self, subject: &Name) -> Name { + subject.clone() + } + + fn build_extensions( + &self, + spk: SubjectPublicKeyInfoRef<'_>, + issuer_spk: SubjectPublicKeyInfoRef<'_>, + tbs: &TbsCertificate, + ) -> Result> { + let mut extensions: vec::Vec = vec::Vec::new(); + + // 7.1.2.1.2 Root CA Extensions + + // subjectKeyIdentifier MUST + // + // TODO: from 7.1.2.11.4 Subject Key Identifier + // The CA MUST generate a subjectKeyIdentifier that is unique within the scope of all + // Certificates it has issued for each unique public key (the subjectPublicKeyInfo field of the + // tbsCertificate). For example, CAs may generate the subject key identifier using an algorithm + // derived from the public key, or may generate a sufficiently‐large unique number, such by using a + // CSPRNG. + let ski = SubjectKeyIdentifier::try_from(spk)?; + extensions.push(ski.to_extension(&tbs.subject, &extensions)?); + + // authorityKeyIdentifier RECOMMENDED + // 7.1.2.1.3 Root CA Authority Key Identifier + extensions.push( + AuthorityKeyIdentifier { + // KeyIdentifier must be the same as subjectKeyIdentifier + key_identifier: Some(ski.0), + // other fields must not be present. + ..Default::default() + } + .to_extension(&tbs.subject, &extensions)?, + ); + + // Spec: 7.1.2.1.4 Root CA Basic Constraints + extensions.push( + BasicConstraints { + ca: true, + path_len_constraint: None, + } + .to_extension(&tbs.subject, &extensions)?, + ); + + // Spec: 7.1.2.10.7 CA Certificate Key Usage + let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign; + if self.emits_ocsp_response { + key_usage |= KeyUsages::DigitalSignature; + } + extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); + + Ok(extensions) + } + + // 7.1.2.1 Root CA Certificate Profile + // TODO: + // - issuerUniqueID MUST NOT be present + // - subjectUniqueID MUST NOT be present + // NOTE(baloo): we never build those? + // + // 7.1.2.1.1 Root CA Validity + // TODO: + // - Minimum 2922 days (approx. 8 years) + // - Max 9132 days (approx. 25 years) + // + // +} + +pub mod tls; + +pub mod codesigning { + //! https://cabforum.org/wp-content/uploads/Baseline-Requirements-for-the-Issuance-and-Management-of-Code-Signing.v2.8.pdf +} diff --git a/x509-cert/src/builder/profile/cabf/tls.rs b/x509-cert/src/builder/profile/cabf/tls.rs new file mode 100644 index 000000000..2cff8e4df --- /dev/null +++ b/x509-cert/src/builder/profile/cabf/tls.rs @@ -0,0 +1,231 @@ +//! https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf +//! 7.1.2.6 TLS Subordinate CA Certificate Profile +use alloc::vec; +use core::marker::PhantomData; + +use const_oid::db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH}; + +use crate::{ + builder::{Profile, Result}, + certificate::{Certificate, TbsCertificate, Version}, + ext::{ + pkix::{ + AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages, + SubjectKeyIdentifier, + }, + AsExtension, Extension, Extensions, + }, + name::Name, +}; +use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef}; + +pub struct Subordinate { + /// issuer Name, + /// represents the name signing the certificate + pub issuer: Name, + /// pathLenConstraint INTEGER (0..MAX) OPTIONAL + /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9]. + pub path_len_constraint: Option, + + /// [`emits_ocsp_response`] will append the [`KeyUsages::DigitalSignature`]. This is meant for + /// CAs that will reply to OCSP requests. + pub emits_ocsp_response: bool, + + pub client_auth: bool, +} + +impl Profile for Subordinate { + fn get_issuer(&self, _subject: &Name) -> Name { + self.issuer.clone() + } + + fn build_extensions( + &self, + spk: SubjectPublicKeyInfoRef<'_>, + issuer_spk: SubjectPublicKeyInfoRef<'_>, + tbs: &TbsCertificate, + ) -> Result> { + let mut extensions: vec::Vec = vec::Vec::new(); + + // # 7.1.2.6.1 TLS Subordinate CA Extensions + + // ## authorityKeyIdentifier MUST + // 7.1.2.11.1 Authority Key Identifier + extensions.push( + AuthorityKeyIdentifier::try_from(issuer_spk.clone())? + .to_extension(&tbs.subject, &extensions)?, + ); + + // ## basicConstraints MUST + // Spec: 7.1.2.10.4 CA Certificate Basic Constraints + extensions.push( + BasicConstraints { + // MUST be set TRUE + ca: true, + // May be present + path_len_constraint: self.path_len_constraint, + } + .to_extension(&tbs.subject, &extensions)?, + ); + + // ## keyUsage MUST + // Spec: 7.1.2.10.7 CA Certificate Key Usage + let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign; + if self.emits_ocsp_response { + key_usage |= KeyUsages::DigitalSignature; + } + extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); + + // ## subjectKeyIdentifier MUST + let ski = SubjectKeyIdentifier::try_from(spk)?; + extensions.push(ski.to_extension(&tbs.subject, &extensions)?); + + // ## extKeyUsage MUST + // Spec 7.1.2.10.6 CA Certificate Extended Key Usage + let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]); + if self.client_auth { + eku.0.push(ID_KP_CLIENT_AUTH); + } + + // ## authorityInformationAccess SHOULD + // Spec 7.1.2.10.3 CA Certificate Authority Information Access + // NOTE(baloo): this is a should and we can't put a generic value here, it's mostly up to + // the consumer of the API. + + Ok(extensions) + } + + // 7.1.2.6.1 TLS Subordinate CA Extensions + // Check certificatePolicies MUST + // check crlDistributionPoints MUST +} + +/// 7.1.2.7 Subscriber (Server) Certificate Profile +pub struct Subscriber { + /// issuer Name, + /// represents the name signing the certificate + pub issuer: Name, + pub client_auth: bool, + + #[cfg(feature = "hazmat")] + /// TLS1.2 specific flags + /// + /// It is only available under the `hazmat` feature flag. + pub tls12_options: Tls12Options, + + /// Enable `dataEncipherment` bit on `KeyUsage`. + /// This bit is not recommended and is [`Pending Prohibition`]. + /// + /// It is only available under the `hazmat` feature flag. + /// + /// [`Pending Prohibition`]: https://github.com/cabforum/servercert/issues/384 + #[cfg(feature = "hazmat")] + pub enable_data_encipherment: bool, +} + +/// [`Tls12Options`] stores the KeyUsage bits that are required by specific uses of TLS1.2. +/// +/// This specifically refers to the [section 7.4.2 of RFC 5246]: +/// ``` +/// RSA RSA public key; the certificate MUST allow the +/// RSA_PSK key to be used for encryption (the +/// keyEncipherment bit MUST be set if the key +/// usage extension is present). +/// Note: RSA_PSK is defined in [TLSPSK]. +/// [...] +/// DH_DSS Diffie-Hellman public key; the keyAgreement bit +/// DH_RSA MUST be set if the key usage extension is +/// present. +/// ``` +/// +/// Those are meant for consumers relying on non-DH schemes with RSA keys and non-ECDH schemes +/// with ECC keys. +/// +/// This behavior is no longer provided by TLS 1.3 and is NOT RECOMMENDED by CABF as it is +/// [`Pending Prohibition`]. +/// +/// [section 7.4.2 of RFC 5246]: https://www.rfc-editor.org/rfc/rfc5246#section-7.4.2 +/// [`Pending Prohibition`]: https://github.com/cabforum/servercert/issues/384 +pub struct Tls12Options { + /// Enable `keyEncipherment` on RSA keys. + pub enable_key_encipherment: bool, + /// Enable `keyAgreement` on ECC keys. + pub enable_key_agreement: bool, +} + +impl Profile for Subscriber { + fn get_issuer(&self, _subject: &Name) -> Name { + self.issuer.clone() + } + + fn build_extensions( + &self, + spk: SubjectPublicKeyInfoRef<'_>, + issuer_spk: SubjectPublicKeyInfoRef<'_>, + tbs: &TbsCertificate, + ) -> Result> { + let mut extensions: vec::Vec = vec::Vec::new(); + + // # 7.1.2.7.6 Subscriber Certificate Extensions + + // ## authorityInformationAccess MUST + // 7.1.2.7.7 Subscriber Certificate Authority Information Access + // TODO + + // ## authorityKeyIdentifier MUST + // 7.1.2.11.1 Authority Key Identifier + extensions.push( + AuthorityKeyIdentifier::try_from(issuer_spk.clone())? + .to_extension(&tbs.subject, &extensions)?, + ); + + // ## extKeyUsage MUST + // 7.1.2.7.10 Subscriber Certificate Extended Key Usage + let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]); + if self.client_auth { + eku.0.push(ID_KP_CLIENT_AUTH); + } + + // ## subjectAltName MUST + // TODO: move that to validation? + + // ## keyUsage SHOULD + // 7.1.2.7.11 Subscriber Certificate Key Usage + let mut key_usage = KeyUsages::DigitalSignature.into(); + #[cfg(feature = "hazmat")] + { + if spk.is_rsa() { + if self.enable_data_encipherment { + key_usage |= KeyUsages::DataEncipherment; + } + if self.tls12_options.enable_key_encipherment { + key_usage |= KeyUsages::KeyEncipherment; + } + } + if self.tls12_options.enable_key_agreement && spk.is_ecc() { + key_usage |= KeyUsages::KeyAgreement; + } + } + extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); + + Ok(extensions) + } +} + +// TODO(baloo): move all that? +use const_oid::db::rfc5912; + +trait KeyType { + fn is_rsa(&self) -> bool; + fn is_ecc(&self) -> bool; +} + +impl<'a> KeyType for SubjectPublicKeyInfoRef<'a> { + fn is_rsa(&self) -> bool { + self.algorithm.oid == rfc5912::RSA_ENCRYPTION + } + + fn is_ecc(&self) -> bool { + self.algorithm.oid == rfc5912::ID_EC_PUBLIC_KEY + } +}