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