Skip to main content

structured_pkix/algorithm/
pss.rs

1//! Borrowed RSASSA-PSS parameters, without selecting a cryptographic provider.
2//!
3//! RFC4055 §3.1 distinguishes absent key restrictions from absent signature
4//! parameters. [`read_in`] preserves absence; its caller applies that role rule.
5//! Hash/MGF support, key sanity, salt bounds and signature verification remain
6//! with the selected provider. No replacement parser or encoded default is made.
7//! Retained identifiers use [`read`] or [`inspect`] under current execution;
8//! [`read_in`] and [`inspect_in`] additionally check a producing reader session.
9
10use super::AlgorithmIdentifier;
11use crate::error::{Error, ErrorCode, Located, Result};
12use structured_asn1::memory::{OwnedVec, Work};
13use structured_asn1::{Element, Integer, Oid, ScopedReader as Reader, Tag, universal};
14
15use crate::oid::SHA1;
16pub mod profile;
17const MGF1: Oid<'static> = Oid::from_bytes(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 8]);
18const FIELD: &str = "algorithm.pss";
19
20/// An explicitly written field with its original EXPLICIT wrapper.
21#[derive(Debug, Clone, Copy)]
22pub struct Written<'a, T> {
23    element: Element<'a>,
24    value: T,
25}
26
27impl<'a, T: Copy> Written<'a, T> {
28    /// Original context-specific wrapper, including its source span.
29    #[must_use]
30    pub fn element(self) -> Element<'a> {
31        self.element
32    }
33
34    /// Decoded borrowed value, without re-encoding the field.
35    #[must_use]
36    pub fn value(self) -> T {
37        self.value
38    }
39}
40
41/// A numeric field, distinguishing omission from an explicit value.
42#[derive(Debug, Clone, Copy)]
43pub enum Number<'a> {
44    /// The schema's omitted value (salt 20 or trailer 1).
45    Default(u64),
46    /// An arbitrary-width original INTEGER, including its sign.
47    Written(Written<'a, Integer<'a>>),
48}
49
50impl Number<'_> {
51    /// Bounded numeric projection; negative/wider values remain `None`.
52    #[must_use]
53    pub fn as_u64(self) -> Option<u64> {
54        match self {
55            Self::Default(value) => Some(value),
56            Self::Written(value) => value.value.as_u64(),
57        }
58    }
59}
60
61/// The mask function and, for the defined MGF1 schema, its original hash.
62#[derive(Debug, Clone, Copy)]
63pub enum Mask<'a> {
64    /// Omitted maskGenAlgorithm means MGF1 with SHA-1.
65    DefaultMgf1Sha1,
66    /// Written MGF1 requires a hash `AlgorithmIdentifier` parameter.
67    Mgf1 {
68        /// Original explicit mask field and function identifier.
69        function: Written<'a, AlgorithmIdentifier<'a>>,
70        /// Original nested hash identifier, not a manufactured default.
71        hash: AlgorithmIdentifier<'a>,
72    },
73    /// Another mask function, retaining opaque parameters for its provider.
74    Other(Written<'a, AlgorithmIdentifier<'a>>),
75}
76
77/// Original PSS schema values, not algorithm support or signature validity.
78#[derive(Debug, Clone, Copy)]
79pub struct Parameters<'a> {
80    parser: &'a structured_asn1::Parser,
81    element: Element<'a>,
82    hash: Option<Written<'a, AlgorithmIdentifier<'a>>>,
83    mask: Mask<'a>,
84    salt: Number<'a>,
85    trailer: Number<'a>,
86    defaults: u8,
87}
88
89impl<'a> Parameters<'a> {
90    /// Complete original parameter SEQUENCE, without copies or normalization.
91    #[must_use]
92    pub fn element(self) -> Element<'a> {
93        self.element
94    }
95
96    /// Written hash identifier; `None` is the schema's SHA-1 default.
97    #[must_use]
98    pub fn hash(self) -> Option<Written<'a, AlgorithmIdentifier<'a>>> {
99        self.hash
100    }
101
102    /// Mask identity and original MGF1 hash, including omitted default.
103    #[must_use]
104    pub fn mask(self) -> Mask<'a> {
105        self.mask
106    }
107
108    /// Original written fields equal to their ASN.1 DEFAULT values.
109    /// The shared decoder decides this once, including the exact SHA-1 NULL
110    /// default. A DER encoder omits these whole EXPLICIT wrappers (X.690
111    /// §11.5); algorithm-support/default resolution remains independent.
112    pub fn written_defaults(self) -> impl Iterator<Item = Element<'a>> {
113        let hash = self.hash.map(Written::element);
114        let mask = match self.mask {
115            Mask::Mgf1 { function, .. } | Mask::Other(function) => Some(function.element()),
116            Mask::DefaultMgf1Sha1 => None,
117        };
118        let salt = match self.salt {
119            Number::Written(value) => Some(value.element()),
120            Number::Default(_) => None,
121        };
122        let trailer = match self.trailer {
123            Number::Written(value) => Some(value.element()),
124            Number::Default(_) => None,
125        };
126        [hash, mask, salt, trailer]
127            .into_iter()
128            .enumerate()
129            .filter_map(move |(number, element)| {
130                if self.defaults & (1 << number) != 0 {
131                    element
132                } else {
133                    None
134                }
135            })
136    }
137
138    /// Salt octet length (default 20, independently of the selected hash).
139    /// Its applicability as an exact signature length or key minimum is a
140    /// caller role decision (RFC4055 §3.3); no backend maximum is imposed here.
141    #[must_use]
142    pub fn salt(self) -> Number<'a> {
143        self.salt
144    }
145
146    /// Trailer number (default 1). Other numbers are retained for inspection;
147    /// RFC8017 A.2.3 defines support only for trailer 1, not other schemes.
148    #[must_use]
149    pub fn trailer(self) -> Number<'a> {
150        self.trailer
151    }
152}
153
154/// Readable fields and retained encoding diagnostics, not conforming evidence.
155#[derive(Debug)]
156pub struct Reading<'a> {
157    parameters: Parameters<'a>,
158    faults: OwnedVec<Error>,
159}
160
161impl<'a> Reading<'a> {
162    /// Unmodified original fields, even when their spelling has a diagnostic.
163    #[must_use]
164    pub fn parameters(&self) -> Parameters<'a> {
165        self.parameters
166    }
167
168    /// Located DER DEFAULT spelling diagnostics.
169    /// Resource and foreign-reader failures are terminal errors, never faults.
170    #[must_use]
171    pub fn faults(&self) -> &[Error] {
172        &self.faults
173    }
174
175    pub(super) const fn parameters_ref(&self) -> &Parameters<'a> {
176        &self.parameters
177    }
178}
179
180/// Reads present PSS parameters in the producing session; preserves absence.
181///
182/// Callers reject absent signature parameters but may accept an unrestricted
183/// PSS key (RFC4055 §3.1). This function does neither role selection nor policy.
184/// Original encoding, offsets, depth and local/current work ceilings survive.
185///
186/// ```
187/// use std::sync::Arc;
188/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
189/// use structured_pkix::algorithm::{self, pss};
190/// let parser = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
191/// let bytes = b"\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x00";
192/// let mut reader = parser.read(bytes, 400).unwrap();
193/// let element = reader.next_element().unwrap().unwrap();
194/// let algorithm = algorithm::read(&reader, &element, "signatureAlgorithm")?;
195/// let parameters = pss::read_in(algorithm, &reader)?.unwrap();
196/// assert!(parameters.hash().is_none());
197/// assert_eq!(parameters.salt().as_u64(), Some(20));
198/// assert_eq!(parameters.element().span().start, 413);
199/// # Ok::<(), structured_pkix::Error>(())
200/// ```
201pub fn read_in<'a>(
202    identifier: AlgorithmIdentifier<'a>,
203    parent: &Reader<'a, '_>,
204) -> Result<Option<Parameters<'a>>> {
205    require_session(identifier, parent)?;
206    read(identifier, &parent.execution())
207}
208
209/// Inspection counterpart of [`read_in`], sharing its schema kernel.
210///
211/// RFC4055 §3.1 requires recognizing explicitly written defaults during
212/// verification. Inspection therefore retains those values plus a DER spelling
213/// fault (X.690 §11.5), rather than losing the algorithm's meaning. BER permits
214/// written defaults. Neither operation verifies a signature or accepts a key.
215/// <https://www.rfc-editor.org/rfc/rfc4055#section-3.1>
216pub fn inspect_in<'a>(
217    identifier: AlgorithmIdentifier<'a>,
218    parent: &Reader<'a, '_>,
219) -> Result<Option<Reading<'a>>> {
220    require_session(identifier, parent)?;
221    inspect(identifier, &parent.execution())
222}
223
224/// Reads a retained identifier's original PSS parameters under current work.
225///
226/// For verification callbacks that retain an [`AlgorithmIdentifier`] without
227/// its former reader. Source parser, encoding, parameter depth and cumulative
228/// ceiling come from that identifier; `execution` supplies current funding and
229/// its root-operation ceiling. No reader is fabricated and no parser, resource
230/// account, parameter copy or default encoding is created.
231///
232/// Like [`read_in`], returns `None` for absent parameters. RFC4055 §3.1 and
233/// RFC5756 §2 require signature parameters, while absent PSS key parameters
234/// mean unrestricted PSS usage; that role is the caller's decision. Use the
235/// key's original algorithm identifier: absent PSS restrictions do not inherit
236/// from another certificate (RFC4055 §3.3). No backend acceptance is implied.
237/// <https://www.rfc-editor.org/rfc/rfc5756#section-2>
238///
239/// # Errors
240/// Reports located schema/encoding errors, including written DER defaults,
241/// separately from terminal source/current work, depth or storage refusal.
242/// Returned scalar views borrow the original source, not current execution.
243///
244/// # Examples
245/// ```
246/// use std::sync::Arc;
247/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
248/// use structured_pkix::algorithm::{self, pss};
249/// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
250/// let bytes = b"\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x00";
251/// let identifier = {
252///     let mut reader = source.read(bytes, 400).unwrap();
253///     let element = reader.next_element().unwrap().unwrap();
254///     algorithm::read(&reader, &element, "signatureAlgorithm")?
255/// };
256/// let current = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
257/// let parameters = pss::read(identifier, &current)?.unwrap();
258/// assert_eq!(parameters.element().span().start, 413);
259/// assert_eq!(parameters.salt().as_u64(), Some(20));
260/// # Ok::<(), structured_pkix::Error>(())
261/// ```
262pub fn read<'a, W: Work>(
263    identifier: AlgorithmIdentifier<'a>,
264    execution: &W,
265) -> Result<Option<Parameters<'a>>> {
266    require_algorithm(identifier, execution)?;
267    read_known(identifier, execution)
268}
269
270// Only a caller that has already paid for and classified the PSS OID may skip
271// this check (signature classification or key-restriction interpretation).
272// Parameter traversal still uses the same source/current schema kernel.
273pub(super) fn read_known<'a, W: Work>(
274    identifier: AlgorithmIdentifier<'a>,
275    execution: &W,
276) -> Result<Option<Parameters<'a>>> {
277    let Some((reader, element)) = open_parameters(identifier, execution)? else {
278        return Ok(None);
279    };
280    decode(&reader, element, Err).map(Some)
281}
282
283/// Inspects retained PSS parameters through the same kernel as [`read`].
284///
285/// Readable DER DEFAULT spelling faults are retained on the current storage
286/// account. All diagnostic admission also obeys the original and current work
287/// ceilings. The result can outlive the temporary operation; its fields still
288/// borrow the original source and its diagnostic capacity stays charged until
289/// dropped. Resource failures never become missing faults or successful absence.
290///
291/// # Examples
292/// ```
293/// use std::sync::Arc;
294/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
295/// use structured_pkix::{ErrorCode, algorithm::{self, pss}};
296/// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
297/// let bytes = b"\x30\x12\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x05\xa2\x03\x02\x01\x14";
298/// let mut reader = source.read(bytes, 400).unwrap();
299/// let element = reader.next_element().unwrap().unwrap();
300/// let identifier = algorithm::read(&reader, &element, "signatureAlgorithm")?;
301/// let current = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
302/// let reading = pss::inspect(identifier, &current)?.unwrap();
303/// assert_eq!(reading.parameters().salt().as_u64(), Some(20));
304/// assert_eq!(reading.faults()[0].code(), ErrorCode::DefaultWritten);
305/// # Ok::<(), structured_pkix::Error>(())
306/// ```
307pub fn inspect<'a, W: Work>(
308    identifier: AlgorithmIdentifier<'a>,
309    execution: &W,
310) -> Result<Option<Reading<'a>>> {
311    require_algorithm(identifier, execution)?;
312    inspect_known(identifier, execution)
313}
314
315// The signature classifier has already admitted and recognized the OID.
316pub(super) fn inspect_known<'a, W: Work>(
317    identifier: AlgorithmIdentifier<'a>,
318    execution: &W,
319) -> Result<Option<Reading<'a>>> {
320    let Some((reader, element)) = open_parameters(identifier, execution)? else {
321        return Ok(None);
322    };
323    let query = identifier.query_in(execution);
324    let mut faults = query.vector();
325    let parameters = decode(&reader, element, |fault| {
326        let at = fault.offset();
327        let field = fault.field();
328        faults
329            .try_push(fault)
330            .map_err(|reason| Error::resource(reason, field, at))
331    })?;
332    Ok(Some(Reading {
333        parameters,
334        faults: faults.into_owned(),
335    }))
336}
337
338fn require_algorithm<W: Work>(identifier: AlgorithmIdentifier<'_>, execution: &W) -> Result<()> {
339    let query = identifier.query_in(execution);
340    if !crate::key::same_oid(
341        identifier.algorithm(),
342        crate::oid::RSASSA_PSS,
343        &query,
344        FIELD,
345        identifier.offset(),
346    )? {
347        return Err(Error::new(
348            ErrorCode::UnexpectedField,
349            FIELD,
350            identifier.offset(),
351        ));
352    }
353    Ok(())
354}
355
356fn open_parameters<'a: 'operation, 'operation, W: Work>(
357    identifier: AlgorithmIdentifier<'a>,
358    execution: &'operation W,
359) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
360    let query = identifier.query_in(execution);
361    query
362        .charge_work(1)
363        .map_err(|reason| Error::resource(reason, FIELD, identifier.offset()))?;
364    let value = identifier
365        .parameters_nested()
366        .map(|parameters| parameters.open(execution, FIELD))
367        .transpose()?;
368    if let Some((_, element)) = &value
369        && element.tag() != Tag::constructed(universal::SEQUENCE)
370    {
371        return Err(Error::new(
372            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
373            FIELD,
374            element.span().start,
375        ));
376    }
377    Ok(value)
378}
379
380fn require_session(identifier: AlgorithmIdentifier<'_>, parent: &Reader<'_, '_>) -> Result<()> {
381    if !identifier.same_session(parent) {
382        return Err(Error::new(
383            ErrorCode::ForeignReader,
384            FIELD,
385            identifier.offset(),
386        ));
387    }
388    Ok(())
389}
390
391fn open_parameter<'a: 'operation, 'operation, W: Work>(
392    identifier: AlgorithmIdentifier<'a>,
393    parent: &Reader<'a, '_>,
394    execution: &'operation W,
395    field: &'static str,
396) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
397    charge(parent, 1, identifier.offset())?;
398    let Some(parameters) = identifier.parameters_nested() else {
399        return Ok(None);
400    };
401    parameters.open(execution, field).map(Some)
402}
403
404fn decode<'a>(
405    parent: &Reader<'a, '_>,
406    element: Element<'a>,
407    mut fault: impl FnMut(Error) -> Result<()>,
408) -> Result<Parameters<'a>> {
409    let mut fields = parent.enter(&element).field(FIELD)?;
410    let mut parameters = Parameters {
411        parser: parent.parser(),
412        element,
413        hash: None,
414        mask: Mask::DefaultMgf1Sha1,
415        salt: Number::Default(20),
416        trailer: Number::Default(1),
417        defaults: 0,
418    };
419    let mut previous = None;
420    while let Some(element) = fields.next_element().field(FIELD)? {
421        fields.charge(1).field(FIELD)?;
422        let Some(number) = element.tag().number() else {
423            return Err(Error::new(
424                ErrorCode::UnexpectedField,
425                FIELD,
426                element.span().start,
427            ));
428        };
429        if number > 3
430            || element.tag() != Tag::context(true, number)
431            || previous.is_some_and(|last| number <= last)
432        {
433            return Err(Error::new(
434                ErrorCode::UnexpectedField,
435                FIELD,
436                element.span().start,
437            ));
438        }
439        previous = Some(number);
440        let mut inside = fields.enter(&element).field(FIELD)?;
441        let value = inside
442            .next_element()
443            .field(FIELD)?
444            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, inside.offset()))?;
445        if !inside.is_empty() {
446            return Err(Error::new(ErrorCode::TrailingData, FIELD, inside.offset()));
447        }
448        let defaulted = match number {
449            0 => {
450                let hash = super::read(&inside, &value, "algorithm.pss.hash")?;
451                parameters.hash = Some(Written {
452                    element,
453                    value: hash,
454                });
455                default_hash(&inside, hash)?
456            }
457            1 => {
458                let (mask, defaulted) = read_mask(&inside, element, value)?;
459                parameters.mask = mask;
460                defaulted
461            }
462            _ => {
463                if value.tag() != Tag::primitive(universal::INTEGER) {
464                    return Err(Error::new(
465                        ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
466                        FIELD,
467                        value.span().start,
468                    ));
469                }
470                let integer = inside.integer(&value).field(FIELD)?;
471                if number == 2 && integer.is_negative() {
472                    return Err(Error::new(
473                        ErrorCode::MalformedValue,
474                        FIELD,
475                        value.span().start,
476                    ));
477                }
478                charge(&inside, 9, value.span().start)?;
479                let written = Number::Written(Written {
480                    element,
481                    value: integer,
482                });
483                if number == 2 {
484                    parameters.salt = written;
485                    integer.as_u64() == Some(20)
486                } else {
487                    parameters.trailer = written;
488                    integer.as_u64() == Some(1)
489                }
490            }
491        };
492        // X.690 §11.5 restricts DER spelling, independently of RFC4055's
493        // requirement that verification recognize explicit default values.
494        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
495        if defaulted {
496            parameters.defaults |= 1 << number;
497        }
498        if defaulted && fields.encoding().is_der() {
499            fault(Error::new(
500                ErrorCode::DefaultWritten,
501                FIELD,
502                element.span().start,
503            ))?;
504        }
505    }
506    Ok(parameters)
507}
508
509fn default_hash(parent: &Reader<'_, '_>, identifier: AlgorithmIdentifier<'_>) -> Result<bool> {
510    // RFC4055 §§2.1/6 defines the ASN.1 sha1Identifier DEFAULT with NULL.
511    // Absent parameters have equivalent algorithm semantics but are a
512    // distinct ASN.1 value; do not manufacture NULL for X.690 §11.5.
513    // https://www.rfc-editor.org/rfc/rfc4055#section-2.1
514    Ok(
515        oid_is(parent, identifier.algorithm(), SHA1, identifier.offset())?
516            && identifier.has_null_parameter(),
517    )
518}
519
520fn oid_is(parent: &Reader<'_, '_>, left: Oid<'_>, right: Oid<'_>, at: u64) -> Result<bool> {
521    charge(parent, 1, at)?;
522    if left.as_bytes().len() != right.as_bytes().len() {
523        return Ok(false);
524    }
525    charge(parent, left.as_bytes().len() as u64, at)?;
526    Ok(left == right)
527}
528
529fn charge(parent: &Reader<'_, '_>, amount: u64, at: u64) -> Result<()> {
530    parent
531        .charge(amount)
532        .map_err(|error| Error::new(ErrorCode::Encoding(error.code()), FIELD, at))
533}
534
535fn read_mask<'a>(
536    parent: &Reader<'a, '_>,
537    element: Element<'a>,
538    value: Element<'a>,
539) -> Result<(Mask<'a>, bool)> {
540    let algorithm = super::read(parent, &value, "algorithm.pss.mask")?;
541    let function = Written {
542        element,
543        value: algorithm,
544    };
545    if !oid_is(parent, algorithm.algorithm(), MGF1, algorithm.offset())? {
546        return Ok((Mask::Other(function), false));
547    }
548    let execution = parent.execution();
549    let (reader, element) =
550        open_parameter(algorithm, parent, &execution, "algorithm.pss.mask.hash")?.ok_or_else(
551            || {
552                Error::new(
553                    ErrorCode::MissingField,
554                    "algorithm.pss.mask.hash",
555                    algorithm.offset(),
556                )
557            },
558        )?;
559    let hash = super::read(&reader, &element, "algorithm.pss.mask.hash")?;
560    Ok((Mask::Mgf1 { function, hash }, default_hash(&reader, hash)?))
561}