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
176/// Reads present PSS parameters in the producing session; preserves absence.
177///
178/// Callers reject absent signature parameters but may accept an unrestricted
179/// PSS key (RFC4055 §3.1). This function does neither role selection nor policy.
180/// Original encoding, offsets, depth and local/current work ceilings survive.
181///
182/// ```
183/// use std::sync::Arc;
184/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
185/// use structured_pkix::algorithm::{self, pss};
186/// let parser = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
187/// let bytes = b"\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x00";
188/// let mut reader = parser.read(bytes, 400).unwrap();
189/// let element = reader.next_element().unwrap().unwrap();
190/// let algorithm = algorithm::read(&reader, &element, "signatureAlgorithm")?;
191/// let parameters = pss::read_in(algorithm, &reader)?.unwrap();
192/// assert!(parameters.hash().is_none());
193/// assert_eq!(parameters.salt().as_u64(), Some(20));
194/// assert_eq!(parameters.element().span().start, 413);
195/// # Ok::<(), structured_pkix::Error>(())
196/// ```
197pub fn read_in<'a>(
198    identifier: AlgorithmIdentifier<'a>,
199    parent: &Reader<'a, '_>,
200) -> Result<Option<Parameters<'a>>> {
201    require_session(identifier, parent)?;
202    read(identifier, &parent.execution())
203}
204
205/// Inspection counterpart of [`read_in`], sharing its schema kernel.
206///
207/// RFC4055 §3.1 requires recognizing explicitly written defaults during
208/// verification. Inspection therefore retains those values plus a DER spelling
209/// fault (X.690 §11.5), rather than losing the algorithm's meaning. BER permits
210/// written defaults. Neither operation verifies a signature or accepts a key.
211/// <https://www.rfc-editor.org/rfc/rfc4055#section-3.1>
212pub fn inspect_in<'a>(
213    identifier: AlgorithmIdentifier<'a>,
214    parent: &Reader<'a, '_>,
215) -> Result<Option<Reading<'a>>> {
216    require_session(identifier, parent)?;
217    inspect(identifier, &parent.execution())
218}
219
220/// Reads a retained identifier's original PSS parameters under current work.
221///
222/// For verification callbacks that retain an [`AlgorithmIdentifier`] without
223/// its former reader. Source parser, encoding, parameter depth and cumulative
224/// ceiling come from that identifier; `execution` supplies current funding and
225/// its root-operation ceiling. No reader is fabricated and no parser, resource
226/// account, parameter copy or default encoding is created.
227///
228/// Like [`read_in`], returns `None` for absent parameters. RFC4055 §3.1 and
229/// RFC5756 §2 require signature parameters, while absent PSS key parameters
230/// mean unrestricted PSS usage; that role is the caller's decision. Use the
231/// key's original algorithm identifier: absent PSS restrictions do not inherit
232/// from another certificate (RFC4055 §3.3). No backend acceptance is implied.
233/// <https://www.rfc-editor.org/rfc/rfc5756#section-2>
234///
235/// # Errors
236/// Reports located schema/encoding errors, including written DER defaults,
237/// separately from terminal source/current work, depth or storage refusal.
238/// Returned scalar views borrow the original source, not current execution.
239///
240/// # Examples
241/// ```
242/// use std::sync::Arc;
243/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
244/// use structured_pkix::algorithm::{self, pss};
245/// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
246/// let bytes = b"\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x00";
247/// let identifier = {
248///     let mut reader = source.read(bytes, 400).unwrap();
249///     let element = reader.next_element().unwrap().unwrap();
250///     algorithm::read(&reader, &element, "signatureAlgorithm")?
251/// };
252/// let current = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
253/// let parameters = pss::read(identifier, &current)?.unwrap();
254/// assert_eq!(parameters.element().span().start, 413);
255/// assert_eq!(parameters.salt().as_u64(), Some(20));
256/// # Ok::<(), structured_pkix::Error>(())
257/// ```
258pub fn read<'a, W: Work>(
259    identifier: AlgorithmIdentifier<'a>,
260    execution: &W,
261) -> Result<Option<Parameters<'a>>> {
262    require_algorithm(identifier, execution)?;
263    read_known(identifier, execution)
264}
265
266// Only a caller that has already paid for and classified the PSS OID may skip
267// this check (signature classification or key-restriction interpretation).
268// Parameter traversal still uses the same source/current schema kernel.
269pub(super) fn read_known<'a, W: Work>(
270    identifier: AlgorithmIdentifier<'a>,
271    execution: &W,
272) -> Result<Option<Parameters<'a>>> {
273    let Some((reader, element)) = open_parameters(identifier, execution)? else {
274        return Ok(None);
275    };
276    decode(&reader, element, Err).map(Some)
277}
278
279/// Inspects retained PSS parameters through the same kernel as [`read`].
280///
281/// Readable DER DEFAULT spelling faults are retained on the current storage
282/// account. All diagnostic admission also obeys the original and current work
283/// ceilings. The result can outlive the temporary operation; its fields still
284/// borrow the original source and its diagnostic capacity stays charged until
285/// dropped. Resource failures never become missing faults or successful absence.
286///
287/// # Examples
288/// ```
289/// use std::sync::Arc;
290/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
291/// use structured_pkix::{ErrorCode, algorithm::{self, pss}};
292/// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
293/// let bytes = b"\x30\x12\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x05\xa2\x03\x02\x01\x14";
294/// let mut reader = source.read(bytes, 400).unwrap();
295/// let element = reader.next_element().unwrap().unwrap();
296/// let identifier = algorithm::read(&reader, &element, "signatureAlgorithm")?;
297/// let current = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
298/// let reading = pss::inspect(identifier, &current)?.unwrap();
299/// assert_eq!(reading.parameters().salt().as_u64(), Some(20));
300/// assert_eq!(reading.faults()[0].code(), ErrorCode::DefaultWritten);
301/// # Ok::<(), structured_pkix::Error>(())
302/// ```
303pub fn inspect<'a, W: Work>(
304    identifier: AlgorithmIdentifier<'a>,
305    execution: &W,
306) -> Result<Option<Reading<'a>>> {
307    require_algorithm(identifier, execution)?;
308    let Some((reader, element)) = open_parameters(identifier, execution)? else {
309        return Ok(None);
310    };
311    let query = identifier.query_in(execution);
312    let mut faults = query.vector();
313    let parameters = decode(&reader, element, |fault| {
314        let at = fault.offset();
315        let field = fault.field();
316        faults
317            .try_push(fault)
318            .map_err(|reason| Error::resource(reason, field, at))
319    })?;
320    Ok(Some(Reading {
321        parameters,
322        faults: faults.into_owned(),
323    }))
324}
325
326fn require_algorithm<W: Work>(identifier: AlgorithmIdentifier<'_>, execution: &W) -> Result<()> {
327    let query = identifier.query_in(execution);
328    if !crate::key::same_oid(
329        identifier.algorithm(),
330        crate::oid::RSASSA_PSS,
331        &query,
332        FIELD,
333        identifier.offset(),
334    )? {
335        return Err(Error::new(
336            ErrorCode::UnexpectedField,
337            FIELD,
338            identifier.offset(),
339        ));
340    }
341    Ok(())
342}
343
344fn open_parameters<'a: 'operation, 'operation, W: Work>(
345    identifier: AlgorithmIdentifier<'a>,
346    execution: &'operation W,
347) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
348    let query = identifier.query_in(execution);
349    query
350        .charge_work(1)
351        .map_err(|reason| Error::resource(reason, FIELD, identifier.offset()))?;
352    let value = identifier
353        .parameters_nested()
354        .map(|parameters| parameters.open(execution, FIELD))
355        .transpose()?;
356    if let Some((_, element)) = &value
357        && element.tag() != Tag::constructed(universal::SEQUENCE)
358    {
359        return Err(Error::new(
360            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
361            FIELD,
362            element.span().start,
363        ));
364    }
365    Ok(value)
366}
367
368fn require_session(identifier: AlgorithmIdentifier<'_>, parent: &Reader<'_, '_>) -> Result<()> {
369    if !identifier.same_session(parent) {
370        return Err(Error::new(
371            ErrorCode::ForeignReader,
372            FIELD,
373            identifier.offset(),
374        ));
375    }
376    Ok(())
377}
378
379fn open_parameter<'a: 'operation, 'operation, W: Work>(
380    identifier: AlgorithmIdentifier<'a>,
381    parent: &Reader<'a, '_>,
382    execution: &'operation W,
383    field: &'static str,
384) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
385    charge(parent, 1, identifier.offset())?;
386    let Some(parameters) = identifier.parameters_nested() else {
387        return Ok(None);
388    };
389    parameters.open(execution, field).map(Some)
390}
391
392fn decode<'a>(
393    parent: &Reader<'a, '_>,
394    element: Element<'a>,
395    mut fault: impl FnMut(Error) -> Result<()>,
396) -> Result<Parameters<'a>> {
397    let mut fields = parent.enter(&element).field(FIELD)?;
398    let mut parameters = Parameters {
399        parser: parent.parser(),
400        element,
401        hash: None,
402        mask: Mask::DefaultMgf1Sha1,
403        salt: Number::Default(20),
404        trailer: Number::Default(1),
405        defaults: 0,
406    };
407    let mut previous = None;
408    while let Some(element) = fields.next_element().field(FIELD)? {
409        fields.charge(1).field(FIELD)?;
410        let Some(number) = element.tag().number() else {
411            return Err(Error::new(
412                ErrorCode::UnexpectedField,
413                FIELD,
414                element.span().start,
415            ));
416        };
417        if number > 3
418            || element.tag() != Tag::context(true, number)
419            || previous.is_some_and(|last| number <= last)
420        {
421            return Err(Error::new(
422                ErrorCode::UnexpectedField,
423                FIELD,
424                element.span().start,
425            ));
426        }
427        previous = Some(number);
428        let mut inside = fields.enter(&element).field(FIELD)?;
429        let value = inside
430            .next_element()
431            .field(FIELD)?
432            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, inside.offset()))?;
433        if !inside.is_empty() {
434            return Err(Error::new(ErrorCode::TrailingData, FIELD, inside.offset()));
435        }
436        let defaulted = match number {
437            0 => {
438                let hash = super::read(&inside, &value, "algorithm.pss.hash")?;
439                parameters.hash = Some(Written {
440                    element,
441                    value: hash,
442                });
443                default_hash(&inside, hash)?
444            }
445            1 => {
446                let (mask, defaulted) = read_mask(&inside, element, value)?;
447                parameters.mask = mask;
448                defaulted
449            }
450            _ => {
451                if value.tag() != Tag::primitive(universal::INTEGER) {
452                    return Err(Error::new(
453                        ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
454                        FIELD,
455                        value.span().start,
456                    ));
457                }
458                let integer = inside.integer(&value).field(FIELD)?;
459                if number == 2 && integer.is_negative() {
460                    return Err(Error::new(
461                        ErrorCode::MalformedValue,
462                        FIELD,
463                        value.span().start,
464                    ));
465                }
466                charge(&inside, 9, value.span().start)?;
467                let written = Number::Written(Written {
468                    element,
469                    value: integer,
470                });
471                if number == 2 {
472                    parameters.salt = written;
473                    integer.as_u64() == Some(20)
474                } else {
475                    parameters.trailer = written;
476                    integer.as_u64() == Some(1)
477                }
478            }
479        };
480        // X.690 §11.5 restricts DER spelling, independently of RFC4055's
481        // requirement that verification recognize explicit default values.
482        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
483        if defaulted {
484            parameters.defaults |= 1 << number;
485        }
486        if defaulted && fields.encoding().is_der() {
487            fault(Error::new(
488                ErrorCode::DefaultWritten,
489                FIELD,
490                element.span().start,
491            ))?;
492        }
493    }
494    Ok(parameters)
495}
496
497fn default_hash(parent: &Reader<'_, '_>, identifier: AlgorithmIdentifier<'_>) -> Result<bool> {
498    // RFC4055 §§2.1/6 defines the ASN.1 sha1Identifier DEFAULT with NULL.
499    // Absent parameters have equivalent algorithm semantics but are a
500    // distinct ASN.1 value; do not manufacture NULL for X.690 §11.5.
501    // https://www.rfc-editor.org/rfc/rfc4055#section-2.1
502    Ok(
503        oid_is(parent, identifier.algorithm(), SHA1, identifier.offset())?
504            && identifier.has_null_parameter(),
505    )
506}
507
508fn oid_is(parent: &Reader<'_, '_>, left: Oid<'_>, right: Oid<'_>, at: u64) -> Result<bool> {
509    charge(parent, 1, at)?;
510    if left.as_bytes().len() != right.as_bytes().len() {
511        return Ok(false);
512    }
513    charge(parent, left.as_bytes().len() as u64, at)?;
514    Ok(left == right)
515}
516
517fn charge(parent: &Reader<'_, '_>, amount: u64, at: u64) -> Result<()> {
518    parent
519        .charge(amount)
520        .map_err(|error| Error::new(ErrorCode::Encoding(error.code()), FIELD, at))
521}
522
523fn read_mask<'a>(
524    parent: &Reader<'a, '_>,
525    element: Element<'a>,
526    value: Element<'a>,
527) -> Result<(Mask<'a>, bool)> {
528    let algorithm = super::read(parent, &value, "algorithm.pss.mask")?;
529    let function = Written {
530        element,
531        value: algorithm,
532    };
533    if !oid_is(parent, algorithm.algorithm(), MGF1, algorithm.offset())? {
534        return Ok((Mask::Other(function), false));
535    }
536    let execution = parent.execution();
537    let (reader, element) =
538        open_parameter(algorithm, parent, &execution, "algorithm.pss.mask.hash")?.ok_or_else(
539            || {
540                Error::new(
541                    ErrorCode::MissingField,
542                    "algorithm.pss.mask.hash",
543                    algorithm.offset(),
544                )
545            },
546        )?;
547    let hash = super::read(&reader, &element, "algorithm.pss.mask.hash")?;
548    Ok((Mask::Mgf1 { function, hash }, default_hash(&reader, hash)?))
549}