Skip to main content

structured_pkix/
crl.rs

1//! Borrowed certificate revocation lists and their original signed ranges.
2//!
3//! Reading preserves evidence; it does not authenticate the issuer or establish
4//! a certificate's revocation status. Deferred extension decoding uses the
5//! caller's current execution account and the producing parser's ceilings.
6
7use crate::algorithm::{self, AlgorithmIdentifier};
8use crate::error::{Error, ErrorCode, Located, Result};
9use crate::extension::{self, Extensions};
10use crate::name::{self, Name};
11use crate::path::{Verified, Verify, WorkingKey};
12use crate::time::{self, Time};
13use structured_asn1::memory::{Memory, OwnedVec};
14use structured_asn1::{BitString, Element, Integer, Parser, Reader, Span, Tag, universal};
15
16mod scope;
17pub use scope::{CertificateKind, IssuingDistributionPoint, Reasons, ScopeFailure, ScopeOutcome};
18mod entry;
19pub use entry::EntryLookup;
20pub use entry::Reason;
21pub(crate) use entry::reason_element;
22mod delta;
23pub use delta::{
24    BoundDelta, CombinedEntry, DeltaBinding, DeltaFailure, EntryOrigin, MissingDeltaField,
25};
26mod freshness;
27pub use freshness::Freshness;
28pub(crate) use freshness::update_window;
29mod uri;
30
31mod evidence;
32pub use evidence::{
33    AbsenceEvidence, CompleteEvidence, CompleteFacts, Coverage, CoverageAdmission, DeltaEvidence,
34    DeltaFacts, Evaluation, EvidenceSource, SourceOrigin,
35};
36
37/// The CRL schema version, independently of the issuer certificate's version.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Version {
40    /// Original schema, with neither CRL nor entry extensions.
41    V1,
42    /// Extension-bearing schema; its encoded version is INTEGER 1.
43    V2,
44}
45
46/// Why a supplied signing authority did not authenticate this CRL.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum AuthenticationFailure {
49    /// The effective key's original SPKI is not the issuer certificate's key.
50    KeyBinding,
51    /// The CRL issuer does not match the supplied issuer's subject.
52    IssuerName,
53    /// The issuer's certificate does not permit signing revocation lists.
54    KeyUsage,
55    /// The verifier completed and rejected the signature.
56    Signature,
57}
58
59/// An authenticated signature and its exact source authority. This does not
60/// claim freshness, distribution-point applicability or non-revocation.
61#[derive(Debug, Clone, Copy)]
62pub struct Authenticated<'r, 'a> {
63    crl: &'r CertificateList<'a>,
64    issuer: &'r crate::Certificate<'a>,
65    key: WorkingKey<'a>,
66}
67
68impl<'r, 'a> Authenticated<'r, 'a> {
69    /// Original CRL whose signed bytes the callback authenticated.
70    #[must_use]
71    pub fn crl(self) -> &'r CertificateList<'a> {
72        self.crl
73    }
74    /// Certificate supplied as the already authenticated signing authority.
75    #[must_use]
76    pub fn issuer(self) -> &'r crate::Certificate<'a> {
77        self.issuer
78    }
79    /// Effective key, bound to this issuer's original SPKI.
80    #[must_use]
81    pub fn key(self) -> WorkingKey<'a> {
82        self.key
83    }
84}
85
86/// Completed authentication result, separately from processing and callback
87/// failures. Unsupported verification is never a rejected signature.
88#[derive(Debug)]
89pub enum Authentication<'r, 'a> {
90    /// Signature and issuer binding were established.
91    Authenticated(Authenticated<'r, 'a>),
92    /// A completed binding or verification check rejected the authority.
93    Rejected(AuthenticationFailure),
94    /// The callback does not support this signature or effective key.
95    Unsupported,
96}
97
98/// One CRL entry. An indirect CRL's effective issuer is determined by the
99/// certificateIssuer extension and preceding entries, not by this serial alone.
100#[derive(Debug)]
101pub struct RevokedCertificate<'a> {
102    serial: Integer<'a>,
103    revocation_date: Time,
104    revocation_date_offset: u64,
105    extensions: Extensions<'a>,
106    span: Span,
107}
108
109impl<'a> RevokedCertificate<'a> {
110    /// Serial exactly as encoded, without narrowing to a machine integer.
111    #[must_use]
112    pub fn serial(&self) -> Integer<'a> {
113        self.serial
114    }
115
116    /// The issuer's stated revocation date, retaining its UTC label and form.
117    #[must_use]
118    pub fn revocation_date(&self) -> Time {
119        self.revocation_date
120    }
121
122    /// Establish the recorded date's relation to an explicit UTC reading.
123    /// Caller decides how that fact affects historical/current acceptance;
124    /// this never silently makes a future-dated entry an absence statement.
125    pub fn time_relation(
126        &self,
127        now: crate::temporal::Reading,
128        utc: &crate::temporal::LeapTable<'_>,
129        execution: &Memory,
130    ) -> Result<crate::time::Relation> {
131        crate::time::relation_in(
132            self.revocation_date.instant(),
133            now,
134            utc,
135            execution,
136            (
137                "certificateList.revokedCertificates.revocationDate",
138                self.revocation_date_offset,
139            ),
140        )
141    }
142
143    /// Entry extensions, including unknown critical demands.
144    #[must_use]
145    pub fn extensions(&self) -> &Extensions<'a> {
146        &self.extensions
147    }
148
149    /// Original document location of this entry.
150    #[must_use]
151    pub fn span(&self) -> Span {
152        self.span
153    }
154}
155
156/// The signed CRL fields. Collection capacity is owned and accounted; input,
157/// identifiers, names and extension values remain borrowed.
158#[derive(Debug)]
159pub struct TbsCertList<'a> {
160    version: Version,
161    signature: AlgorithmIdentifier<'a>,
162    issuer: Name<'a>,
163    this_update: Time,
164    next_update: Option<Time>,
165    revoked: OwnedVec<RevokedCertificate<'a>>,
166    extensions: Extensions<'a>,
167    bytes: &'a [u8],
168    span: Span,
169}
170
171impl<'a> TbsCertList<'a> {
172    /// Which CRL schema was used.
173    #[must_use]
174    pub fn version(&self) -> Version {
175        self.version
176    }
177    /// Algorithm inside the authenticated range.
178    #[must_use]
179    pub fn signature(&self) -> AlgorithmIdentifier<'a> {
180        self.signature
181    }
182    /// Nonempty CRL issuer distinguished name.
183    #[must_use]
184    pub fn issuer(&self) -> &Name<'a> {
185        &self.issuer
186    }
187    /// Issue time, without consulting a clock or UTC correction service.
188    #[must_use]
189    pub fn this_update(&self) -> Time {
190        self.this_update
191    }
192    /// Advertised next issue time. Its absence is preserved: RFC 5280 §5.1.2.5
193    /// leaves client behavior for this case unspecified.
194    #[must_use]
195    pub fn next_update(&self) -> Option<Time> {
196        self.next_update
197    }
198    /// Entries in wire order, required for indirect issuer inheritance.
199    #[must_use]
200    pub fn revoked_certificates(&self) -> &[RevokedCertificate<'a>] {
201        &self.revoked
202    }
203    /// CRL extensions, kept independently of recognition or authentication.
204    #[must_use]
205    pub fn extensions(&self) -> &Extensions<'a> {
206        &self.extensions
207    }
208    /// Exact DER `TBSCertList`, including its original tag and length.
209    #[must_use]
210    pub fn as_bytes(&self) -> &'a [u8] {
211        self.bytes
212    }
213    /// Original document location of `TBSCertList`.
214    #[must_use]
215    pub fn span(&self) -> Span {
216        self.span
217    }
218}
219
220/// A DER `CertificateList`, with no fetching or implicit trust decisions.
221#[derive(Debug)]
222pub struct CertificateList<'a> {
223    tbs: TbsCertList<'a>,
224    signature_algorithm: AlgorithmIdentifier<'a>,
225    signature: BitString<'a>,
226    bytes: &'a [u8],
227    span: Span,
228}
229
230impl<'a> CertificateList<'a> {
231    /// Authenticate with an already authenticated issuer and its effective
232    /// working key. The caller establishes the issuer path and its trust-anchor
233    /// provenance; this operation binds that certificate to the actual key and
234    /// CRL. It performs no path search, fetching, clock access or status verdict.
235    ///
236    /// The verifier must account its own transitive work and allocations in
237    /// `execution`. Passing this domain is not evidence that an opaque provider
238    /// backend enforces the accounting contract.
239    pub fn authenticate<'r, V: Verify>(
240        &'r self,
241        issuer: &'r crate::Certificate<'a>,
242        key: WorkingKey<'a>,
243        verifier: &V,
244        execution: &Memory,
245    ) -> crate::error::CallbackResult<Authentication<'r, 'a>, V::Error> {
246        use crate::error::CallbackError;
247        let encoded_key = key.key().as_bytes();
248        let issuer_key = issuer.tbs().public_key().as_bytes();
249        let work = (encoded_key.len().max(issuer_key.len()) as u64)
250            .checked_mul(2)
251            .and_then(|n| n.checked_add(1))
252            .ok_or_else(|| {
253                Error::resource(
254                    structured_asn1::memory::MemoryError::WorkExceeded,
255                    FIELD,
256                    self.span.start,
257                )
258            })?;
259        execution
260            .charge_work(work)
261            .map_err(|reason| Error::resource(reason, FIELD, self.span.start))?;
262        if encoded_key != issuer_key {
263            return Ok(Authentication::Rejected(AuthenticationFailure::KeyBinding));
264        }
265        if !self.tbs.issuer.matches(issuer.tbs().subject(), execution)? {
266            return Ok(Authentication::Rejected(AuthenticationFailure::IssuerName));
267        }
268        let usage = issuer.tbs().extensions().key_usage(execution)?;
269        // RFC 10007 §4: version 3 CRL issuer certificates require keyUsage
270        // with cRLSign. Version 1/2 certificates are exempt from its presence;
271        // an existing keyUsage still constrains the operation (RFC 5280 §6.3.3).
272        // https://www.rfc-editor.org/rfc/rfc10007#section-4
273        if usage.is_some_and(|usage| !usage.crl_sign())
274            || (usage.is_none() && issuer.tbs().version() == crate::Version::V3)
275        {
276            return Ok(Authentication::Rejected(AuthenticationFailure::KeyUsage));
277        }
278        match verifier
279            .verify(
280                &self.tbs.signature,
281                key,
282                self.signed_bytes(),
283                self.signature,
284                execution,
285            )
286            .map_err(CallbackError::Callback)?
287        {
288            Verified::Yes => Ok(Authentication::Authenticated(Authenticated {
289                crl: self,
290                issuer,
291                key,
292            })),
293            Verified::No => Ok(Authentication::Rejected(AuthenticationFailure::Signature)),
294            Verified::Unsupported => Ok(Authentication::Unsupported),
295        }
296    }
297
298    /// Read exactly one CRL, retaining original offsets and the shared parser.
299    pub fn read(parser: &'a Parser, bytes: &'a [u8], base: u64) -> Result<Self> {
300        let mut reader = parser.read(bytes, base).field(FIELD)?;
301        let element = required(&mut reader, FIELD, base)?;
302        let crl = Self::read_element(&reader, &element)?;
303        if !reader.is_empty() {
304            return Err(Error::new(
305                ErrorCode::TrailingData,
306                FIELD,
307                element.span().end(),
308            ));
309        }
310        Ok(crl)
311    }
312
313    /// Read an embedded CRL through its producing Reader. A BER envelope does
314    /// not relax the CRL's DER spelling or reset depth and work limits.
315    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
316        // RFC 5280 §5.1 and Appendix A.1 require the CertificateList DER model.
317        // https://www.rfc-editor.org/rfc/rfc5280#section-5.1
318        let mut outer = parent
319            .reread(element, structured_asn1::Encoding::Der)
320            .field(FIELD)?;
321        let root = outer
322            .expect(Tag::constructed(universal::SEQUENCE))
323            .field(FIELD)?;
324        let mut inside = outer.enter(&root).field(FIELD)?;
325        let tbs_element = required(&mut inside, TBS, root.span().start)?;
326        let tbs = read_tbs(&inside, &tbs_element)?;
327        let algorithm_element = required(&mut inside, ALGORITHM, root.span().start)?;
328        let signature_algorithm = algorithm::read(&inside, &algorithm_element, ALGORITHM)?;
329        let signature = inside.read_bit_string().field(SIGNATURE)?;
330        end(&inside, FIELD, root.span().start)?;
331        // RFC 5280 §§5.1.1.2, 5.1.2.2: the unsigned algorithm must agree with
332        // the signed identifier. Preserve both execution and parser ceilings.
333        if !signature_algorithm.matches_in(&tbs.signature, &inside)? {
334            return Err(Error::new(
335                ErrorCode::AlgorithmMismatch,
336                ALGORITHM,
337                algorithm_element.span().start,
338            ));
339        }
340        Ok(Self {
341            tbs,
342            signature_algorithm,
343            signature,
344            bytes: root.as_bytes(),
345            span: root.span(),
346        })
347    }
348
349    /// Signed fields and retained revocation entries.
350    #[must_use]
351    pub fn tbs(&self) -> &TbsCertList<'a> {
352        &self.tbs
353    }
354    /// Outer algorithm, already required to match the signed algorithm.
355    #[must_use]
356    pub fn signature_algorithm(&self) -> AlgorithmIdentifier<'a> {
357        self.signature_algorithm
358    }
359    /// Original signature BIT STRING.
360    #[must_use]
361    pub fn signature(&self) -> BitString<'a> {
362        self.signature
363    }
364    /// Exact `TBSCertList` range to supply to a signature verifier.
365    #[must_use]
366    pub fn signed_bytes(&self) -> &'a [u8] {
367        self.tbs.bytes
368    }
369    /// Whole original `CertificateList` encoding.
370    #[must_use]
371    pub fn as_bytes(&self) -> &'a [u8] {
372        self.bytes
373    }
374    /// Whole CRL location in its enclosing document.
375    #[must_use]
376    pub fn span(&self) -> Span {
377        self.span
378    }
379}
380
381const FIELD: &str = "certificateList";
382const TBS: &str = "certificateList.tbsCertList";
383const ALGORITHM: &str = "certificateList.signatureAlgorithm";
384const SIGNATURE: &str = "certificateList.signature";
385const ISSUER: &str = "certificateList.tbsCertList.issuer";
386const THIS_UPDATE: &str = "certificateList.tbsCertList.thisUpdate";
387const NEXT_UPDATE: &str = "certificateList.tbsCertList.nextUpdate";
388const ENTRIES: &str = "certificateList.tbsCertList.revokedCertificates";
389const EXTENSIONS: &str = "certificateList.tbsCertList.crlExtensions";
390
391fn required<'a>(reader: &mut Reader<'a>, field: &'static str, at: u64) -> Result<Element<'a>> {
392    reader
393        .next_element()
394        .field(field)?
395        .ok_or_else(|| Error::new(ErrorCode::MissingField, field, at))
396}
397
398fn end(reader: &Reader<'_>, field: &'static str, at: u64) -> Result<()> {
399    if reader.is_empty() {
400        return Ok(());
401    }
402    Err(Error::new(ErrorCode::TrailingData, field, at))
403}
404
405fn is_time(element: &Element<'_>) -> bool {
406    element.tag() == Tag::primitive(universal::UTC_TIME)
407        || element.tag() == Tag::primitive(universal::GENERALIZED_TIME)
408}
409
410fn read_tbs<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<TbsCertList<'a>> {
411    if element.tag() != Tag::constructed(universal::SEQUENCE) {
412        return Err(Error::new(
413            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
414            TBS,
415            element.span().start,
416        ));
417    }
418    let mut inside = parent.enter(element).field(TBS)?;
419    let mut next = required(&mut inside, TBS, element.span().start)?;
420    let version = if next.tag() == Tag::primitive(universal::INTEGER) {
421        // as_u64 refuses wider inputs before scanning, and converts at most
422        // eight magnitude bytes. Admit that bounded conversion before it runs.
423        inside.charge(9).field(TBS)?;
424        if inside.integer(&next).field(TBS)?.as_u64() != Some(1) {
425            return Err(Error::new(
426                ErrorCode::UnsupportedVersion,
427                TBS,
428                next.span().start,
429            ));
430        }
431        next = required(&mut inside, TBS, element.span().start)?;
432        Version::V2
433    } else {
434        Version::V1
435    };
436    let signature = algorithm::read(&inside, &next, TBS)?;
437    let issuer_element = required(&mut inside, ISSUER, element.span().start)?;
438    let issuer = name::read(&inside, &issuer_element, ISSUER)?;
439    // RFC 5280 §5.1.2.3: issuer MUST contain a nonempty distinguished name.
440    if issuer.is_empty() {
441        return Err(Error::new(
442            ErrorCode::EmptySequence,
443            ISSUER,
444            issuer_element.span().start,
445        ));
446    }
447    let this = required(&mut inside, THIS_UPDATE, element.span().start)?;
448    let this_update = time::read_in(&inside, &this, THIS_UPDATE)?;
449    let mut next = inside.next_element().field(TBS)?;
450    let next_update = match next {
451        Some(value) if is_time(&value) => {
452            let time = time::read_in(&inside, &value, NEXT_UPDATE)?;
453            next = inside.next_element().field(TBS)?;
454            Some(time)
455        }
456        _ => None,
457    };
458    let mut revoked = inside.memory().vector();
459    if let Some(sequence) =
460        next.filter(|value| value.tag() == Tag::constructed(universal::SEQUENCE))
461    {
462        let mut entries = inside.enter(&sequence).field(ENTRIES)?;
463        while let Some(entry) = entries.next_element().field(ENTRIES)? {
464            let value = read_entry(&entries, &entry, version)?;
465            revoked
466                .try_push(value)
467                .map_err(|reason| Error::resource(reason, ENTRIES, entry.span().start))?;
468        }
469        // RFC 5280 §5.1.2.6: no revoked certificates means the list is absent.
470        if revoked.is_empty() {
471            return Err(Error::new(
472                ErrorCode::EmptySequence,
473                ENTRIES,
474                sequence.span().start,
475            ));
476        }
477        next = inside.next_element().field(TBS)?;
478    }
479    let mut extensions = Extensions::empty(inside.memory());
480    if let Some(tagged) = next {
481        if tagged.tag() != Tag::context(true, 0) || version != Version::V2 {
482            return Err(Error::new(
483                ErrorCode::UnexpectedField,
484                EXTENSIONS,
485                tagged.span().start,
486            ));
487        }
488        let mut wrapper = inside.enter(&tagged).field(EXTENSIONS)?;
489        let sequence = required(&mut wrapper, EXTENSIONS, tagged.span().start)?;
490        extensions = extension::read(&wrapper, &sequence, EXTENSIONS)?;
491        end(&wrapper, EXTENSIONS, tagged.span().start)?;
492    }
493    end(&inside, TBS, element.span().start)?;
494    Ok(TbsCertList {
495        version,
496        signature,
497        issuer,
498        this_update,
499        next_update,
500        revoked,
501        extensions,
502        bytes: element.as_bytes(),
503        span: element.span(),
504    })
505}
506
507fn read_entry<'a>(
508    parent: &Reader<'a>,
509    element: &Element<'a>,
510    version: Version,
511) -> Result<RevokedCertificate<'a>> {
512    if element.tag() != Tag::constructed(universal::SEQUENCE) {
513        return Err(Error::new(
514            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
515            ENTRIES,
516            element.span().start,
517        ));
518    }
519    let mut inside = parent.enter(element).field(ENTRIES)?;
520    let serial = inside.read_integer().field(ENTRIES)?;
521    let date = required(&mut inside, ENTRIES, element.span().start)?;
522    let revocation_date = time::read_in(&inside, &date, ENTRIES)?;
523    let extensions = match inside.next_element().field(ENTRIES)? {
524        Some(value) => {
525            // RFC 5280 §5.1.2.6: entry extensions require CRL version 2.
526            if version != Version::V2 {
527                return Err(Error::new(
528                    ErrorCode::UnexpectedField,
529                    ENTRIES,
530                    value.span().start,
531                ));
532            }
533            extension::read(&inside, &value, ENTRIES)?
534        }
535        None => Extensions::empty(inside.memory()),
536    };
537    end(&inside, ENTRIES, element.span().start)?;
538    Ok(RevokedCertificate {
539        serial,
540        revocation_date,
541        revocation_date_offset: date.span().start,
542        extensions,
543        span: element.span(),
544    })
545}