Skip to main content

structured_pkix/
access.rs

1//! Where things related to a certificate are published.
2//!
3//! Every location here is text. Nothing in this crate resolves one, and
4//! nothing above it may resolve one without a person choosing to: a parser
5//! that fetched a location a document named would ask a server chosen by
6//! whoever wrote the document to confirm what the document says, and would
7//! tell that server which document is being looked at.
8
9use crate::error::{Error, ErrorCode, Located, Result};
10use crate::extension::{self, Extension};
11use crate::general_name::{self, GeneralName};
12use crate::name::{self, RelativeName};
13use crate::oid;
14use structured_asn1::memory::{Memory, OwnedVec};
15use structured_asn1::{BitString, Element, Oid, Reader, Tag, universal};
16
17/// One published service or document, and where it is.
18#[derive(Debug)]
19pub struct AccessDescription<'a> {
20    method: Oid<'a>,
21    location: GeneralName<'a>,
22    at: u64,
23}
24
25impl<'a> AccessDescription<'a> {
26    /// What sits at the location.
27    #[must_use]
28    pub fn method(&self) -> Oid<'a> {
29        self.method
30    }
31
32    /// Where it is, unresolved.
33    #[must_use]
34    pub fn location(&self) -> &GeneralName<'a> {
35        &self.location
36    }
37}
38
39/// The access descriptions of one extension, whether about the issuer or about
40/// the subject.
41#[derive(Debug)]
42pub struct InfoAccess<'a> {
43    descriptions: OwnedVec<AccessDescription<'a>>,
44    field: &'static str,
45}
46
47impl<'a> InfoAccess<'a> {
48    /// Every description, in order.
49    #[must_use]
50    pub fn descriptions(&self) -> &[AccessDescription<'a>] {
51        &self.descriptions
52    }
53
54    /// The locations published for one method. Each comparison is admitted
55    /// in the current execution domain; an error is yielded once and ends
56    /// the iterator. Returned locations borrow this collection.
57    pub fn locations_for<'n, 'q>(
58        &'n self,
59        method: Oid<'q>,
60        execution: &'q Memory,
61    ) -> impl Iterator<Item = Result<&'n GeneralName<'a>>> + 'q
62    where
63        'n: 'q,
64    {
65        let mut descriptions = self.descriptions.iter();
66        let mut failed = false;
67        core::iter::from_fn(move || {
68            if failed {
69                return None;
70            }
71            for description in descriptions.by_ref() {
72                let work = 1 + description
73                    .method
74                    .as_bytes()
75                    .len()
76                    .max(method.as_bytes().len()) as u64;
77                if let Err(reason) = execution.charge_work(work) {
78                    failed = true;
79                    return Some(Err(Error::resource(reason, self.field, description.at)));
80                }
81                if description.method == method {
82                    return Some(Ok(description.location()));
83                }
84            }
85            None
86        })
87    }
88
89    /// Reads the value of an authority or subject information access
90    /// extension, which have the same shape.
91    pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
92        let field = if extension.kind() == oid::SUBJECT_INFO_ACCESS {
93            "extensions.subjectInfoAccess"
94        } else {
95            "extensions.authorityInfoAccess"
96        };
97
98        let mut inside = extension::sequence(extension, field, execution)?;
99        let mut descriptions = inside.memory().vector();
100
101        while let Some(entry) = inside.next_element().field(field)? {
102            let at = |code| Error::new(code, field, entry.span().start);
103            if entry.tag() != Tag::constructed(universal::SEQUENCE) {
104                return Err(at(ErrorCode::Encoding(
105                    structured_asn1::ErrorCode::UnexpectedTag,
106                )));
107            }
108
109            let mut pair = inside.enter(&entry).field(field)?;
110            let method = pair.read_oid().field(field)?;
111            let location = pair
112                .next_element()
113                .field(field)?
114                .ok_or_else(|| at(ErrorCode::MissingField))?;
115            let location = general_name::read(&pair, &location, field)?;
116            if !pair.is_empty() {
117                return Err(at(ErrorCode::TrailingData));
118            }
119
120            descriptions
121                .try_push(AccessDescription {
122                    method,
123                    location,
124                    at: entry.span().start,
125                })
126                .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
127        }
128
129        if descriptions.is_empty() {
130            return Err(Error::new(
131                ErrorCode::EmptySequence,
132                field,
133                extension.span().start,
134            ));
135        }
136        Ok(Self {
137            descriptions,
138            field,
139        })
140    }
141}
142
143/// One place a revocation list covering this certificate is published.
144#[derive(Debug)]
145pub struct DistributionPoint<'a> {
146    full_name: OwnedVec<GeneralName<'a>>,
147    name_relative_to_issuer: Option<RelativeName<'a>>,
148    reasons: Option<BitString<'a>>,
149    issuer: OwnedVec<GeneralName<'a>>,
150}
151
152impl<'a> DistributionPoint<'a> {
153    /// Where the list is, where the point names it outright.
154    #[must_use]
155    pub fn full_name(&self) -> &[GeneralName<'a>] {
156        &self.full_name
157    }
158
159    /// Where the list is, where the point names it relative to whoever issues
160    /// it rather than outright.
161    #[must_use]
162    pub fn name_relative_to_issuer(&self) -> Option<&RelativeName<'a>> {
163        self.name_relative_to_issuer.as_ref()
164    }
165
166    /// Which reasons for revocation this list covers, where the point says.
167    /// Absent means all of them, and a point that names a few is not a list a
168    /// complete answer can be built from on its own.
169    #[must_use]
170    pub fn reasons(&self) -> Option<BitString<'a>> {
171        self.reasons
172    }
173
174    /// Who issues the list, where that is not the issuer of the certificate.
175    #[must_use]
176    pub fn issuer(&self) -> &[GeneralName<'a>] {
177        &self.issuer
178    }
179}
180
181/// The places revocation lists covering this certificate are published.
182#[derive(Debug)]
183pub struct DistributionPoints<'a> {
184    points: OwnedVec<DistributionPoint<'a>>,
185}
186
187impl<'a> DistributionPoints<'a> {
188    /// Every point, in order.
189    #[must_use]
190    pub fn points(&self) -> &[DistributionPoint<'a>] {
191        &self.points
192    }
193
194    /// Reads the value of a distribution points extension, which the freshest
195    /// revocation list is published in the same shape as.
196    pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
197        let field = if extension.kind() == oid::FRESHEST_CRL {
198            "extensions.freshestCRL"
199        } else {
200            "extensions.cRLDistributionPoints"
201        };
202
203        let mut inside = extension::sequence(extension, field, execution)?;
204        let mut points = inside.memory().vector();
205        while let Some(entry) = inside.next_element().field(field)? {
206            points
207                .try_push(read_point(&inside, &entry, field)?)
208                .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
209        }
210
211        if points.is_empty() {
212            return Err(Error::new(
213                ErrorCode::EmptySequence,
214                field,
215                extension.span().start,
216            ));
217        }
218        Ok(Self { points })
219    }
220}
221
222fn read_point<'a>(
223    parent: &Reader<'a>,
224    element: &Element<'a>,
225    field: &'static str,
226) -> Result<DistributionPoint<'a>> {
227    let fail = |code| Error::new(code, field, element.span().start);
228
229    if element.tag() != Tag::constructed(universal::SEQUENCE) {
230        return Err(fail(ErrorCode::Encoding(
231            structured_asn1::ErrorCode::UnexpectedTag,
232        )));
233    }
234
235    let mut inside = parent.enter(element).field(field)?;
236    let mut point = DistributionPoint {
237        full_name: parent.memory().vector(),
238        name_relative_to_issuer: None,
239        reasons: None,
240        issuer: parent.memory().vector(),
241    };
242
243    // The three fields are written in the order of their numbers, once each:
244    // a tag at or below the last one read is not this schema.
245    let mut last_number = None;
246    while let Some(entry) = inside.next_element().field(field)? {
247        let tag = extension::context_of(entry.tag());
248        match tag {
249            Some((0, true)) if last_number.is_none() => {
250                read_point_name(&inside, &entry, field, &mut point)?;
251            }
252            Some((1, false)) if last_number < Some(1) => {
253                let reasons = inside.bit_string(&entry).field(field)?;
254                // The zero walk below touches every content byte, so it is
255                // charged to the shared work budget first.
256                inside
257                    .charge(reasons.as_bytes().len() as u64)
258                    .field(field)?;
259                // Present while naming no reason, the field covers none while
260                // claiming to narrow the list: a reader would take the point
261                // for a partial list and a full one at once. Absent is how
262                // "all reasons" is said. Written as zero bits, as zero bytes,
263                // or with only bit zero, which the schema names "unused" and
264                // gives no meaning (RFC 5280 4.2.1.13), it says the same
265                // nothing: a scope whose only member means nothing has no
266                // members.
267                if !(1..=8).any(|bit| reasons.bit(bit)) {
268                    return Err(Error::new(
269                        ErrorCode::MalformedValue,
270                        field,
271                        entry.span().start,
272                    ));
273                }
274                // A named bit list under DER: X.690 11.2.2 trims trailing
275                // zero bits, and RFC 5280 4.2.1.13 names bits 0 through 8, so
276                // a longer string carries a bit with no name. unused(0) is
277                // deliberately readable: it is a named bit of the schema, and
278                // RFC 5280 gives it no meaning but no prohibition either;
279                // what a set bit means is the revocation evidence's question.
280                if inside.encoding().is_der()
281                    && (reasons.len() > 9
282                        || (!reasons.is_empty() && !reasons.bit(reasons.len() - 1)))
283                {
284                    return Err(Error::new(
285                        ErrorCode::MalformedValue,
286                        field,
287                        entry.span().start,
288                    ));
289                }
290                point.reasons = Some(reasons);
291            }
292            Some((2, true)) if last_number < Some(2) => {
293                let issuer = general_name::read_all(&inside, &entry, field)?;
294                // The profile is explicit that this is exclusive: a present
295                // cRLIssuer "MUST only contain the distinguished name (DN)
296                // from the issuer field of the CRL" (RFC 5280 4.2.1.13), so
297                // every member is required to be a directory name, not
298                // merely one of them. Any other form cannot be matched
299                // against a list's issuer field anyway. Several directory
300                // names stay permitted: the same section contemplates them
301                // by forbidding only the relative alternative "when
302                // cRLIssuer contains more than one distinguished name", the
303                // check below, because the relative fragment appends to
304                // exactly one name and with two there is no answer to which
305                // list the point names.
306                if !issuer
307                    .iter()
308                    .all(|name| matches!(name, general_name::GeneralName::Directory(_)))
309                    || (point.name_relative_to_issuer.is_some() && issuer.len() > 1)
310                {
311                    return Err(Error::new(
312                        ErrorCode::MalformedValue,
313                        field,
314                        entry.span().start,
315                    ));
316                }
317                point.issuer = issuer;
318            }
319            _ => {
320                return Err(Error::new(
321                    ErrorCode::UnexpectedField,
322                    field,
323                    entry.span().start,
324                ));
325            }
326        }
327        last_number = tag.map(|(number, _)| number);
328    }
329
330    // A point must say where the list is or who issues it. With neither, it
331    // names no source a list could ever be resolved from, and reporting it as
332    // parsed would count a malformed extension as a working one. A relative
333    // name WITHOUT a cRLIssuer stays valid on its own: the profile appends
334    // the fragment to the certificate issuer's distinguished name when the
335    // issuer field is absent (RFC 5280 4.2.1.13), so the point still names a
336    // resolvable source. Reasons beside a cRLIssuer with no point name stay
337    // valid too: the only shape rule the profile states is that a point
338    // "MUST NOT consist of only the reasons field; either distributionPoint
339    // or cRLIssuer MUST be present" (RFC 5280 4.2.1.13), and no sentence
340    // requires a point name beside reasons; the list is then located
341    // through the issuer's directory entry, its reason scope intact.
342    if point.full_name.is_empty()
343        && point.name_relative_to_issuer.is_none()
344        && point.issuer.is_empty()
345    {
346        return Err(fail(ErrorCode::MalformedValue));
347    }
348
349    Ok(point)
350}
351
352/// Reads the name of a point, which is a choice between naming the list
353/// outright and naming it under whoever issues it. A choice cannot take the
354/// tag of its position, so the field wraps it.
355#[derive(Debug)]
356pub(crate) enum PointName<'a> {
357    Full(OwnedVec<GeneralName<'a>>),
358    Relative(RelativeName<'a>),
359}
360
361/// Shared `DistributionPointName` grammar for certificate DPs and CRL IDPs.
362pub(crate) fn read_distribution_name<'a>(
363    parent: &Reader<'a>,
364    element: &Element<'a>,
365    field: &'static str,
366) -> Result<PointName<'a>> {
367    let fail = |code| Error::new(code, field, element.span().start);
368    let mut inside = parent.enter(element).field(field)?;
369    let chosen = inside
370        .next_element()
371        .field(field)?
372        .ok_or_else(|| fail(ErrorCode::MissingField))?;
373    let name = match extension::context_of(chosen.tag()) {
374        Some((0, true)) => PointName::Full(general_name::read_locations(&inside, &chosen, field)?),
375        Some((1, true)) => {
376            let relative = name::read_relative(&inside, &chosen, field)?;
377            for attribute in relative.attributes() {
378                if !name::well_formed_in(attribute, &inside, field)? {
379                    return Err(fail(ErrorCode::MalformedValue));
380                }
381            }
382            PointName::Relative(relative)
383        }
384        _ => {
385            return Err(fail(ErrorCode::Encoding(
386                structured_asn1::ErrorCode::UnexpectedTag,
387            )));
388        }
389    };
390    if !inside.is_empty() {
391        return Err(fail(ErrorCode::TrailingData));
392    }
393    Ok(name)
394}
395
396fn read_point_name<'a>(
397    parent: &Reader<'a>,
398    element: &Element<'a>,
399    field: &'static str,
400    point: &mut DistributionPoint<'a>,
401) -> Result<()> {
402    match read_distribution_name(parent, element, field)? {
403        PointName::Full(names) => point.full_name = names,
404        PointName::Relative(name) => point.name_relative_to_issuer = Some(name),
405    }
406    Ok(())
407}
408
409#[cfg(test)]
410mod tests;