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    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: crate::signature::SignatureValue<'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<E>, E: Work>(
240        &'r self,
241        issuer: &'r crate::Certificate<'a>,
242        key: WorkingKey<'a>,
243        verifier: &V,
244        execution: &E,
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 = crate::signature::SignatureValue::read(&mut inside, 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.bit_string()
363    }
364    /// Signature bytes with original source context for deferred decoding.
365    #[must_use]
366    pub fn signature_value(&self) -> crate::signature::SignatureValue<'a> {
367        self.signature
368    }
369    /// Exact `TBSCertList` range to supply to a signature verifier.
370    #[must_use]
371    pub fn signed_bytes(&self) -> &'a [u8] {
372        self.tbs.bytes
373    }
374    /// Whole original `CertificateList` encoding.
375    #[must_use]
376    pub fn as_bytes(&self) -> &'a [u8] {
377        self.bytes
378    }
379    /// Whole CRL location in its enclosing document.
380    #[must_use]
381    pub fn span(&self) -> Span {
382        self.span
383    }
384}
385
386const FIELD: &str = "certificateList";
387const TBS: &str = "certificateList.tbsCertList";
388const ALGORITHM: &str = "certificateList.signatureAlgorithm";
389const SIGNATURE: &str = "certificateList.signature";
390const ISSUER: &str = "certificateList.tbsCertList.issuer";
391const THIS_UPDATE: &str = "certificateList.tbsCertList.thisUpdate";
392const NEXT_UPDATE: &str = "certificateList.tbsCertList.nextUpdate";
393const ENTRIES: &str = "certificateList.tbsCertList.revokedCertificates";
394const EXTENSIONS: &str = "certificateList.tbsCertList.crlExtensions";
395
396fn required<'a>(reader: &mut Reader<'a>, field: &'static str, at: u64) -> Result<Element<'a>> {
397    reader
398        .next_element()
399        .field(field)?
400        .ok_or_else(|| Error::new(ErrorCode::MissingField, field, at))
401}
402
403fn end(reader: &structured_asn1::ScopedReader<'_, '_>, field: &'static str, at: u64) -> Result<()> {
404    if reader.is_empty() {
405        return Ok(());
406    }
407    Err(Error::new(ErrorCode::TrailingData, field, at))
408}
409
410fn is_time(element: &Element<'_>) -> bool {
411    element.tag() == Tag::primitive(universal::UTC_TIME)
412        || element.tag() == Tag::primitive(universal::GENERALIZED_TIME)
413}
414
415fn read_tbs<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<TbsCertList<'a>> {
416    if element.tag() != Tag::constructed(universal::SEQUENCE) {
417        return Err(Error::new(
418            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
419            TBS,
420            element.span().start,
421        ));
422    }
423    let mut inside = parent.enter(element).field(TBS)?;
424    let mut next = required(&mut inside, TBS, element.span().start)?;
425    let version = if next.tag() == Tag::primitive(universal::INTEGER) {
426        // as_u64 refuses wider inputs before scanning, and converts at most
427        // eight magnitude bytes. Admit that bounded conversion before it runs.
428        inside.charge(9).field(TBS)?;
429        if inside.integer(&next).field(TBS)?.as_u64() != Some(1) {
430            return Err(Error::new(
431                ErrorCode::UnsupportedVersion,
432                TBS,
433                next.span().start,
434            ));
435        }
436        next = required(&mut inside, TBS, element.span().start)?;
437        Version::V2
438    } else {
439        Version::V1
440    };
441    let signature = algorithm::read(&inside, &next, TBS)?;
442    let issuer_element = required(&mut inside, ISSUER, element.span().start)?;
443    let issuer = name::read(&inside, &issuer_element, ISSUER)?;
444    // RFC 5280 §5.1.2.3: issuer MUST contain a nonempty distinguished name.
445    if issuer.is_empty() {
446        return Err(Error::new(
447            ErrorCode::EmptySequence,
448            ISSUER,
449            issuer_element.span().start,
450        ));
451    }
452    let this = required(&mut inside, THIS_UPDATE, element.span().start)?;
453    let this_update = time::read_in(&inside, &this, THIS_UPDATE)?;
454    let mut next = inside.next_element().field(TBS)?;
455    let next_update = match next {
456        Some(value) if is_time(&value) => {
457            let time = time::read_in(&inside, &value, NEXT_UPDATE)?;
458            next = inside.next_element().field(TBS)?;
459            Some(time)
460        }
461        _ => None,
462    };
463    let work = parent.execution();
464    let mut revoked = work.vector();
465    if let Some(sequence) =
466        next.filter(|value| value.tag() == Tag::constructed(universal::SEQUENCE))
467    {
468        let mut entries = inside.enter(&sequence).field(ENTRIES)?;
469        while let Some(entry) = entries.next_element().field(ENTRIES)? {
470            let value = read_entry(&entries, &entry, version)?;
471            revoked
472                .try_push(value)
473                .map_err(|reason| Error::resource(reason, ENTRIES, entry.span().start))?;
474        }
475        // RFC 5280 §5.1.2.6: no revoked certificates means the list is absent.
476        if revoked.is_empty() {
477            return Err(Error::new(
478                ErrorCode::EmptySequence,
479                ENTRIES,
480                sequence.span().start,
481            ));
482        }
483        next = inside.next_element().field(TBS)?;
484    }
485    let mut extensions = Extensions::empty(inside.memory());
486    if let Some(tagged) = next {
487        if tagged.tag() != Tag::context(true, 0) || version != Version::V2 {
488            return Err(Error::new(
489                ErrorCode::UnexpectedField,
490                EXTENSIONS,
491                tagged.span().start,
492            ));
493        }
494        let mut wrapper = inside.enter(&tagged).field(EXTENSIONS)?;
495        let sequence = required(&mut wrapper, EXTENSIONS, tagged.span().start)?;
496        extensions = extension::read(&wrapper, &sequence, EXTENSIONS)?;
497        end(&wrapper, EXTENSIONS, tagged.span().start)?;
498    }
499    end(&inside, TBS, element.span().start)?;
500    Ok(TbsCertList {
501        version,
502        signature,
503        issuer,
504        this_update,
505        next_update,
506        revoked: revoked.into_owned(),
507        extensions,
508        bytes: element.as_bytes(),
509        span: element.span(),
510    })
511}
512
513fn read_entry<'a>(
514    parent: &Reader<'a>,
515    element: &Element<'a>,
516    version: Version,
517) -> Result<RevokedCertificate<'a>> {
518    if element.tag() != Tag::constructed(universal::SEQUENCE) {
519        return Err(Error::new(
520            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
521            ENTRIES,
522            element.span().start,
523        ));
524    }
525    let mut inside = parent.enter(element).field(ENTRIES)?;
526    let serial = inside.read_integer().field(ENTRIES)?;
527    let date = required(&mut inside, ENTRIES, element.span().start)?;
528    let revocation_date = time::read_in(&inside, &date, ENTRIES)?;
529    let extensions = match inside.next_element().field(ENTRIES)? {
530        Some(value) => {
531            // RFC 5280 §5.1.2.6: entry extensions require CRL version 2.
532            if version != Version::V2 {
533                return Err(Error::new(
534                    ErrorCode::UnexpectedField,
535                    ENTRIES,
536                    value.span().start,
537                ));
538            }
539            extension::read(&inside, &value, ENTRIES)?
540        }
541        None => Extensions::empty(inside.memory()),
542    };
543    end(&inside, ENTRIES, element.span().start)?;
544    Ok(RevokedCertificate {
545        serial,
546        revocation_date,
547        revocation_date_offset: date.span().start,
548        extensions,
549        span: element.span(),
550    })
551}