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    /// Borrows the original CRL execution under the current operation without
232    /// reading parameters, cloning storage or resetting the producing ceiling.
233    #[must_use]
234    pub fn execution_in<'operation, W: Work>(
235        &self,
236        work: &'operation W,
237    ) -> structured_asn1::memory::Execution<'operation>
238    where
239        'a: 'operation,
240    {
241        self.signature_algorithm.query_in(work)
242    }
243
244    /// Authenticate with an already authenticated issuer and its effective
245    /// working key. The caller establishes the issuer path and its trust-anchor
246    /// provenance; this operation binds that certificate to the actual key and
247    /// CRL. It performs no path search, fetching, clock access or status verdict.
248    ///
249    /// The verifier must account its own transitive work and allocations in
250    /// `execution`. Passing this domain is not evidence that an opaque provider
251    /// backend enforces the accounting contract.
252    pub fn authenticate<'r, V: Verify<E>, E: Work>(
253        &'r self,
254        issuer: &'r crate::Certificate<'a>,
255        key: WorkingKey<'a>,
256        verifier: &V,
257        execution: &E,
258    ) -> crate::error::CallbackResult<Authentication<'r, 'a>, V::Error> {
259        use crate::error::CallbackError;
260        let encoded_key = key.key().as_bytes();
261        let issuer_key = issuer.tbs().public_key().as_bytes();
262        let work = (encoded_key.len().max(issuer_key.len()) as u64)
263            .checked_mul(2)
264            .and_then(|n| n.checked_add(1))
265            .ok_or_else(|| {
266                Error::resource(
267                    structured_asn1::memory::MemoryError::WorkExceeded,
268                    FIELD,
269                    self.span.start,
270                )
271            })?;
272        execution
273            .charge_work(work)
274            .map_err(|reason| Error::resource(reason, FIELD, self.span.start))?;
275        if encoded_key != issuer_key {
276            return Ok(Authentication::Rejected(AuthenticationFailure::KeyBinding));
277        }
278        if !self.tbs.issuer.matches(issuer.tbs().subject(), execution)? {
279            return Ok(Authentication::Rejected(AuthenticationFailure::IssuerName));
280        }
281        let usage = issuer.tbs().extensions().key_usage(execution)?;
282        // RFC 10007 §4: version 3 CRL issuer certificates require keyUsage
283        // with cRLSign. Version 1/2 certificates are exempt from its presence;
284        // an existing keyUsage still constrains the operation (RFC 5280 §6.3.3).
285        // https://www.rfc-editor.org/rfc/rfc10007#section-4
286        if usage.is_some_and(|usage| !usage.crl_sign())
287            || (usage.is_none() && issuer.tbs().version() == crate::Version::V3)
288        {
289            return Ok(Authentication::Rejected(AuthenticationFailure::KeyUsage));
290        }
291        match verifier
292            .verify(
293                &self.tbs.signature,
294                key,
295                self.signed_bytes(),
296                self.signature,
297                execution,
298            )
299            .map_err(CallbackError::Callback)?
300        {
301            Verified::Yes => Ok(Authentication::Authenticated(Authenticated {
302                crl: self,
303                issuer,
304                key,
305            })),
306            Verified::No => Ok(Authentication::Rejected(AuthenticationFailure::Signature)),
307            Verified::Unsupported => Ok(Authentication::Unsupported),
308        }
309    }
310
311    /// Read exactly one CRL, retaining original offsets and the shared parser.
312    pub fn read(parser: &'a Parser, bytes: &'a [u8], base: u64) -> Result<Self> {
313        let mut reader = parser.read(bytes, base).field(FIELD)?;
314        let element = required(&mut reader, FIELD, base)?;
315        let crl = Self::read_element(&reader, &element)?;
316        if !reader.is_empty() {
317            return Err(Error::new(
318                ErrorCode::TrailingData,
319                FIELD,
320                element.span().end(),
321            ));
322        }
323        Ok(crl)
324    }
325
326    /// Read an embedded CRL through its producing Reader. A BER envelope does
327    /// not relax the CRL's DER spelling or reset depth and work limits.
328    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
329        // RFC 5280 §5.1 and Appendix A.1 require the CertificateList DER model.
330        // https://www.rfc-editor.org/rfc/rfc5280#section-5.1
331        let mut outer = parent
332            .reread(element, structured_asn1::Encoding::Der)
333            .field(FIELD)?;
334        let root = outer
335            .expect(Tag::constructed(universal::SEQUENCE))
336            .field(FIELD)?;
337        let mut inside = outer.enter(&root).field(FIELD)?;
338        let tbs_element = required(&mut inside, TBS, root.span().start)?;
339        let tbs = read_tbs(&inside, &tbs_element)?;
340        let algorithm_element = required(&mut inside, ALGORITHM, root.span().start)?;
341        let signature_algorithm = algorithm::read(&inside, &algorithm_element, ALGORITHM)?;
342        let signature = crate::signature::SignatureValue::read(&mut inside, SIGNATURE)?;
343        end(&inside, FIELD, root.span().start)?;
344        // RFC 5280 §§5.1.1.2, 5.1.2.2: the unsigned algorithm must agree with
345        // the signed identifier. Preserve both execution and parser ceilings.
346        if !signature_algorithm.matches_in(&tbs.signature, &inside)? {
347            return Err(Error::new(
348                ErrorCode::AlgorithmMismatch,
349                ALGORITHM,
350                algorithm_element.span().start,
351            ));
352        }
353        Ok(Self {
354            tbs,
355            signature_algorithm,
356            signature,
357            bytes: root.as_bytes(),
358            span: root.span(),
359        })
360    }
361
362    /// Signed fields and retained revocation entries.
363    #[must_use]
364    pub fn tbs(&self) -> &TbsCertList<'a> {
365        &self.tbs
366    }
367    /// Outer algorithm, already required to match the signed algorithm.
368    #[must_use]
369    pub fn signature_algorithm(&self) -> AlgorithmIdentifier<'a> {
370        self.signature_algorithm
371    }
372    /// Original signature BIT STRING.
373    #[must_use]
374    pub fn signature(&self) -> BitString<'a> {
375        self.signature.bit_string()
376    }
377    /// Signature bytes with original source context for deferred decoding.
378    #[must_use]
379    pub fn signature_value(&self) -> crate::signature::SignatureValue<'a> {
380        self.signature
381    }
382    /// Exact `TBSCertList` range to supply to a signature verifier.
383    #[must_use]
384    pub fn signed_bytes(&self) -> &'a [u8] {
385        self.tbs.bytes
386    }
387    /// Whole original `CertificateList` encoding.
388    #[must_use]
389    pub fn as_bytes(&self) -> &'a [u8] {
390        self.bytes
391    }
392    /// Whole CRL location in its enclosing document.
393    #[must_use]
394    pub fn span(&self) -> Span {
395        self.span
396    }
397}
398
399const FIELD: &str = "certificateList";
400const TBS: &str = "certificateList.tbsCertList";
401const ALGORITHM: &str = "certificateList.signatureAlgorithm";
402const SIGNATURE: &str = "certificateList.signature";
403const ISSUER: &str = "certificateList.tbsCertList.issuer";
404const THIS_UPDATE: &str = "certificateList.tbsCertList.thisUpdate";
405const NEXT_UPDATE: &str = "certificateList.tbsCertList.nextUpdate";
406const ENTRIES: &str = "certificateList.tbsCertList.revokedCertificates";
407const EXTENSIONS: &str = "certificateList.tbsCertList.crlExtensions";
408
409fn required<'a>(reader: &mut Reader<'a>, field: &'static str, at: u64) -> Result<Element<'a>> {
410    reader
411        .next_element()
412        .field(field)?
413        .ok_or_else(|| Error::new(ErrorCode::MissingField, field, at))
414}
415
416fn end(reader: &structured_asn1::ScopedReader<'_, '_>, field: &'static str, at: u64) -> Result<()> {
417    if reader.is_empty() {
418        return Ok(());
419    }
420    Err(Error::new(ErrorCode::TrailingData, field, at))
421}
422
423fn is_time(element: &Element<'_>) -> bool {
424    element.tag() == Tag::primitive(universal::UTC_TIME)
425        || element.tag() == Tag::primitive(universal::GENERALIZED_TIME)
426}
427
428fn read_tbs<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<TbsCertList<'a>> {
429    if element.tag() != Tag::constructed(universal::SEQUENCE) {
430        return Err(Error::new(
431            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
432            TBS,
433            element.span().start,
434        ));
435    }
436    let mut inside = parent.enter(element).field(TBS)?;
437    let mut next = required(&mut inside, TBS, element.span().start)?;
438    let version = if next.tag() == Tag::primitive(universal::INTEGER) {
439        // as_u64 refuses wider inputs before scanning, and converts at most
440        // eight magnitude bytes. Admit that bounded conversion before it runs.
441        inside.charge(9).field(TBS)?;
442        if inside.integer(&next).field(TBS)?.as_u64() != Some(1) {
443            return Err(Error::new(
444                ErrorCode::UnsupportedVersion,
445                TBS,
446                next.span().start,
447            ));
448        }
449        next = required(&mut inside, TBS, element.span().start)?;
450        Version::V2
451    } else {
452        Version::V1
453    };
454    let signature = algorithm::read(&inside, &next, TBS)?;
455    let issuer_element = required(&mut inside, ISSUER, element.span().start)?;
456    let issuer = name::read(&inside, &issuer_element, ISSUER)?;
457    // RFC 5280 §5.1.2.3: issuer MUST contain a nonempty distinguished name.
458    if issuer.is_empty() {
459        return Err(Error::new(
460            ErrorCode::EmptySequence,
461            ISSUER,
462            issuer_element.span().start,
463        ));
464    }
465    let this = required(&mut inside, THIS_UPDATE, element.span().start)?;
466    let this_update = time::read_in(&inside, &this, THIS_UPDATE)?;
467    let mut next = inside.next_element().field(TBS)?;
468    let next_update = match next {
469        Some(value) if is_time(&value) => {
470            let time = time::read_in(&inside, &value, NEXT_UPDATE)?;
471            next = inside.next_element().field(TBS)?;
472            Some(time)
473        }
474        _ => None,
475    };
476    let work = parent.execution();
477    let mut revoked = work.vector();
478    if let Some(sequence) =
479        next.filter(|value| value.tag() == Tag::constructed(universal::SEQUENCE))
480    {
481        let mut entries = inside.enter(&sequence).field(ENTRIES)?;
482        while let Some(entry) = entries.next_element().field(ENTRIES)? {
483            let value = read_entry(&entries, &entry, version)?;
484            revoked
485                .try_push(value)
486                .map_err(|reason| Error::resource(reason, ENTRIES, entry.span().start))?;
487        }
488        // RFC 5280 §5.1.2.6: no revoked certificates means the list is absent.
489        if revoked.is_empty() {
490            return Err(Error::new(
491                ErrorCode::EmptySequence,
492                ENTRIES,
493                sequence.span().start,
494            ));
495        }
496        next = inside.next_element().field(TBS)?;
497    }
498    let mut extensions = Extensions::empty(inside.memory());
499    if let Some(tagged) = next {
500        if tagged.tag() != Tag::context(true, 0) || version != Version::V2 {
501            return Err(Error::new(
502                ErrorCode::UnexpectedField,
503                EXTENSIONS,
504                tagged.span().start,
505            ));
506        }
507        let mut wrapper = inside.enter(&tagged).field(EXTENSIONS)?;
508        let sequence = required(&mut wrapper, EXTENSIONS, tagged.span().start)?;
509        extensions = extension::read(&wrapper, &sequence, EXTENSIONS)?;
510        end(&wrapper, EXTENSIONS, tagged.span().start)?;
511    }
512    end(&inside, TBS, element.span().start)?;
513    Ok(TbsCertList {
514        version,
515        signature,
516        issuer,
517        this_update,
518        next_update,
519        revoked: revoked.into_owned(),
520        extensions,
521        bytes: element.as_bytes(),
522        span: element.span(),
523    })
524}
525
526fn read_entry<'a>(
527    parent: &Reader<'a>,
528    element: &Element<'a>,
529    version: Version,
530) -> Result<RevokedCertificate<'a>> {
531    if element.tag() != Tag::constructed(universal::SEQUENCE) {
532        return Err(Error::new(
533            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
534            ENTRIES,
535            element.span().start,
536        ));
537    }
538    let mut inside = parent.enter(element).field(ENTRIES)?;
539    let serial = inside.read_integer().field(ENTRIES)?;
540    let date = required(&mut inside, ENTRIES, element.span().start)?;
541    let revocation_date = time::read_in(&inside, &date, ENTRIES)?;
542    let extensions = match inside.next_element().field(ENTRIES)? {
543        Some(value) => {
544            // RFC 5280 §5.1.2.6: entry extensions require CRL version 2.
545            if version != Version::V2 {
546                return Err(Error::new(
547                    ErrorCode::UnexpectedField,
548                    ENTRIES,
549                    value.span().start,
550                ));
551            }
552            extension::read(&inside, &value, ENTRIES)?
553        }
554        None => Extensions::empty(inside.memory()),
555    };
556    end(&inside, ENTRIES, element.span().start)?;
557    Ok(RevokedCertificate {
558        serial,
559        revocation_date,
560        revocation_date_offset: date.span().start,
561        extensions,
562        span: element.span(),
563    })
564}