Skip to main content
This is unreleased documentation for the main (development) branch of crypto-glue.

x509_cert/builder/profile/cabf/
tls.rs

1//! <https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf>
2//! 7.1.2.6 TLS Subordinate CA Certificate Profile
3use alloc::vec;
4
5use const_oid::db::{
6    rfc4519,
7    rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH},
8};
9use der::asn1::SetOfVec;
10
11#[cfg(feature = "hazmat")]
12use const_oid::db::rfc5912;
13
14use crate::{
15    attr::AttributeTypeAndValue,
16    builder::{BuilderProfile, Result},
17    certificate::TbsCertificate,
18    ext::{
19        Extension, ToExtension,
20        pkix::{
21            AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages,
22            SubjectKeyIdentifier, name::GeneralNames,
23        },
24    },
25    name::{Name, RelativeDistinguishedName},
26};
27use spki::SubjectPublicKeyInfoRef;
28
29/// TLS Subordinate CA Certificate Profile
30///
31/// BR 7.1.2.6 TLS Subordinate CA Certificate Profile
32pub struct Subordinate {
33    /// issuer   Name,
34    /// represents the name signing the certificate
35    pub issuer: Name,
36
37    /// subject Name,
38    /// represents the name of the newly issued certificated
39    pub subject: Name,
40
41    /// pathLenConstraint       INTEGER (0..MAX) OPTIONAL
42    /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
43    pub path_len_constraint: Option<u8>,
44
45    /// `emits_ocsp_response` will append the [`KeyUsages::DigitalSignature`]. This is meant for
46    /// CAs that will reply to OCSP requests.
47    pub emits_ocsp_response: bool,
48
49    /// Allows this subordinate CA to issue certificates capable of doing client authentication
50    pub client_auth: bool,
51}
52
53impl BuilderProfile for Subordinate {
54    fn get_issuer(&self, _subject: &Name) -> Name {
55        self.issuer.clone()
56    }
57
58    fn get_subject(&self) -> Name {
59        self.subject.clone()
60    }
61
62    fn build_extensions(
63        &self,
64        spk: SubjectPublicKeyInfoRef<'_>,
65        issuer_spk: SubjectPublicKeyInfoRef<'_>,
66        tbs: &TbsCertificate,
67    ) -> Result<vec::Vec<Extension>> {
68        let mut extensions: vec::Vec<Extension> = vec::Vec::new();
69
70        // # 7.1.2.6.1 TLS Subordinate CA Extensions
71
72        // ## authorityKeyIdentifier MUST
73        // 7.1.2.11.1 Authority Key Identifier
74        extensions.push(
75            AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
76                .to_extension(&tbs.subject, &extensions)?,
77        );
78
79        // ## basicConstraints MUST
80        // Spec: 7.1.2.10.4 CA Certificate Basic Constraints
81        extensions.push(
82            BasicConstraints {
83                // MUST be set TRUE
84                ca: true,
85                // May be present
86                path_len_constraint: self.path_len_constraint,
87            }
88            .to_extension(&tbs.subject, &extensions)?,
89        );
90
91        // ## keyUsage MUST
92        // Spec: 7.1.2.10.7 CA Certificate Key Usage
93        let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign;
94        if self.emits_ocsp_response {
95            key_usage |= KeyUsages::DigitalSignature;
96        }
97        extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
98
99        // ## subjectKeyIdentifier MUST
100        let ski = SubjectKeyIdentifier::try_from(spk)?;
101        extensions.push(ski.to_extension(&tbs.subject, &extensions)?);
102
103        // ## extKeyUsage MUST
104        // Spec 7.1.2.10.6 CA Certificate Extended Key Usage
105        let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]);
106        if self.client_auth {
107            eku.0.push(ID_KP_CLIENT_AUTH);
108        }
109        extensions.push(eku.to_extension(&tbs.subject, &extensions)?);
110
111        // ## authorityInformationAccess SHOULD
112        // Spec 7.1.2.10.3 CA Certificate Authority Information Access
113        // NOTE(baloo): this is a should and we can't put a generic value here, it's mostly up to
114        // the consumer of the API.
115
116        Ok(extensions)
117    }
118
119    // 7.1.2.6.1 TLS Subordinate CA Extensions
120    // Check certificatePolicies MUST
121    // check crlDistributionPoints MUST
122}
123
124/// Type of Subscriber Certificates that may be issued.
125#[derive(Debug, Clone, PartialEq)]
126pub enum CertificateType {
127    /// Subscriber Certificate to be Domain Validated
128    DomainValidated(DomainValidated),
129    /// Subscriber Certificate to be Individual Validated
130    IndividualValidated,
131    /// Subscriber Certificate to be Organization Validated
132    OrganizationValidated,
133    /// Subscriber Certificate to be Extended Validation
134    ExtendedValidation,
135}
136
137impl CertificateType {
138    /// Creates a new [`CertificateType`] that has been domain validated
139    pub fn domain_validated(subject: Name, names: GeneralNames) -> Result<Self> {
140        // # 7.1.2.7.2 Domain Validated
141        // CountryName MAY
142        // CommonName NOT RECOMMENDED
143        // Any other attribute MUST NOT
144
145        // TODO(baloo): not very happy with all that, might as well throw that in a helper
146        // or something.
147        let rdns: vec::Vec<RelativeDistinguishedName> = subject
148            .iter_rdn()
149            .filter_map(|rdn| {
150                let out = SetOfVec::<AttributeTypeAndValue>::from_iter(
151                    rdn.iter()
152                        .filter(|attr_value| {
153                            attr_value.oid == rfc4519::COUNTRY_NAME
154                                || attr_value.oid == rfc4519::COMMON_NAME
155                        })
156                        .cloned(),
157                )
158                .ok()?;
159
160                Some(RelativeDistinguishedName(out))
161            })
162            .filter(|rdn| !rdn.is_empty())
163            .collect();
164
165        let subject: Name = Name(rdns.into());
166
167        Ok(Self::DomainValidated(DomainValidated { subject, names }))
168    }
169}
170
171/// Subscriber Certificate to be Domain Validated
172#[derive(Debug, Clone, PartialEq)]
173pub struct DomainValidated {
174    subject: Name,
175    names: GeneralNames,
176}
177
178/// 7.1.2.7 Subscriber (Server) Certificate Profile
179pub struct Subscriber {
180    /// Subtype of the Subscriber Certificate Profile
181    pub certificate_type: CertificateType,
182
183    /// issuer   Name,
184    /// represents the name signing the certificate
185    pub issuer: Name,
186
187    /// Enable client authentication with the newly issued certificate
188    pub client_auth: bool,
189
190    /// TLS1.2 specific flags
191    ///
192    /// It is only available under the `hazmat` feature flag.
193    #[cfg(feature = "hazmat")]
194    pub tls12_options: Tls12Options,
195
196    /// Enable `dataEncipherment` bit on `KeyUsage`.
197    /// This bit is not recommended and is [`Pending Prohibition`].
198    ///
199    /// It is only available under the `hazmat` feature flag.
200    ///
201    /// [`Pending Prohibition`]: https://github.com/cabforum/servercert/issues/384
202    #[cfg(feature = "hazmat")]
203    pub enable_data_encipherment: bool,
204}
205
206/// [`Tls12Options`] stores the KeyUsage bits that are required by specific uses of TLS1.2.
207///
208/// This specifically refers to the [section 7.4.2 of RFC 5246]:
209/// ``` text
210///  RSA                RSA public key; the certificate MUST allow the
211///  RSA_PSK            key to be used for encryption (the
212///                     keyEncipherment bit MUST be set if the key
213///                     usage extension is present).
214///                     Note: RSA_PSK is defined in [TLSPSK].
215/// [...]
216///  DH_DSS             Diffie-Hellman public key; the keyAgreement bit
217///  DH_RSA             MUST be set if the key usage extension is
218///                     present.
219/// ```
220///
221/// Those are meant for consumers relying on non-DH schemes with RSA keys and non-ECDH schemes
222/// with ECC keys.
223///
224/// This behavior is no longer provided by TLS 1.3 and is NOT RECOMMENDED by CABF as it is
225/// [`Pending Prohibition`].
226///
227/// [section 7.4.2 of RFC 5246]: https://www.rfc-editor.org/rfc/rfc5246#section-7.4.2
228/// [`Pending Prohibition`]: https://github.com/cabforum/servercert/issues/384
229#[derive(Default)]
230pub struct Tls12Options {
231    /// Enable `keyEncipherment` on RSA keys.
232    pub enable_key_encipherment: bool,
233    /// Enable `keyAgreement` on ECC keys.
234    pub enable_key_agreement: bool,
235}
236
237impl BuilderProfile for Subscriber {
238    fn get_issuer(&self, _subject: &Name) -> Name {
239        self.issuer.clone()
240    }
241
242    fn get_subject(&self) -> Name {
243        match &self.certificate_type {
244            CertificateType::DomainValidated(DomainValidated { subject, .. }) => subject.clone(),
245            _ => todo!(),
246        }
247    }
248
249    #[cfg_attr(not(feature = "hazmat"), allow(unused_variables))]
250    fn build_extensions(
251        &self,
252        spk: SubjectPublicKeyInfoRef<'_>,
253        issuer_spk: SubjectPublicKeyInfoRef<'_>,
254        tbs: &TbsCertificate,
255    ) -> Result<vec::Vec<Extension>> {
256        let mut extensions: vec::Vec<Extension> = vec::Vec::new();
257
258        // # 7.1.2.7.6 Subscriber Certificate Extensions
259
260        // ## authorityInformationAccess MUST
261        // 7.1.2.7.7 Subscriber Certificate Authority Information Access
262        // TODO
263
264        // ## authorityKeyIdentifier MUST
265        // 7.1.2.11.1 Authority Key Identifier
266        extensions.push(
267            AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
268                .to_extension(&tbs.subject, &extensions)?,
269        );
270
271        // ## extKeyUsage MUST
272        // 7.1.2.7.10 Subscriber Certificate Extended Key Usage
273        let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]);
274        if self.client_auth {
275            eku.0.push(ID_KP_CLIENT_AUTH);
276        }
277        extensions.push(eku.to_extension(&tbs.subject, &extensions)?);
278
279        // ## basicConstraints MUST
280        // Spec: 7.1.2.7.8 Subscriber Certificate Basic Constraints
281        extensions.push(
282            BasicConstraints {
283                // MUST be set FALSE
284                ca: false,
285                // MUST NOT be preset
286                path_len_constraint: None,
287            }
288            .to_extension(&tbs.subject, &extensions)?,
289        );
290
291        // ## subjectAltName MUST
292        // TODO: move that to validation?
293
294        // ## keyUsage SHOULD
295        // 7.1.2.7.11 Subscriber Certificate Key Usage
296        #[cfg_attr(not(feature = "hazmat"), allow(unused_mut))]
297        let mut key_usage = KeyUsages::DigitalSignature.into();
298        #[cfg(feature = "hazmat")]
299        {
300            if spk.is_rsa() {
301                if self.enable_data_encipherment {
302                    key_usage |= KeyUsages::DataEncipherment;
303                }
304                if self.tls12_options.enable_key_encipherment {
305                    key_usage |= KeyUsages::KeyEncipherment;
306                }
307            }
308            if self.tls12_options.enable_key_agreement && spk.is_ecc() {
309                key_usage |= KeyUsages::KeyAgreement;
310            }
311        }
312        extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
313
314        // ## subjectKeyIdentifier NOT RECOMMENDED
315        // TODO(baloo): there is a conflict between BRG and RFC 5280 4.2.1.2
316        // RFC marks it as SHOULD, BRG marks it as NOT RECOMMENDED.
317        //
318        // Zlint (our linter) also emits an error if not applied.
319        // upstream PR: https://github.com/zmap/zlint/pull/788
320        //let ski = SubjectKeyIdentifier::try_from(spk)?;
321        //extensions.push(ski.to_extension(&tbs.subject, &extensions)?);
322
323        Ok(extensions)
324    }
325}
326
327#[cfg(feature = "hazmat")]
328trait KeyType {
329    fn is_rsa(&self) -> bool;
330    fn is_ecc(&self) -> bool;
331}
332
333#[cfg(feature = "hazmat")]
334impl KeyType for SubjectPublicKeyInfoRef<'_> {
335    fn is_rsa(&self) -> bool {
336        self.algorithm.oid == rfc5912::RSA_ENCRYPTION
337    }
338
339    fn is_ecc(&self) -> bool {
340        self.algorithm.oid == rfc5912::ID_EC_PUBLIC_KEY
341    }
342}