Skip to main content

structured_pkix/
algorithm.rs

1//! Which algorithm, and the parameters that go with it.
2//!
3//! The identifier is kept as it was encoded and is not interpreted here. What
4//! may be verified with a key, and how, belongs to the algorithm registry; this
5//! layer's job is to hand that registry exactly what the certificate said,
6//! including parameters it does not understand.
7
8use crate::error::{Error, ErrorCode, Located, Result};
9use core::num::NonZeroUsize;
10use structured_asn1::memory::{Memory, Work};
11use structured_asn1::{Element, Encoding, Oid, Parser, Reader, ScopedReader, Span, Tag, universal};
12
13pub mod digest;
14pub mod dstu4145;
15pub mod ml_dsa;
16pub mod pss;
17pub mod signature;
18
19/// An algorithm and its parameters, as the certificate wrote them.
20#[derive(Debug, Clone, Copy)]
21pub struct AlgorithmIdentifier<'a> {
22    algorithm: Oid<'a>,
23    // A parameter follows the OID, so its relative start is nonzero. It is
24    // the last schema component; only an indefinite root's EOC follows it.
25    parameter_start: Option<NonZeroUsize>,
26    null_parameter: bool,
27    der: &'a [u8],
28    offset: u64,
29    field: &'static str,
30    parser: &'a Parser,
31    outer: u32,
32    encoding: Encoding,
33    indefinite: bool,
34}
35
36/// Borrowed producing execution, without retaining an algorithm, parser
37/// input buffer or account owner. This is not a document/value identity.
38#[derive(Debug, Clone, Copy)]
39pub struct Producer<'a> {
40    parser: &'a Parser,
41}
42impl<'a> Producer<'a> {
43    pub(crate) const fn from_parser(parser: &'a Parser) -> Self {
44        Self { parser }
45    }
46    /// Admits one joint operation without replacing a source already carried
47    /// by current work. Distinct source ceilings and the account are paid once.
48    pub fn charge_work_in<W: Work>(
49        &self,
50        work: &W,
51        amount: u64,
52    ) -> core::result::Result<(), structured_asn1::memory::MemoryError> {
53        work.charge_work_from([self.parser, self.parser], amount)
54    }
55
56    /// Admits a comparison or operation jointly derived from two producers,
57    /// preserving every existing current ceiling with one aggregate bill.
58    pub fn charge_work_with<W: Work>(
59        &self,
60        other: Self,
61        work: &W,
62        amount: u64,
63    ) -> core::result::Result<(), structured_asn1::memory::MemoryError> {
64        work.charge_work_from([self.parser, other.parser], amount)
65    }
66
67    /// Funds new work against this producer and the current operation.
68    #[must_use]
69    pub fn execution_in<'q, W: Work>(&self, work: &'q W) -> structured_asn1::memory::Execution<'q>
70    where
71        'a: 'q,
72    {
73        work.execution_for(self.parser)
74    }
75}
76
77/// Original bytes nested one level inside an identifier's reader, carrying
78/// only what reading them in place needs: a key that inherits parameters
79/// keeps this rather than the authority's whole identifier.
80#[derive(Debug, Clone, Copy)]
81pub(crate) struct Nested<'a> {
82    bytes: &'a [u8],
83    at: u64,
84    parser: &'a Parser,
85    outer: u32,
86    encoding: Encoding,
87}
88
89impl<'a> Nested<'a> {
90    pub(crate) fn execution_origin(self) -> Producer<'a> {
91        Producer {
92            parser: self.parser,
93        }
94    }
95
96    pub(crate) fn encoding(self) -> Encoding {
97        self.encoding
98    }
99    pub(crate) fn execution_in<'q, W: Work>(
100        self,
101        work: &'q W,
102    ) -> structured_asn1::memory::Execution<'q>
103    where
104        'a: 'q,
105    {
106        work.execution_for(self.parser)
107    }
108
109    /// Original start, without opening or scanning the nested value.
110    pub(crate) fn offset(self) -> u64 {
111        self.at
112    }
113
114    pub(crate) fn as_bytes(self) -> &'a [u8] {
115        self.bytes
116    }
117
118    /// Opens the single element these bytes hold. Source parser, offsets,
119    /// encoding and depth stay original; `execution` pays for the decoding.
120    pub(crate) fn open<'operation, W: Work>(
121        self,
122        execution: &'operation W,
123        field: &'static str,
124    ) -> Result<(ScopedReader<'a, 'operation>, Element<'a>)>
125    where
126        'a: 'operation,
127    {
128        let mut reader = execution
129            .read_scoped_nested(self.parser, self.bytes, self.at, self.encoding, self.outer)
130            .field(field)?;
131        let element = reader
132            .next_element()
133            .field(field)?
134            .ok_or_else(|| Error::new(ErrorCode::MissingField, field, reader.offset()))?;
135        if !reader.is_empty() {
136            return Err(Error::new(ErrorCode::TrailingData, field, reader.offset()));
137        }
138        Ok((reader, element))
139    }
140}
141
142// Raw schema fields are deliberately a different type from the validated
143// identifier. Only a fault-free reading can cross the crypto-facing boundary.
144#[derive(Debug, Clone, Copy)]
145struct AlgorithmFields<'a> {
146    algorithm: Oid<'a>,
147    parameter_start: Option<NonZeroUsize>,
148    null_parameter: bool,
149    der: &'a [u8],
150    offset: u64,
151    field: &'static str,
152    parser: &'a Parser,
153    outer: u32,
154    encoding: Encoding,
155    indefinite: bool,
156}
157
158/// Original algorithm fields plus a recoverable parameter-syntax diagnostic.
159///
160/// A malformed reading is inspection data, not valid BER/DER or a provider
161/// input. Only [`Self::identifier`] can expose a validated identifier. No input
162/// bytes are copied and the diagnostic itself has no allocated storage.
163///
164/// ```compile_fail
165/// use structured_pkix::algorithm::{AlgorithmIdentifier, AlgorithmReading};
166/// fn crypto_input(_: AlgorithmIdentifier<'_>) {}
167/// fn unchecked(reading: AlgorithmReading<'_>) {
168///     crypto_input(reading); // Inspection data is not a validated identifier.
169/// }
170/// ```
171#[derive(Debug)]
172pub struct AlgorithmReading<'a> {
173    fields: AlgorithmFields<'a>,
174    fault: Option<Error>,
175}
176
177impl<'a> AlgorithmReading<'a> {
178    /// The original, successfully decoded OID; not a supported capability.
179    #[must_use]
180    pub fn algorithm(&self) -> Oid<'a> {
181        self.fields.algorithm
182    }
183
184    /// The whole original parameter encoding, including any malformed form.
185    #[must_use]
186    pub fn parameters(&self) -> Option<&'a [u8]> {
187        parameter_bytes(
188            self.fields.der,
189            self.fields.parameter_start,
190            self.fields.indefinite,
191        )
192    }
193
194    /// Original parameter range in the producing reader's coordinates.
195    /// Joined content requires its caller-owned source map for physical ranges.
196    #[must_use]
197    pub fn parameters_span(&self) -> Option<Span> {
198        let start = self.fields.parameter_start?.get();
199        Some(Span {
200            start: self.fields.offset + start as u64,
201            length: self.parameters()?.len() as u64,
202        })
203    }
204
205    /// A primitive NULL whose complete parameter validation succeeded.
206    /// A malformed form, including constructed NULL, never has this fact.
207    #[must_use]
208    pub fn has_null_parameter(&self) -> bool {
209        self.fields.null_parameter
210    }
211
212    /// Whole original algorithm encoding, including an original IMPLICIT tag.
213    #[must_use]
214    pub fn as_bytes(&self) -> &'a [u8] {
215        self.fields.der
216    }
217
218    /// Original enclosing algorithm position in the producing source.
219    #[must_use]
220    pub fn offset(&self) -> u64 {
221        self.fields.offset
222    }
223
224    /// Encoding used by the producing reader, without a replacement parser.
225    #[must_use]
226    pub fn encoding(&self) -> Encoding {
227        self.fields.encoding
228    }
229
230    /// Whether this inspection reading belongs to the supplied parser session.
231    /// This checks original provenance even when no validated identifier exists;
232    /// account identity alone does not establish source/session identity.
233    #[must_use]
234    pub fn same_session(&self, parent: &Reader<'_>) -> bool {
235        core::ptr::eq(self.fields.parser, parent.parser())
236    }
237
238    /// Parameter syntax fault; resource and processing failures are never here.
239    #[must_use]
240    pub fn fault(&self) -> Option<&Error> {
241        self.fault.as_ref()
242    }
243
244    /// A validated identifier only when the complete parameter check succeeded.
245    /// Selected-provider parameter/profile acceptance still belongs to that provider.
246    #[must_use]
247    pub fn identifier(&self) -> Option<AlgorithmIdentifier<'a>> {
248        if self.fault.is_some() {
249            return None;
250        }
251        Some(self.fields.identifier())
252    }
253
254    /// Requires a validated provider input, returning the original syntax fault.
255    /// This is a scalar provenance check; no reparse, allocation or parameter
256    /// normalization is performed. Selected-provider acceptance is separate.
257    pub fn require_identifier(&self) -> Result<AlgorithmIdentifier<'a>> {
258        match &self.fault {
259            Some(error) => Err(error.clone()),
260            None => Ok(self.fields.identifier()),
261        }
262    }
263
264    /// Paid written-value identity for inspection, not parameter acceptance.
265    /// Malformed parameters retain exact encoded identity; only two validated
266    /// primitive NULL values can ignore their BER length spelling. Absence is
267    /// never inferred as a default. Both readings require this producing parser.
268    pub fn same_written_in(&self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
269        written_matches(&self.fields, &other.fields, parent, false)
270    }
271
272    fn into_identifier(self) -> Result<AlgorithmIdentifier<'a>> {
273        match self.fault {
274            Some(error) => Err(error),
275            None => Ok(self.fields.identifier()),
276        }
277    }
278}
279
280impl<'a> AlgorithmFields<'a> {
281    // Called only after the reading's fault gate. The raw type never escapes.
282    fn identifier(self) -> AlgorithmIdentifier<'a> {
283        AlgorithmIdentifier {
284            algorithm: self.algorithm,
285            parameter_start: self.parameter_start,
286            null_parameter: self.null_parameter,
287            der: self.der,
288            offset: self.offset,
289            field: self.field,
290            parser: self.parser,
291            outer: self.outer,
292            encoding: self.encoding,
293            indefinite: self.indefinite,
294        }
295    }
296}
297
298fn parameter_bytes(bytes: &[u8], start: Option<NonZeroUsize>, indefinite: bool) -> Option<&[u8]> {
299    let start = start?.get();
300    let end = bytes.len() - if indefinite { 2 } else { 0 };
301    Some(&bytes[start..end])
302}
303
304trait WrittenValue {
305    fn oid_bytes(&self) -> &[u8];
306    fn parameter_bytes(&self) -> Option<&[u8]>;
307    fn valid_null(&self) -> bool;
308    fn producer(&self) -> &Parser;
309    fn field(&self) -> &'static str;
310    fn offset(&self) -> u64;
311}
312
313impl WrittenValue for AlgorithmFields<'_> {
314    fn oid_bytes(&self) -> &[u8] {
315        self.algorithm.as_bytes()
316    }
317    fn parameter_bytes(&self) -> Option<&[u8]> {
318        parameter_bytes(self.der, self.parameter_start, self.indefinite)
319    }
320    fn valid_null(&self) -> bool {
321        self.null_parameter
322    }
323    fn producer(&self) -> &Parser {
324        self.parser
325    }
326    fn field(&self) -> &'static str {
327        self.field
328    }
329    fn offset(&self) -> u64 {
330        self.offset
331    }
332}
333
334impl WrittenValue for AlgorithmIdentifier<'_> {
335    fn oid_bytes(&self) -> &[u8] {
336        self.algorithm.as_bytes()
337    }
338    fn parameter_bytes(&self) -> Option<&[u8]> {
339        self.parameters()
340    }
341    fn valid_null(&self) -> bool {
342        self.null_parameter
343    }
344    fn producer(&self) -> &Parser {
345        self.parser
346    }
347    fn field(&self) -> &'static str {
348        self.field
349    }
350    fn offset(&self) -> u64 {
351        self.offset
352    }
353}
354
355fn require_written_parsers(
356    left: &impl WrittenValue,
357    right: &impl WrittenValue,
358    parent: &Reader<'_>,
359) -> Result<()> {
360    if !core::ptr::eq(left.producer(), parent.parser()) {
361        return Err(Error::new(
362            ErrorCode::ForeignReader,
363            left.field(),
364            left.offset(),
365        ));
366    }
367    if !core::ptr::eq(right.producer(), parent.parser()) {
368        return Err(Error::new(
369            ErrorCode::ForeignReader,
370            right.field(),
371            right.offset(),
372        ));
373    }
374    Ok(())
375}
376
377fn written_matches(
378    left: &impl WrittenValue,
379    right: &impl WrittenValue,
380    parent: &Reader<'_>,
381    defaults: bool,
382) -> Result<bool> {
383    require_written_parsers(left, right, parent)?;
384    written_values(left, right, parent, defaults)
385}
386
387fn written_values(
388    left: &impl WrittenValue,
389    right: &impl WrittenValue,
390    parent: &Reader<'_>,
391    defaults: bool,
392) -> Result<bool> {
393    let charge = |amount| {
394        parent.charge(amount).map_err(|error| {
395            Error::new(
396                ErrorCode::Encoding(error.code()),
397                right.field(),
398                right.offset(),
399            )
400        })
401    };
402    charge(1)?;
403    let kind = left.oid_bytes();
404    if kind.len() != right.oid_bytes().len() {
405        return Ok(false);
406    }
407    charge(kind.len() as u64)?;
408    if kind != right.oid_bytes() {
409        return Ok(false);
410    }
411    if defaults || (left.valid_null() && right.valid_null()) {
412        return Ok(true);
413    }
414    let (Some(first), Some(second)) = (left.parameter_bytes(), right.parameter_bytes()) else {
415        return Ok(left.parameter_bytes().is_none() && right.parameter_bytes().is_none());
416    };
417    if first.len() != second.len() {
418        return Ok(false);
419    }
420    charge(first.len() as u64)?;
421    Ok(first == second)
422}
423
424impl<'a> AlgorithmIdentifier<'a> {
425    /// Whether a written primitive NULL was validated by the producing reader.
426    /// Original parameter bytes remain unchanged; absence is not NULL here.
427    #[must_use]
428    pub fn has_null_parameter(self) -> bool {
429        self.null_parameter
430    }
431
432    /// Compares written values without assuming algorithm-specific defaults.
433    /// Two validated primitive NULLs share a value despite BER length spelling;
434    /// other parameters retain opaque encoded identity. Both identifiers must
435    /// come from this producing parser. This is not algorithm acceptance.
436    ///
437    /// # Errors
438    /// Returns a located resource refusal or a foreign-parser error before
439    /// comparing identifiers from another parsing session.
440    pub fn same_written_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
441        self.value_matches(other, parent, false)
442    }
443
444    /// Compares RFC5754 §2 SHA224/256/384/512 identifiers with absent/NULL defaults.
445    /// Unknown algorithms retain written-value semantics, not a guessed default.
446    /// Invalid known parameters and resource/caller refusals remain errors.
447    /// Selected-provider support and signature-profile acceptance are separate.
448    /// <https://www.rfc-editor.org/rfc/rfc5754#section-2>
449    ///
450    /// # Errors
451    /// Returns invalid known parameters, foreign-parser misuse, or resource
452    /// refusal separately from an ordinary identity mismatch.
453    pub fn digest_matches_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
454        self.value_matches(other, parent, true)
455    }
456
457    fn value_matches(self, other: &Self, parent: &Reader<'_>, digest: bool) -> Result<bool> {
458        require_written_parsers(&self, other, parent)?;
459        let sha2 = if digest {
460            let known = self.validate_digest_params(parent)?;
461            other.validate_digest_params(parent)?;
462            known
463        } else {
464            false
465        };
466        written_values(&self, other, parent, sha2)
467    }
468
469    fn validate_digest_params(self, parent: &Reader<'_>) -> Result<bool> {
470        let charge = |amount| {
471            parent.charge(amount).map_err(|error| {
472                Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
473            })
474        };
475        charge(1)?;
476        let kind = self.algorithm.as_bytes();
477        if kind.len() != 9 {
478            return Ok(false);
479        }
480        charge(9)?;
481        if kind[..8] != [0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2] || kind[8] < 1 || kind[8] > 4 {
482            return Ok(false);
483        }
484        if self.parameter_start.is_some() && !self.null_parameter {
485            // RFC5754 §2 permits only absent or NULL parameters for these four
486            // SHA2 identifiers. Unknown algorithms get no inferred default.
487            // https://www.rfc-editor.org/rfc/rfc5754#section-2
488            return Err(Error::new(
489                ErrorCode::MalformedValue,
490                self.field,
491                self.offset,
492            ));
493        }
494        Ok(true)
495    }
496    /// Which algorithm.
497    #[must_use]
498    pub fn algorithm(self) -> Oid<'a> {
499        self.algorithm
500    }
501
502    /// The parameters, as the whole encoded element, or `None` where the
503    /// certificate wrote none.
504    ///
505    /// Kept encoded because what they mean depends on the algorithm: the same
506    /// bytes are a curve for one and a hash function and salt length for
507    /// another.
508    #[must_use]
509    pub fn parameters(self) -> Option<&'a [u8]> {
510        parameter_bytes(self.der, self.parameter_start, self.indefinite)
511    }
512
513    /// Original algorithm element offset, without scanning or deriving a new range.
514    #[must_use]
515    pub fn offset(self) -> u64 {
516        self.offset
517    }
518
519    /// Encoding selected by the original schema reader for this identifier.
520    #[must_use]
521    pub fn encoding(self) -> Encoding {
522        self.encoding
523    }
524
525    /// Opens the whole original parameter TLV at its original reader depth.
526    ///
527    /// `parent` must belong to the original parser session; rebinding its
528    /// current execution account is allowed. A foreign session is rejected
529    /// before lookup, work or allocation. Both current funding and the original
530    /// cumulative session ceiling govern all later decoding. Absence is only
531    /// returned after admission, never as a resource fallback.
532    ///
533    /// The returned depth equals an immediate `inside.reread(parameter, encoding)`
534    /// after this identifier was read, including an IMPLICIT context envelope.
535    /// It includes the parameter header, not just its contents; no replacement
536    /// parser, canonical serialization or extra parameter nesting is introduced.
537    pub fn parameters_reader_in<'query>(
538        self,
539        parent: &Reader<'query>,
540        field: &'static str,
541    ) -> Result<Option<Reader<'query>>>
542    where
543        'a: 'query,
544    {
545        if !self.same_session(parent) {
546            return Err(Error::new(ErrorCode::ForeignReader, field, self.offset));
547        }
548        let at = self
549            .parameter_start
550            .map_or(self.offset, |start| self.offset + start.get() as u64);
551        parent
552            .execution()
553            .charge_work(1)
554            .map_err(|reason| Error::resource(reason, field, at))?;
555        let Some(parameter) = self.parameters() else {
556            return Ok(None);
557        };
558        self.parser
559            .read_nested_query_in(parent, parameter, at, self.encoding, self.outer)
560            .field(field)
561            .map(Some)
562    }
563
564    /// Rebind original metadata queries without parsing or owning an account.
565    pub(crate) fn query_in<'operation, W: Work>(
566        &self,
567        execution: &'operation W,
568    ) -> structured_asn1::memory::Execution<'operation>
569    where
570        'a: 'operation,
571    {
572        execution.execution_for(self.parser)
573    }
574
575    pub(crate) fn source_parser(self) -> &'a Parser {
576        self.parser
577    }
578
579    /// Retains only the original parser-session funding capability. No
580    /// bytes are parsed, copied, hashed or interpreted by this projection.
581    #[must_use]
582    pub fn execution_origin(self) -> Producer<'a> {
583        Producer {
584            parser: self.parser,
585        }
586    }
587
588    /// The written parameters, readable in place.
589    pub(crate) fn parameters_nested(self) -> Option<Nested<'a>> {
590        let start = self.parameter_start?.get();
591        Some(self.nested(self.parameters()?, self.offset + start as u64))
592    }
593
594    /// Original bytes at `at` that sit one level inside this identifier's
595    /// reader: its parameters, or the sibling key bits of an enclosing SPKI.
596    pub(crate) fn nested(self, bytes: &'a [u8], at: u64) -> Nested<'a> {
597        Nested {
598            bytes,
599            at,
600            parser: self.parser,
601            outer: self.outer,
602            encoding: self.encoding,
603        }
604    }
605
606    /// Whether this original identifier was produced by this parser session.
607    /// Account equality does not establish source/session identity. This O(1)
608    /// metadata predicate reads no parameters and spends no data-dependent work.
609    #[must_use]
610    pub fn same_session(self, parent: &ScopedReader<'_, '_>) -> bool {
611        core::ptr::eq(self.parser, parent.parser())
612    }
613
614    /// The whole identifier as it was encoded.
615    ///
616    /// For [`read_implicit`], this retains the original context tag, not a
617    /// synthesized SEQUENCE suitable for reparsing as an untagged identifier.
618    #[must_use]
619    pub fn as_bytes(self) -> &'a [u8] {
620        self.der
621    }
622
623    /// Whether the parameters are absent, or the null that means the same
624    /// thing for the algorithms that spell it that way.
625    #[must_use]
626    pub fn has_no_parameters(self) -> bool {
627        // The NULL validated under the document's own encoding, not the DER
628        // bytes `05 00`: BER may write its zero length in long form
629        // (X.690 §8.1.3.5, §8.8; RFC 5652 §1 makes a signed message BER).
630        self.parameters().is_none() || self.null_parameter
631    }
632
633    /// Whether two identifiers name the same algorithm with the same
634    /// parameters.
635    ///
636    /// An absent parameter and an explicit null are treated as the same, and
637    /// nothing else is. Issuers disagree about which of the two to write for
638    /// the algorithms that take no parameters, and both say the same thing;
639    /// any other difference changes what is being verified.
640    ///
641    /// The equivalence is deliberately not algorithm-specific. Which
642    /// parameter encodings an algorithm permits at all, including that the
643    /// Edwards-curve identifiers forbid the null form, is the algorithm
644    /// registry's decision, made against the identifier inside the signed
645    /// bytes. This comparison only asks whether the unsigned outer identifier
646    /// agrees with that signed one, and absent versus null cannot change
647    /// which algorithm that is: an outer null beside a signed absent is a
648    /// malformed spelling of the same identity, not a different algorithm
649    /// escaping the mismatch check.
650    ///
651    /// Deferred comparisons charge the supplied current operation. Their
652    /// resource diagnostics identify `self`'s original field and offset; they
653    /// do not assert that the two identifiers share a source. Parser-local
654    /// limits apply only when comparing through the producing reader.
655    ///
656    /// # Errors
657    /// Returns a resource refusal before an unfunded byte comparison.
658    ///
659    /// # Examples
660    /// ```
661    /// # extern crate alloc;
662    /// # use alloc::sync::Arc;
663    /// # use structured_asn1::{Parser, Tag, universal};
664    /// # use structured_asn1::memory::{Memory, MemoryAccount};
665    /// # let execution = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
666    /// # let parser = Parser::der(execution.clone());
667    /// # let mut reader = parser.read(b"\x30\x04\x06\x02\x2a\x03", 0).unwrap();
668    /// # let element = reader.expect(Tag::constructed(universal::SEQUENCE)).unwrap();
669    /// let identifier = structured_pkix::algorithm::read(&reader, &element, "algorithm")?;
670    /// assert!(identifier.matches(&identifier, &execution)?);
671    /// # Ok::<(), structured_pkix::Error>(())
672    /// ```
673    pub fn matches(self, other: &AlgorithmIdentifier<'_>, execution: &Memory) -> Result<bool> {
674        self.matches_with(other, |work| {
675            execution
676                .charge_work(work)
677                .map_err(|reason| Error::resource(reason, self.field, self.offset))
678        })
679    }
680
681    pub(crate) fn matches_in(
682        self,
683        other: &AlgorithmIdentifier<'_>,
684        parent: &Reader<'_>,
685    ) -> Result<bool> {
686        self.matches_with(other, |work| {
687            parent.charge(work).map_err(|error| {
688                Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
689            })
690        })
691    }
692
693    fn matches_with(
694        self,
695        other: &AlgorithmIdentifier<'_>,
696        mut charge: impl FnMut(u64) -> Result<()>,
697    ) -> Result<bool> {
698        charge(1)?;
699        if self.algorithm.as_bytes().len() != other.algorithm.as_bytes().len() {
700            return Ok(false);
701        }
702        charge(self.algorithm.as_bytes().len() as u64)?;
703        if self.algorithm != other.algorithm {
704            return Ok(false);
705        }
706        if self.has_no_parameters() && other.has_no_parameters() {
707            return Ok(true);
708        }
709        let (Some(left), Some(right)) = (self.parameters(), other.parameters()) else {
710            return Ok(false);
711        };
712        if left.len() != right.len() {
713            return Ok(false);
714        }
715        charge(left.len() as u64)?;
716        Ok(left == right)
717    }
718}
719
720/// Reads an algorithm identifier from an element a schema said is one.
721///
722/// It is public because certificates are not the only place this structure
723/// appears: a signed message names a digest and a signature algorithm the same
724/// way, and reading them a second time somewhere else would be a second set of
725/// rules about what parameters are allowed to look like.
726///
727/// # Errors
728///
729/// Reports the encoding fault and the field it was in.
730pub fn read<'a>(
731    parent: &ScopedReader<'a, '_>,
732    element: &Element<'a>,
733    field: &'static str,
734) -> Result<AlgorithmIdentifier<'a>> {
735    read_tagged(
736        parent,
737        element,
738        Tag::constructed(universal::SEQUENCE),
739        field,
740    )
741}
742
743/// Reads an original `[number] IMPLICIT AlgorithmIdentifier` without retagging.
744///
745/// The caller supplies the tag number selected by its trusted schema. For
746/// example, `CMSAlgorithmProtection` uses `[1]` for a signature and `[2]` for a
747/// MAC (RFC6211 §2); that cardinality/choice rule belongs to the CMS schema.
748/// The same inner algorithm/parameter core as [`read`] preserves original
749/// bytes, encoding, inherited depth and current producing execution ceilings.
750/// <https://www.rfc-editor.org/rfc/rfc6211#section-2>
751pub fn read_implicit<'a>(
752    parent: &Reader<'a>,
753    element: &Element<'a>,
754    number: u32,
755    field: &'static str,
756) -> Result<AlgorithmIdentifier<'a>> {
757    read_tagged(parent, element, Tag::context(true, number), field)
758}
759
760/// Inspects a uniquely shaped identifier while retaining parameter syntax faults.
761///
762/// The OID and at most one whole parameter must be unambiguous. Invalid
763/// parameter syntax is preserved as [`AlgorithmReading::fault`], never as a
764/// validated identifier. Resource/caller refusals, missing fields and ambiguous
765/// schema remain errors. This does not authorize malformed crypto input.
766pub fn read_inspection<'a>(
767    parent: &Reader<'a>,
768    element: &Element<'a>,
769    field: &'static str,
770) -> Result<AlgorithmReading<'a>> {
771    read_fields::<true>(
772        parent,
773        element,
774        Tag::constructed(universal::SEQUENCE),
775        field,
776    )
777}
778
779/// Original IMPLICIT counterpart of [`read_inspection`], sharing the same kernel.
780pub fn read_implicit_inspection<'a>(
781    parent: &Reader<'a>,
782    element: &Element<'a>,
783    number: u32,
784    field: &'static str,
785) -> Result<AlgorithmReading<'a>> {
786    read_fields::<true>(parent, element, Tag::context(true, number), field)
787}
788
789fn read_fields<'a, const INSPECTION: bool>(
790    parent: &ScopedReader<'a, '_>,
791    element: &Element<'a>,
792    expected: Tag,
793    field: &'static str,
794) -> Result<AlgorithmReading<'a>> {
795    let fail = |code| Error::new(code, field, element.span().start);
796    if element.tag() != expected {
797        return Err(fail(ErrorCode::Encoding(
798            structured_asn1::ErrorCode::UnexpectedTag,
799        )));
800    }
801    let mut inside = parent.enter(element).field(field)?;
802    let algorithm = inside.read_oid().field(field)?;
803    let parameter = inside.next_element().field(field)?;
804    let mut fault = None;
805    let mut null_parameter = false;
806    if let Some(parameter) = &parameter {
807        match crate::name::walk_der(&inside, parameter, field, crate::name::Strings::Checked) {
808            Ok(()) => {
809                // The shared walker validates primitive NULL contents. A
810                // constructed/nonempty NULL cannot acquire this cached fact.
811                // X.690 (02/2021) §8.8.1–2: primitive, with empty contents.
812                // https://www.itu.int/rec/T-REC-X.690-202102-I/en
813                null_parameter = parameter.tag() == Tag::primitive(universal::NULL);
814            }
815            Err(error) => {
816                if !INSPECTION || error.is_resource() || error.code() == ErrorCode::ForeignReader {
817                    return Err(error);
818                }
819                fault = Some(error);
820            }
821        }
822    }
823    if !inside.is_empty() {
824        return Err(fail(ErrorCode::TrailingData));
825    }
826    let parameter_start = match parameter {
827        None => None,
828        Some(parameter) => {
829            let start = usize::try_from(parameter.span().start - element.span().start)
830                .map_err(|_| fail(ErrorCode::ValueTooLarge))?;
831            Some(NonZeroUsize::new(start).expect("the algorithm OID precedes its parameters"))
832        }
833    };
834    Ok(AlgorithmReading {
835        fields: AlgorithmFields {
836            algorithm,
837            parameter_start,
838            null_parameter,
839            der: element.as_bytes(),
840            offset: element.span().start,
841            field,
842            parser: parent.parser(),
843            outer: parent.depth(),
844            encoding: inside.encoding(),
845            indefinite: element.is_indefinite(),
846        },
847        fault,
848    })
849}
850
851fn read_tagged<'a>(
852    parent: &ScopedReader<'a, '_>,
853    element: &Element<'a>,
854    expected: Tag,
855    field: &'static str,
856) -> Result<AlgorithmIdentifier<'a>> {
857    read_fields::<false>(parent, element, expected, field)?.into_identifier()
858}
859
860#[cfg(test)]
861mod tests;