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}