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::{OwnedVec, Work};
14use structured_asn1::{BitString, Element, Integer, Parser, Reader, Span, Tag, universal};
15
16mod scope;
17use crate::revocation::{RevocationScheme, RevocationTarget, TargetKind};
18pub use scope::{CertificateKind, IssuingDistributionPoint, Reasons, ScopeFailure, ScopeOutcome};
19mod entry;
20pub use entry::EntryLookup;
21pub use entry::Reason;
22pub(crate) use entry::reason_element;
23mod delta;
24pub use delta::{
25    BoundDelta, CombinedEntry, DeltaBinding, DeltaFailure, EntryOrigin, MissingDeltaField,
26};
27mod freshness;
28pub use freshness::Freshness;
29pub(crate) use freshness::update_window;
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<W: Work>(
126        &self,
127        now: crate::temporal::Reading,
128        utc: &crate::temporal::LeapTable<'_>,
129        execution: &W,
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    source_depth: u32,
161    version: Version,
162    signature: AlgorithmIdentifier<'a>,
163    issuer: Name<'a>,
164    this_update: Time,
165    next_update: Option<Time>,
166    revoked: OwnedVec<RevokedCertificate<'a>>,
167    extensions: Extensions<'a>,
168    bytes: &'a [u8],
169    span: Span,
170}
171
172impl<'a> TbsCertList<'a> {
173    pub(crate) fn reader_scoped<'q, W: Work>(
174        &self,
175        work: &'q W,
176    ) -> Result<structured_asn1::ScopedReader<'a, 'q>>
177    where
178        'a: 'q,
179    {
180        work.read_scoped_source(
181            self.signature.source_parser(),
182            self.bytes,
183            self.span.start,
184            structured_asn1::Encoding::Der,
185            self.source_depth,
186        )
187        .field(TBS)
188    }
189
190    /// Which CRL schema was used.
191    #[must_use]
192    pub fn version(&self) -> Version {
193        self.version
194    }
195    /// Algorithm inside the authenticated range.
196    #[must_use]
197    pub fn signature(&self) -> AlgorithmIdentifier<'a> {
198        self.signature
199    }
200    /// Nonempty CRL issuer distinguished name.
201    #[must_use]
202    pub fn issuer(&self) -> &Name<'a> {
203        &self.issuer
204    }
205    /// Issue time, without consulting a clock or UTC correction service.
206    #[must_use]
207    pub fn this_update(&self) -> Time {
208        self.this_update
209    }
210    /// Advertised next issue time. Its absence is preserved: RFC 5280 §5.1.2.5
211    /// leaves client behavior for this case unspecified.
212    #[must_use]
213    pub fn next_update(&self) -> Option<Time> {
214        self.next_update
215    }
216    /// Entries in wire order, required for indirect issuer inheritance.
217    #[must_use]
218    pub fn revoked_certificates(&self) -> &[RevokedCertificate<'a>] {
219        &self.revoked
220    }
221    /// CRL extensions, kept independently of recognition or authentication.
222    #[must_use]
223    pub fn extensions(&self) -> &Extensions<'a> {
224        &self.extensions
225    }
226    /// Exact DER `TBSCertList`, including its original tag and length.
227    #[must_use]
228    pub fn as_bytes(&self) -> &'a [u8] {
229        self.bytes
230    }
231    /// Original document location of `TBSCertList`.
232    #[must_use]
233    pub fn span(&self) -> Span {
234        self.span
235    }
236}
237
238/// A DER `CertificateList`, with no fetching or implicit trust decisions.
239#[derive(Debug)]
240pub struct CertificateList<'a> {
241    tbs: TbsCertList<'a>,
242    signature_algorithm: AlgorithmIdentifier<'a>,
243    signature: crate::signature::SignatureValue<'a>,
244    bytes: &'a [u8],
245    span: Span,
246}
247
248impl<'a> CertificateList<'a> {
249    /// Borrows the original CRL execution under the current operation without
250    /// reading parameters, cloning storage or resetting the producing ceiling.
251    #[must_use]
252    pub fn execution_in<'operation, W: Work>(
253        &self,
254        work: &'operation W,
255    ) -> structured_asn1::memory::Execution<'operation>
256    where
257        'a: 'operation,
258    {
259        self.signature_algorithm.query_in(work)
260    }
261
262    /// Authenticate with an already authenticated issuer and its effective
263    /// working key. The caller establishes the issuer path and its trust-anchor
264    /// provenance; this operation binds that certificate to the actual key and
265    /// CRL. It performs no path search, fetching, clock access or status verdict.
266    ///
267    /// The verifier must account its own transitive work and allocations in
268    /// `execution`. Passing this domain is not evidence that an opaque provider
269    /// backend enforces the accounting contract.
270    pub fn authenticate<'r, V: Verify<E>, E: Work>(
271        &'r self,
272        issuer: &'r crate::Certificate<'a>,
273        key: WorkingKey<'a>,
274        verifier: &V,
275        execution: &E,
276    ) -> crate::error::CallbackResult<Authentication<'r, 'a>, V::Error> {
277        use crate::error::CallbackError;
278        let encoded_key = key.key().as_bytes();
279        let issuer_key = issuer.tbs().public_key().as_bytes();
280        let work = (encoded_key.len().max(issuer_key.len()) as u64)
281            .checked_mul(2)
282            .and_then(|n| n.checked_add(1))
283            .ok_or_else(|| {
284                Error::resource(
285                    structured_asn1::memory::MemoryError::WorkExceeded,
286                    FIELD,
287                    self.span.start,
288                )
289            })?;
290        execution
291            .charge_work(work)
292            .map_err(|reason| Error::resource(reason, FIELD, self.span.start))?;
293        if encoded_key != issuer_key {
294            return Ok(Authentication::Rejected(AuthenticationFailure::KeyBinding));
295        }
296        if !self.tbs.issuer.matches(issuer.tbs().subject(), execution)? {
297            return Ok(Authentication::Rejected(AuthenticationFailure::IssuerName));
298        }
299        let usage = issuer.tbs().extensions().key_usage(execution)?;
300        // RFC 10007 §4: version 3 CRL issuer certificates require keyUsage
301        // with cRLSign. Version 1/2 certificates are exempt from its presence;
302        // an existing keyUsage still constrains the operation (RFC 5280 §6.3.3).
303        // https://www.rfc-editor.org/rfc/rfc10007#section-4
304        if usage.is_some_and(|usage| !usage.crl_sign())
305            || (usage.is_none() && issuer.tbs().version() == crate::Version::V3)
306        {
307            return Ok(Authentication::Rejected(AuthenticationFailure::KeyUsage));
308        }
309        match verifier
310            .verify(
311                &self.tbs.signature,
312                key,
313                self.signed_bytes(),
314                self.signature,
315                execution,
316            )
317            .map_err(CallbackError::Callback)?
318        {
319            Verified::Yes => Ok(Authentication::Authenticated(Authenticated {
320                crl: self,
321                issuer,
322                key,
323            })),
324            Verified::No => Ok(Authentication::Rejected(AuthenticationFailure::Signature)),
325            Verified::Unsupported => Ok(Authentication::Unsupported),
326        }
327    }
328
329    /// Read exactly one CRL, retaining original offsets and the shared parser.
330    pub fn read(parser: &'a Parser, bytes: &'a [u8], base: u64) -> Result<Self> {
331        let mut reader = parser.read(bytes, base).field(FIELD)?;
332        let element = required(&mut reader, FIELD, base)?;
333        let crl = Self::read_element(&reader, &element)?;
334        if !reader.is_empty() {
335            return Err(Error::new(
336                ErrorCode::TrailingData,
337                FIELD,
338                element.span().end(),
339            ));
340        }
341        Ok(crl)
342    }
343
344    /// Read an embedded CRL through its producing Reader. A BER envelope does
345    /// not relax the CRL's DER spelling or reset depth and work limits.
346    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
347        // RFC 5280 §5.1 and Appendix A.1 require the CertificateList DER model.
348        // https://www.rfc-editor.org/rfc/rfc5280#section-5.1
349        let mut outer = parent
350            .reread(element, structured_asn1::Encoding::Der)
351            .field(FIELD)?;
352        let root = outer
353            .expect(Tag::constructed(universal::SEQUENCE))
354            .field(FIELD)?;
355        let mut inside = outer.enter(&root).field(FIELD)?;
356        let tbs_element = required(&mut inside, TBS, root.span().start)?;
357        let tbs = read_tbs(&inside, &tbs_element)?;
358        let algorithm_element = required(&mut inside, ALGORITHM, root.span().start)?;
359        let signature_algorithm = algorithm::read(&inside, &algorithm_element, ALGORITHM)?;
360        let signature = crate::signature::SignatureValue::read(&mut inside, SIGNATURE)?;
361        end(&inside, FIELD, root.span().start)?;
362        // RFC 5280 §§5.1.1.2, 5.1.2.2: the unsigned algorithm must agree with
363        // the signed identifier. Preserve both execution and parser ceilings.
364        if !signature_algorithm.matches_in(&tbs.signature, &inside)? {
365            return Err(Error::new(
366                ErrorCode::AlgorithmMismatch,
367                ALGORITHM,
368                algorithm_element.span().start,
369            ));
370        }
371        Ok(Self {
372            tbs,
373            signature_algorithm,
374            signature,
375            bytes: root.as_bytes(),
376            span: root.span(),
377        })
378    }
379
380    /// Signed fields and retained revocation entries.
381    #[must_use]
382    pub fn tbs(&self) -> &TbsCertList<'a> {
383        &self.tbs
384    }
385    /// Outer algorithm, already required to match the signed algorithm.
386    #[must_use]
387    pub fn signature_algorithm(&self) -> AlgorithmIdentifier<'a> {
388        self.signature_algorithm
389    }
390    /// Original signature BIT STRING.
391    #[must_use]
392    pub fn signature(&self) -> BitString<'a> {
393        self.signature.bit_string()
394    }
395    /// Signature bytes with original source context for deferred decoding.
396    #[must_use]
397    pub fn signature_value(&self) -> crate::signature::SignatureValue<'a> {
398        self.signature
399    }
400    /// Exact `TBSCertList` range to supply to a signature verifier.
401    #[must_use]
402    pub fn signed_bytes(&self) -> &'a [u8] {
403        self.tbs.bytes
404    }
405    /// Whole original `CertificateList` encoding.
406    #[must_use]
407    pub fn as_bytes(&self) -> &'a [u8] {
408        self.bytes
409    }
410    /// Whole CRL location in its enclosing document.
411    #[must_use]
412    pub fn span(&self) -> Span {
413        self.span
414    }
415}
416
417const FIELD: &str = "certificateList";
418const TBS: &str = "certificateList.tbsCertList";
419const ALGORITHM: &str = "certificateList.signatureAlgorithm";
420const SIGNATURE: &str = "certificateList.signature";
421const ISSUER: &str = "certificateList.tbsCertList.issuer";
422const THIS_UPDATE: &str = "certificateList.tbsCertList.thisUpdate";
423const NEXT_UPDATE: &str = "certificateList.tbsCertList.nextUpdate";
424const ENTRIES: &str = "certificateList.tbsCertList.revokedCertificates";
425const EXTENSIONS: &str = "certificateList.tbsCertList.crlExtensions";
426
427fn required<'a>(reader: &mut Reader<'a>, field: &'static str, at: u64) -> Result<Element<'a>> {
428    reader
429        .next_element()
430        .field(field)?
431        .ok_or_else(|| Error::new(ErrorCode::MissingField, field, at))
432}
433
434fn end(reader: &structured_asn1::ScopedReader<'_, '_>, field: &'static str, at: u64) -> Result<()> {
435    if reader.is_empty() {
436        return Ok(());
437    }
438    Err(Error::new(ErrorCode::TrailingData, field, at))
439}
440
441fn is_time(element: &Element<'_>) -> bool {
442    element.tag() == Tag::primitive(universal::UTC_TIME)
443        || element.tag() == Tag::primitive(universal::GENERALIZED_TIME)
444}
445
446fn read_tbs<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<TbsCertList<'a>> {
447    if element.tag() != Tag::constructed(universal::SEQUENCE) {
448        return Err(Error::new(
449            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
450            TBS,
451            element.span().start,
452        ));
453    }
454    let mut inside = parent.enter(element).field(TBS)?;
455    let mut next = required(&mut inside, TBS, element.span().start)?;
456    let version = if next.tag() == Tag::primitive(universal::INTEGER) {
457        // as_u64 refuses wider inputs before scanning, and converts at most
458        // eight magnitude bytes. Admit that bounded conversion before it runs.
459        inside.charge(9).field(TBS)?;
460        if inside.integer(&next).field(TBS)?.as_u64() != Some(1) {
461            return Err(Error::new(
462                ErrorCode::UnsupportedVersion,
463                TBS,
464                next.span().start,
465            ));
466        }
467        next = required(&mut inside, TBS, element.span().start)?;
468        Version::V2
469    } else {
470        Version::V1
471    };
472    let signature = algorithm::read(&inside, &next, TBS)?;
473    let issuer_element = required(&mut inside, ISSUER, element.span().start)?;
474    let issuer = name::read(&inside, &issuer_element, ISSUER)?;
475    // RFC 5280 §5.1.2.3: issuer MUST contain a nonempty distinguished name.
476    if issuer.is_empty() {
477        return Err(Error::new(
478            ErrorCode::EmptySequence,
479            ISSUER,
480            issuer_element.span().start,
481        ));
482    }
483    let this = required(&mut inside, THIS_UPDATE, element.span().start)?;
484    let this_update = time::read_in(&inside, &this, THIS_UPDATE)?;
485    let mut next = inside.next_element().field(TBS)?;
486    let next_update = match next {
487        Some(value) if is_time(&value) => {
488            let time = time::read_in(&inside, &value, NEXT_UPDATE)?;
489            next = inside.next_element().field(TBS)?;
490            Some(time)
491        }
492        _ => None,
493    };
494    let work = parent.execution();
495    let mut revoked = work.vector();
496    if let Some(sequence) =
497        next.filter(|value| value.tag() == Tag::constructed(universal::SEQUENCE))
498    {
499        let mut entries = inside.enter(&sequence).field(ENTRIES)?;
500        while let Some(entry) = entries.next_element().field(ENTRIES)? {
501            let value = read_entry(&entries, &entry, version)?;
502            revoked
503                .try_push(value)
504                .map_err(|reason| Error::resource(reason, ENTRIES, entry.span().start))?;
505        }
506        // RFC 5280 §5.1.2.6: no revoked certificates means the list is absent.
507        if revoked.is_empty() {
508            return Err(Error::new(
509                ErrorCode::EmptySequence,
510                ENTRIES,
511                sequence.span().start,
512            ));
513        }
514        next = inside.next_element().field(TBS)?;
515    }
516    let mut extensions = Extensions::empty(inside.memory());
517    if let Some(tagged) = next {
518        if tagged.tag() != Tag::context(true, 0) || version != Version::V2 {
519            return Err(Error::new(
520                ErrorCode::UnexpectedField,
521                EXTENSIONS,
522                tagged.span().start,
523            ));
524        }
525        let mut wrapper = inside.enter(&tagged).field(EXTENSIONS)?;
526        let sequence = required(&mut wrapper, EXTENSIONS, tagged.span().start)?;
527        extensions = extension::read(&wrapper, &sequence, EXTENSIONS)?;
528        end(&wrapper, EXTENSIONS, tagged.span().start)?;
529    }
530    end(&inside, TBS, element.span().start)?;
531    Ok(TbsCertList {
532        source_depth: parent.depth(),
533        version,
534        signature,
535        issuer,
536        this_update,
537        next_update,
538        revoked: revoked.into_owned(),
539        extensions,
540        bytes: element.as_bytes(),
541        span: element.span(),
542    })
543}
544
545fn read_entry<'a>(
546    parent: &Reader<'a>,
547    element: &Element<'a>,
548    version: Version,
549) -> Result<RevokedCertificate<'a>> {
550    if element.tag() != Tag::constructed(universal::SEQUENCE) {
551        return Err(Error::new(
552            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
553            ENTRIES,
554            element.span().start,
555        ));
556    }
557    let mut inside = parent.enter(element).field(ENTRIES)?;
558    let serial = inside.read_integer().field(ENTRIES)?;
559    let date = required(&mut inside, ENTRIES, element.span().start)?;
560    let revocation_date = time::read_in(&inside, &date, ENTRIES)?;
561    let extensions = match inside.next_element().field(ENTRIES)? {
562        Some(value) => {
563            // RFC 5280 §5.1.2.6: entry extensions require CRL version 2.
564            if version != Version::V2 {
565                return Err(Error::new(
566                    ErrorCode::UnexpectedField,
567                    ENTRIES,
568                    value.span().start,
569                ));
570            }
571            extension::read(&inside, &value, ENTRIES)?
572        }
573        None => Extensions::empty(inside.memory()),
574    };
575    end(&inside, ENTRIES, element.span().start)?;
576    Ok(RevokedCertificate {
577        serial,
578        revocation_date,
579        revocation_date_offset: date.span().start,
580        extensions,
581        span: element.span(),
582    })
583}