Skip to main content

structured_pkix/algorithm/signature/
inspection.rs

1//! Signature-role profile facts, separate from strict acceptance.
2
3use super::{AlgorithmIdentifier, Scheme, check_parameters, classify, pss};
4use crate::{Error, ErrorCode, Result};
5use structured_asn1::memory::Work;
6
7/// A scoped recognized scheme and its readable parameter-profile diagnostics.
8///
9/// This is not a validated [`super::Algorithm`] or permission to call a provider.
10/// Fixed-scheme recognition survives unwanted parameters; their original bytes
11/// remain available through [`Self::identifier`]. No parameter is rewritten.
12#[derive(Debug)]
13pub struct Inspection<'a, 'parameters> {
14    identifier: AlgorithmIdentifier<'a>,
15    scheme: Option<Scheme>,
16    parameters: Option<&'parameters pss::Parameters<'a>>,
17    faults: &'parameters [Error],
18}
19
20impl<'a> Inspection<'a, '_> {
21    /// Original identifier, including its source, encoding and parameter bytes.
22    #[must_use]
23    pub const fn identifier(&self) -> AlgorithmIdentifier<'a> {
24        self.identifier
25    }
26
27    /// Recognized OID identity; unknown OIDs are not inferred from parameters.
28    #[must_use]
29    pub const fn scheme(&self) -> Option<Scheme> {
30        self.scheme
31    }
32
33    /// Actual readable PSS parameters, absent for fixed schemes or missing PSS.
34    /// Missing mandatory signature parameters have a separate located fault.
35    #[must_use]
36    pub const fn parameters(&self) -> Option<&pss::Parameters<'a>> {
37        self.parameters
38    }
39
40    /// Located profile faults, independent of provider support and mathematics.
41    /// Empty for unknown algorithms, whose conformance is not assessed here.
42    #[must_use]
43    pub const fn faults(&self) -> &[Error] {
44        self.faults
45    }
46}
47
48/// Inspects signature-role parameters without weakening [`super::with_algorithm`].
49///
50/// Uses exactly the strict resolver's recognized OIDs and parameter rules.
51/// Readable NULL/absence violations retain their scheme and original fault;
52/// this API does not decide whether mathematical verification is appropriate.
53/// Fixed cases allocate nothing. PSS uses [`pss::inspect`]'s schema kernel and
54/// accounted diagnostics, including readable DER written DEFAULTs. Missing PSS
55/// signature parameters retain `Some(Scheme::RsaPss)` plus `MissingField`, with
56/// no fabricated parameters (RFC4055 ยง3.1). Optional PSS key restrictions still
57/// use `pss::inspect`, where absence is unrestricted, not a signature fault.
58/// <https://www.rfc-editor.org/rfc/rfc4055#section-3.1>
59///
60/// Original/current ceilings apply before classification or parameter traversal.
61/// The callback borrows temporary parameters and diagnostics only for the visit;
62/// its result is returned unchanged, including caller-specific errors. Original
63/// document views may escape, but temporary descriptor references may not.
64///
65/// # Errors
66/// Malformed or ambiguous PSS grammar and terminal resource/processing refusal
67/// prevent the visit. Structural ASN.1 inspection is a separate operation:
68/// [`super::super::read_inspection`] does not fabricate a valid identifier from
69/// malformed parameter encoding for this function.
70///
71/// # Examples
72/// ```
73/// use std::sync::Arc;
74/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
75/// use structured_pkix::algorithm::{self, digest, signature};
76/// let work = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
77/// let source = Parser::der(work.clone());
78/// // ecdsa-with-SHA256 with a readable but forbidden NULL parameter.
79/// let bytes = b"\x30\x0c\x06\x08\x2a\x86\x48\xce\x3d\x04\x03\x02\x05\x00";
80/// let mut reader = source.read(bytes, 400).unwrap();
81/// let element = reader.next_element().unwrap().unwrap();
82/// let identifier = algorithm::read(&reader, &element, "signatureAlgorithm")?;
83/// signature::with_inspection(identifier, &work, |reading| {
84///     assert_eq!(reading.scheme(), Some(signature::Scheme::Ecdsa(digest::Algorithm::Sha256)));
85///     assert_eq!(reading.faults()[0].offset(), 412);
86/// })?;
87/// assert!(signature::with_algorithm(identifier, &work, |_| ()).is_err());
88/// # Ok::<(), structured_pkix::Error>(())
89/// ```
90/// A reference to a temporary diagnostic or PSS descriptor cannot escape:
91/// ```compile_fail
92/// use structured_pkix::{Error, algorithm::{AlgorithmIdentifier, signature}};
93/// use structured_asn1::memory::Memory;
94/// fn escape<'a>(id: AlgorithmIdentifier<'a>, work: &Memory) -> &'a [Error] {
95///     signature::with_inspection(id, work, |reading| reading.faults()).unwrap()
96/// }
97/// ```
98pub fn with_inspection<'a, W: Work, R>(
99    identifier: AlgorithmIdentifier<'a>,
100    execution: &W,
101    visitor: impl for<'parameters> FnOnce(Inspection<'a, 'parameters>) -> R,
102) -> Result<R> {
103    let classified = classify(identifier, execution)?;
104    let scheme = classified.map(|(scheme, _)| scheme);
105    if scheme == Some(Scheme::RsaPss) {
106        let reading = pss::inspect_known(identifier, execution)?;
107        return Ok(match &reading {
108            Some(reading) => visitor(Inspection {
109                identifier,
110                scheme,
111                parameters: Some(reading.parameters_ref()),
112                faults: reading.faults(),
113            }),
114            None => visitor(Inspection {
115                identifier,
116                scheme,
117                parameters: None,
118                faults: &[Error::new(
119                    ErrorCode::MissingField,
120                    identifier.field,
121                    identifier.offset(),
122                )],
123            }),
124        });
125    }
126    let fault = classified.and_then(|(_, rule)| check_parameters(identifier, rule).err());
127    Ok(visitor(Inspection {
128        identifier,
129        scheme,
130        parameters: None,
131        faults: fault.as_slice(),
132    }))
133}