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