Skip to main content

structured_pkix/
policy.rs

1//! The policies a certificate was issued under.
2//!
3//! A policy identifier is what a trusted list points at when it says which
4//! service issued a certificate, so these are identity rather than
5//! description. The qualifiers beside them are kept as they were encoded: one
6//! of them is a location, and a location is data here.
7
8use crate::error::{Error, ErrorCode, Located, Result, gate};
9use crate::extension::{self, Extension};
10use structured_asn1::memory::{Memory, OwnedVec};
11use structured_asn1::{Oid, Parser, Tag, universal};
12
13/// One policy, and whatever was written beside it.
14#[derive(Debug)]
15pub struct PolicyInformation<'a> {
16    policy: Oid<'a>,
17    qualifiers: OwnedVec<PolicyQualifier<'a>>,
18    at: u64,
19}
20
21impl<'a> PolicyInformation<'a> {
22    /// Which policy.
23    #[must_use]
24    pub fn policy(&self) -> Oid<'a> {
25        self.policy
26    }
27
28    /// What was written beside it.
29    #[must_use]
30    pub fn qualifiers(&self) -> &[PolicyQualifier<'a>] {
31        &self.qualifiers
32    }
33}
34
35/// One qualifier: what kind it is, and its value as encoded.
36#[derive(Debug, Clone, Copy)]
37pub struct PolicyQualifier<'a> {
38    kind: Oid<'a>,
39    value: &'a [u8],
40    /// Where the value sits in the document, so an error found while
41    /// re-parsing it points at the qualifier rather than at the front of the
42    /// certificate.
43    at: u64,
44    /// Depth above the qualifier pair, retained for its later decode.
45    outer: u32,
46}
47
48impl<'a> PolicyQualifier<'a> {
49    /// What kind of qualifier this is.
50    #[must_use]
51    pub fn kind(self) -> Oid<'a> {
52        self.kind
53    }
54
55    /// The value, as it was encoded. A practice statement qualifier holds a
56    /// location, which stays a string of bytes here.
57    #[must_use]
58    pub fn value(self) -> &'a [u8] {
59        self.value
60    }
61}
62
63/// The policies of one certificate.
64#[derive(Debug)]
65pub struct CertificatePolicies<'a> {
66    policies: OwnedVec<PolicyInformation<'a>>,
67}
68
69impl<'a> CertificatePolicies<'a> {
70    /// Every policy, in order.
71    #[must_use]
72    pub fn policies(&self) -> &[PolicyInformation<'a>] {
73        &self.policies
74    }
75
76    /// Whether one policy is among them.
77    ///
78    /// The identifier that stands for any policy is not treated as matching
79    /// everything: whether it does depends on where in a path this certificate
80    /// sits, which is not a question this layer can answer.
81    pub fn has(&self, policy: Oid<'_>, execution: &Memory) -> Result<bool> {
82        for information in &self.policies {
83            let work = 1 + information
84                .policy
85                .as_bytes()
86                .len()
87                .max(policy.as_bytes().len()) as u64;
88            execution.charge_work(work).map_err(|reason| {
89                Error::resource(reason, "extensions.certificatePolicies", information.at)
90            })?;
91            if information.policy == policy {
92                return Ok(true);
93            }
94        }
95        Ok(false)
96    }
97
98    /// Reads the value of a certificate policies extension.
99    pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
100        const FIELD: &str = "extensions.certificatePolicies";
101        let fail = |code| Error::new(code, FIELD, extension.span().start);
102
103        let mut inside = extension::sequence(extension, FIELD, execution)?;
104        let mut policies = inside.memory().vector();
105        let mut identifiers = inside.memory().map::<Oid<'a>, ()>();
106
107        while let Some(entry) = inside.next_element().field(FIELD)? {
108            let at = |code| Error::new(code, FIELD, entry.span().start);
109            if entry.tag() != Tag::constructed(universal::SEQUENCE) {
110                return Err(at(ErrorCode::Encoding(
111                    structured_asn1::ErrorCode::UnexpectedTag,
112                )));
113            }
114
115            let mut information = inside.enter(&entry).field(FIELD)?;
116            let policy = information.read_oid().field(FIELD)?;
117            let mut qualifiers = inside.memory().vector();
118
119            if let Some(list) = information.next_element().field(FIELD)? {
120                if list.tag() != Tag::constructed(universal::SEQUENCE) {
121                    return Err(at(ErrorCode::Encoding(
122                        structured_asn1::ErrorCode::UnexpectedTag,
123                    )));
124                }
125                let mut each = information.enter(&list).field(FIELD)?;
126                while let Some(qualifier) = each.next_element().field(FIELD)? {
127                    if qualifier.tag() != Tag::constructed(universal::SEQUENCE) {
128                        return Err(at(ErrorCode::Encoding(
129                            structured_asn1::ErrorCode::UnexpectedTag,
130                        )));
131                    }
132                    let mut pair = each.enter(&qualifier).field(FIELD)?;
133                    let kind = pair.read_oid().field(FIELD)?;
134                    let value = pair
135                        .next_element()
136                        .field(FIELD)?
137                        .ok_or_else(|| at(ErrorCode::MissingField))?;
138                    if !pair.is_empty() {
139                        return Err(at(ErrorCode::TrailingData));
140                    }
141                    // The value is preserved for whoever knows the kind, and
142                    // a policy is what a trusted list points at when it names
143                    // the service that issued a certificate, so what is kept
144                    // has to be the encoding the document claims all the way
145                    // down. The outer header alone would leave a second
146                    // spelling of a value one level lower, arriving at the
147                    // same reader by the same route.
148                    crate::name::walk_der(&pair, &value, FIELD, crate::name::Strings::Checked)?;
149                    qualifiers
150                        .try_push(PolicyQualifier {
151                            kind,
152                            value: value.as_bytes(),
153                            at: value.span().start,
154                            outer: each.depth(),
155                        })
156                        .map_err(|reason| Error::resource(reason, FIELD, qualifier.span().start))?;
157                }
158                if qualifiers.is_empty() {
159                    return Err(at(ErrorCode::EmptySequence));
160                }
161            }
162
163            if !information.is_empty() {
164                return Err(at(ErrorCode::TrailingData));
165            }
166
167            // One policy written twice leaves no answer to which qualifiers
168            // belong to it. A set rather than a scan, so a long list costs
169            // the work its size says and not its square.
170            if identifiers
171                .try_insert(policy, ())
172                .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
173                .is_some()
174            {
175                return Err(at(ErrorCode::MalformedValue));
176            }
177
178            policies
179                .try_push(PolicyInformation {
180                    policy,
181                    qualifiers,
182                    at: entry.span().start,
183                })
184                .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
185        }
186
187        if policies.is_empty() {
188            return Err(fail(ErrorCode::EmptySequence));
189        }
190        Ok(Self { policies })
191    }
192}
193
194/// A policy of one domain treated as equivalent to a policy of another.
195#[derive(Debug, Clone, Copy)]
196pub struct PolicyMapping<'a> {
197    issuer_domain: Oid<'a>,
198    subject_domain: Oid<'a>,
199}
200
201impl<'a> PolicyMapping<'a> {
202    /// The policy as the issuer of this certificate names it.
203    #[must_use]
204    pub fn issuer_domain(self) -> Oid<'a> {
205        self.issuer_domain
206    }
207
208    /// The policy it stands for below this certificate.
209    #[must_use]
210    pub fn subject_domain(self) -> Oid<'a> {
211        self.subject_domain
212    }
213}
214
215/// The mappings of one certificate.
216#[derive(Debug)]
217pub struct PolicyMappings<'a> {
218    mappings: OwnedVec<PolicyMapping<'a>>,
219}
220
221impl<'a> PolicyMappings<'a> {
222    /// Every mapping, in order.
223    #[must_use]
224    pub fn mappings(&self) -> &[PolicyMapping<'a>] {
225        &self.mappings
226    }
227
228    /// Reads the value of a policy mappings extension.
229    pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
230        const FIELD: &str = "extensions.policyMappings";
231        let fail = |code| Error::new(code, FIELD, extension.span().start);
232
233        let mut inside = extension::sequence(extension, FIELD, execution)?;
234        let mut mappings = inside.memory().vector();
235        let mut seen = inside.memory().map::<(Oid<'a>, Oid<'a>), ()>();
236
237        while let Some(entry) = inside.next_element().field(FIELD)? {
238            let at = |code| Error::new(code, FIELD, entry.span().start);
239            if entry.tag() != Tag::constructed(universal::SEQUENCE) {
240                return Err(at(ErrorCode::Encoding(
241                    structured_asn1::ErrorCode::UnexpectedTag,
242                )));
243            }
244
245            let mut pair = inside.enter(&entry).field(FIELD)?;
246            let issuer_domain = pair.read_oid().field(FIELD)?;
247            let subject_domain = pair.read_oid().field(FIELD)?;
248            if !pair.is_empty() {
249                return Err(at(ErrorCode::TrailingData));
250            }
251
252            // Mapping the identifier that stands for any policy would make
253            // every policy below this certificate equivalent to one of them.
254            if issuer_domain == crate::oid::ANY_POLICY || subject_domain == crate::oid::ANY_POLICY {
255                return Err(at(ErrorCode::MalformedValue));
256            }
257
258            // A repeated issuer domain with distinct subjects is legitimate:
259            // path processing takes the set of subject policies equivalent to
260            // one issuer policy (RFC 5280 6.1.4 (b)(1)), and repeating the
261            // issuer is how that set is written. The exact pair written twice
262            // maps what one occurrence maps, and the list is a SEQUENCE OF
263            // with no uniqueness constraint: the repeat is folded away rather
264            // than refused, so a valid certificate is not blocked over it.
265            if seen
266                .try_insert((issuer_domain, subject_domain), ())
267                .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
268                .is_none()
269            {
270                mappings
271                    .try_push(PolicyMapping {
272                        issuer_domain,
273                        subject_domain,
274                    })
275                    .map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
276            }
277        }
278
279        if mappings.is_empty() {
280            return Err(fail(ErrorCode::EmptySequence));
281        }
282        Ok(Self { mappings })
283    }
284}
285
286impl CertificatePolicies<'_> {
287    /// Whether every qualifier was actually interpreted, for the gate that
288    /// decides what a critical extension blocked.
289    ///
290    /// RFC 5280 4.2.1.4 defines two qualifiers, the practice-statement
291    /// pointer and the user notice. A kind outside those, or a known kind
292    /// whose payload does not have its shape, is a critical qualifier this
293    /// run never understood. An error is a spent budget and only that.
294    pub(crate) fn understood(&self, parser: &Parser, execution: &Memory) -> Result<bool> {
295        for qualifier in self.policies.iter().flat_map(PolicyInformation::qualifiers) {
296            execution
297                .charge_work(1)
298                .map_err(|reason| Error::resource(reason, QUALIFIER_FIELD, qualifier.at))?;
299            if !qualifier_understood(parser, *qualifier, execution)? {
300                return Ok(false);
301            }
302        }
303        Ok(true)
304    }
305}
306
307/// The field the qualifier gates below report a spent budget in.
308const QUALIFIER_FIELD: &str = "extensions.certificatePolicies";
309
310fn qualifier_understood(
311    parser: &Parser,
312    qualifier: PolicyQualifier<'_>,
313    execution: &Memory,
314) -> Result<bool> {
315    // Re-parsed from where the value sits, so the one error that leaves
316    // here, a spent budget, carries a document offset as the error type
317    // promises. Malformation still answers no.
318    let Some(mut reader) = gate(
319        parser
320            .read_nested_in(
321                qualifier.value,
322                qualifier.at,
323                structured_asn1::Encoding::Der,
324                qualifier.outer,
325                execution,
326            )
327            .field(QUALIFIER_FIELD),
328    )?
329    else {
330        return Ok(false);
331    };
332    let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
333        return Ok(false);
334    };
335    if !reader.is_empty() {
336        return Ok(false);
337    }
338
339    Ok(if qualifier.kind == crate::oid::CPS_QUALIFIER {
340        // A pointer to the practice statement: one IA5String holding a URI
341        // (RFC 5280 4.2.1.4, CPSuri). Text that is not a URI points at
342        // nothing while wearing the qualifier. Read, never fetched. The
343        // scans below touch every content byte and CPSuri has no local size
344        // ceiling, so the length is charged to the shared work budget first.
345        reader
346            .charge(value.contents().len() as u64)
347            .field(QUALIFIER_FIELD)?;
348        value.tag() == Tag::primitive(universal::IA5_STRING)
349            && crate::name::is_ia5(value.contents())
350            && core::str::from_utf8(value.contents()).is_ok_and(crate::general_name::is_uri)
351    } else if qualifier.kind == crate::oid::USER_NOTICE_QUALIFIER {
352        user_notice_understood(&reader, &value)?
353    } else {
354        false
355    })
356}
357
358/// Whether a user notice has its shape: an optional notice reference, then
359/// optional explicit text. Both members are optional (RFC 5280 4.2.1.4):
360/// the empty sequence says nothing, but it is inside the schema, and
361/// refusing it would block an otherwise valid critical policy.
362fn user_notice_understood(
363    parent: &structured_asn1::Reader<'_>,
364    value: &structured_asn1::Element<'_>,
365) -> Result<bool> {
366    if value.tag() != Tag::constructed(universal::SEQUENCE) {
367        return Ok(false);
368    }
369    let Some(mut inside) = gate(parent.enter(value).field(QUALIFIER_FIELD))? else {
370        return Ok(false);
371    };
372    let Some(mut next) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
373        return Ok(false);
374    };
375    if let Some(reference) = next
376        && reference.tag() == Tag::constructed(universal::SEQUENCE)
377    {
378        if !notice_reference_understood(&inside, &reference)? {
379            return Ok(false);
380        }
381        let Some(following) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
382            return Ok(false);
383        };
384        next = following;
385    }
386    if let Some(text) = next
387        && !is_display_text(&inside, &text)?
388    {
389        return Ok(false);
390    }
391    Ok(inside.is_empty())
392}
393
394/// The reference half of a user notice: the organization's display text,
395/// then a non-empty sequence of notice numbers.
396fn notice_reference_understood(
397    parent: &structured_asn1::Reader<'_>,
398    reference: &structured_asn1::Element<'_>,
399) -> Result<bool> {
400    let Some(mut inside) = gate(parent.enter(reference).field(QUALIFIER_FIELD))? else {
401        return Ok(false);
402    };
403    let Some(Some(organization)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
404        return Ok(false);
405    };
406    if !is_display_text(&inside, &organization)? {
407        return Ok(false);
408    }
409    let Some(Some(numbers)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
410        return Ok(false);
411    };
412    if numbers.tag() != Tag::constructed(universal::SEQUENCE) || !inside.is_empty() {
413        return Ok(false);
414    }
415    let Some(mut each) = gate(inside.enter(&numbers).field(QUALIFIER_FIELD))? else {
416        return Ok(false);
417    };
418    loop {
419        match gate(each.next_element().field(QUALIFIER_FIELD))? {
420            Some(Some(number)) => {
421                // The schema says SEQUENCE OF INTEGER (RFC 5280 4.2.1.4);
422                // the decoding helper is tag-agnostic for implicitly tagged
423                // fields, so the tag is required here.
424                if number.tag() != Tag::primitive(universal::INTEGER)
425                    || gate(each.integer(&number).field(QUALIFIER_FIELD))?.is_none()
426                {
427                    return Ok(false);
428                }
429            }
430            // noticeNumbers is SEQUENCE OF INTEGER with no minimum size
431            // (RFC 5280 4.2.1.4): reaching the end is understood even when
432            // nothing was inside, the way an empty UserNotice is.
433            Some(None) => return Ok(true),
434            None => return Ok(false),
435        }
436    }
437}
438
439/// Whether an element is one of the display text types of RFC 5280 4.2.1.4,
440/// decoding under its declared type to between one and two hundred
441/// characters, the SIZE (1..200) the schema puts on every choice. Bytes that
442/// do not decode wear the tag without being the type.
443fn is_display_text(
444    reader: &structured_asn1::Reader<'_>,
445    element: &structured_asn1::Element<'_>,
446) -> Result<bool> {
447    let tag = element.tag();
448    let contents = element.contents();
449    // The ceiling is checked on the encoded bytes before any text is
450    // materialized: no choice here spends more than four bytes per
451    // character, so contents past four times the character bound can never
452    // fit it and are refused without walking the value.
453    if contents.len() > 200 * 4 {
454        return Ok(false);
455    }
456    // The per-value ceiling does not bound the aggregate: a policy may carry
457    // as many notices as the document has room for, so each scan is charged
458    // to the shared work budget before it runs.
459    reader
460        .charge(contents.len() as u64)
461        .field(QUALIFIER_FIELD)?;
462    let characters = if tag == Tag::primitive(universal::IA5_STRING) {
463        // The 128-character IRV (X.680, 41.4).
464        contents.iter().all(u8::is_ascii).then_some(contents.len())
465    } else if tag == Tag::primitive(universal::UTF8_STRING) {
466        core::str::from_utf8(contents)
467            .ok()
468            .map(|text| text.chars().count())
469    } else if tag == Tag::primitive(26) {
470        // VisibleString, whose number this schema layer names itself since
471        // nothing below it needs it: the printing characters and space.
472        contents
473            .iter()
474            .all(|byte| matches!(byte, 0x20..=0x7e))
475            .then_some(contents.len())
476    } else if tag == Tag::primitive(30) {
477        // BMPString, two bytes a character, so the size constraint counts
478        // characters rather than bytes. Counted rather than decoded: the
479        // gate asks how many characters there are, and building the text to
480        // answer that would allocate a copy of the value.
481        crate::name::wide_char_count(contents, 2)
482    } else {
483        return Ok(false);
484    };
485    Ok(characters.is_some_and(|count| (1..=200).contains(&count)))
486}
487
488#[cfg(test)]
489mod tests;