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

x509_cert/builder/profile/
cabf.rs

1//! CA/Browser forum specific profiles
2//!
3//! <https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf>
4
5use alloc::{collections::BTreeSet, vec};
6
7use crate::{
8    builder::{BuilderProfile, Error, Result},
9    certificate::TbsCertificate,
10    ext::{
11        Extension, ToExtension,
12        pkix::{
13            AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
14        },
15    },
16    name::Name,
17};
18use const_oid::db::{rfc2256, rfc4519};
19use spki::SubjectPublicKeyInfoRef;
20
21/// Check Name encoding
22///
23/// BR 7.1.4.1 Name Encoding
24///
25/// See <https://cabforum.org/working-groups/server/baseline-requirements/requirements/#7141-name-encoding>
26pub fn check_names_encoding(name: &Name, multiple_allowed: bool) -> Result<()> {
27    // NOTE: RDNSequence may be empty (at least with tls Subscribers).
28
29    let enforce_ordering = vec![
30        rfc4519::DOMAIN_COMPONENT,
31        rfc4519::COUNTRY_NAME,
32        rfc2256::STATE_OR_PROVINCE_NAME,
33        rfc4519::LOCALITY_NAME,
34        rfc4519::POSTAL_CODE,
35        rfc2256::STREET_ADDRESS,
36        rfc4519::ORGANIZATION_NAME,
37        rfc4519::SURNAME,
38        rfc4519::GIVEN_NAME,
39        rfc4519::ORGANIZATIONAL_UNIT_NAME,
40        rfc4519::COMMON_NAME,
41    ];
42    let mut ordering = enforce_ordering.iter();
43
44    let mut seen = BTreeSet::new();
45
46    for rdn in name.iter_rdn() {
47        if rdn.len() != 1 {
48            return Err(Error::NonUniqueRdn);
49        }
50
51        for atv in rdn.iter() {
52            if !multiple_allowed && !seen.insert(atv.oid) {
53                return Err(Error::NonUniqueATV);
54            }
55
56            // If the type is in the list we should enforce ordering of
57            if enforce_ordering.iter().any(|attr| attr == &atv.oid) {
58                // then advance the iterator in that list, and make sure we respected it
59                if !ordering.any(|attr| attr == &atv.oid) {
60                    return Err(Error::InvalidAttribute { oid: atv.oid });
61                }
62            }
63        }
64    }
65
66    Ok(())
67}
68
69/// Check CA subject naming
70///
71/// BR 7.1.2.10.2 CA Certificate Naming
72pub fn ca_certificate_naming(subject: &Name) -> Result<()> {
73    let mut required = BTreeSet::from([
74        rfc4519::COUNTRY_NAME,
75        rfc4519::ORGANIZATION_NAME,
76        rfc4519::COMMON_NAME,
77    ]);
78    let mut allowed = BTreeSet::from([
79        rfc4519::COUNTRY_NAME,
80        rfc2256::STATE_OR_PROVINCE_NAME,
81        rfc4519::LOCALITY_NAME,
82        rfc4519::POSTAL_CODE,
83        rfc2256::STREET_ADDRESS,
84        rfc4519::ORGANIZATION_NAME,
85        rfc4519::COMMON_NAME,
86    ]);
87
88    check_names_encoding(subject, false)?;
89
90    for atv in subject.iter() {
91        if !allowed.remove(&atv.oid) {
92            return Err(Error::InvalidAttribute { oid: atv.oid });
93        }
94        required.remove(&atv.oid);
95    }
96
97    if !required.is_empty() {
98        return Err(Error::MissingAttributes);
99    }
100
101    Ok(())
102}
103
104/// Root CA certificate profile
105///
106/// Certificate profile conforming - to the extent possible - to the CABF BR for Root CAs.
107pub struct Root {
108    /// Whether the root CA will emit OCSP responses.
109    /// This adds the [`KeyUsages::DigitalSignature`] bit to the [`KeyUsage`] extension.
110    pub emits_ocsp_response: bool,
111    subject: Name,
112}
113
114impl Root {
115    /// Create a new root profile.
116    pub fn new(emits_ocsp_response: bool, subject: Name) -> Result<Self> {
117        ca_certificate_naming(&subject)?;
118
119        Ok(Self {
120            emits_ocsp_response,
121            subject,
122        })
123    }
124}
125
126impl BuilderProfile for Root {
127    fn get_issuer(&self, subject: &Name) -> Name {
128        subject.clone()
129    }
130
131    fn get_subject(&self) -> Name {
132        self.subject.clone()
133    }
134
135    fn build_extensions(
136        &self,
137        spk: SubjectPublicKeyInfoRef<'_>,
138        _issuer_spk: SubjectPublicKeyInfoRef<'_>,
139        tbs: &TbsCertificate,
140    ) -> Result<vec::Vec<Extension>> {
141        let mut extensions: vec::Vec<Extension> = vec::Vec::new();
142
143        // 7.1.2.1.2 Root CA Extensions
144
145        let ski = SubjectKeyIdentifier::try_from(spk)?;
146
147        // ## authorityKeyIdentifier RECOMMENDED
148        // 7.1.2.1.3 Root CA Authority Key Identifier
149        extensions.push(
150            AuthorityKeyIdentifier {
151                // KeyIdentifier must be the same as subjectKeyIdentifier
152                key_identifier: Some(ski.0.clone()),
153                // other fields must not be present.
154                ..Default::default()
155            }
156            .to_extension(&tbs.subject, &extensions)?,
157        );
158
159        // ## basicConstraints MUST
160        // Spec: 7.1.2.1.4 Root CA Basic Constraints
161        extensions.push(
162            BasicConstraints {
163                ca: true,
164                path_len_constraint: None,
165            }
166            .to_extension(&tbs.subject, &extensions)?,
167        );
168
169        // ## keyUsage MUST
170        // Spec: 7.1.2.10.7 CA Certificate Key Usage
171        let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign;
172        if self.emits_ocsp_response {
173            key_usage |= KeyUsages::DigitalSignature;
174        }
175        extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
176
177        // ## subjectKeyIdentifier MUST
178        //
179        // TODO: from 7.1.2.11.4 Subject Key Identifier
180        // The CA MUST generate a subjectKeyIdentifier that is unique within the scope of all
181        // Certificates it has issued for each unique public key (the subjectPublicKeyInfo field of the
182        // tbsCertificate). For example, CAs may generate the subject key identifier using an algorithm
183        // derived from the public key, or may generate a sufficiently‐large unique number, such by using a
184        // CSPRNG.
185        extensions.push(ski.to_extension(&tbs.subject, &extensions)?);
186
187        // ## extKeyUsage MUST NOT
188
189        // ## certificatePolicies NOT RECOMMENDED
190
191        // ## Signed Certificate Timestamp List MAY
192
193        // ## Any other extension NOT RECOMMENDED
194
195        Ok(extensions)
196    }
197
198    // 7.1.2.1 Root CA Certificate Profile
199    // TODO:
200    //   - issuerUniqueID MUST NOT be present
201    //   - subjectUniqueID MUST NOT be present
202    // NOTE(baloo): we never build those?
203    //
204    // 7.1.2.1.1 Root CA Validity
205    // TODO:
206    //   - Minimum 2922 days (approx. 8 years)
207    //   - Max 9132 days (approx. 25 years)
208    //
209    //
210}
211
212pub mod tls;
213
214pub mod codesigning {
215    //! <https://cabforum.org/uploads/Baseline-Requirements-for-the-Issuance-and-Management-of-Code-Signing.v3.9.pdf>
216    // TODO
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use core::str::FromStr;
223
224    #[test]
225    fn test_check_names() {
226        assert!(
227            check_names_encoding(&Name::from_str("C=US,ST=CA").expect("parse name"), false)
228                .is_err()
229        );
230        assert!(
231            check_names_encoding(&Name::from_str("ST=CA,C=US").expect("parse name"), false).is_ok()
232        );
233        assert!(
234            check_names_encoding(
235                &Name::from_str("serialNumber=1234,ST=CA,C=US").expect("parse name"),
236                false
237            )
238            .is_ok()
239        );
240    }
241}