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