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