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