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
8use super::AlgorithmIdentifier;
9use crate::error::{Error, ErrorCode, Located, Result};
10use structured_asn1::memory::{OwnedVec, Work};
11use structured_asn1::{Element, Integer, Oid, ScopedReader as Reader, Tag, universal};
12
13const SHA1: Oid<'static> = Oid::from_bytes(&[0x2b, 0x0e, 3, 2, 0x1a]);
14const MGF1: Oid<'static> = Oid::from_bytes(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 8]);
15const FIELD: &str = "algorithm.pss";
16
17/// An explicitly written field with its original EXPLICIT wrapper.
18#[derive(Debug, Clone, Copy)]
19pub struct Written<'a, T> {
20    element: Element<'a>,
21    value: T,
22}
23
24impl<'a, T: Copy> Written<'a, T> {
25    /// Original context-specific wrapper, including its source span.
26    #[must_use]
27    pub fn element(self) -> Element<'a> {
28        self.element
29    }
30
31    /// Decoded borrowed value, without re-encoding the field.
32    #[must_use]
33    pub fn value(self) -> T {
34        self.value
35    }
36}
37
38/// A numeric field, distinguishing omission from an explicit value.
39#[derive(Debug, Clone, Copy)]
40pub enum Number<'a> {
41    /// The schema's omitted value (salt 20 or trailer 1).
42    Default(u64),
43    /// An arbitrary-width original INTEGER, including its sign.
44    Written(Written<'a, Integer<'a>>),
45}
46
47impl Number<'_> {
48    /// Bounded numeric projection; negative/wider values remain `None`.
49    #[must_use]
50    pub fn as_u64(self) -> Option<u64> {
51        match self {
52            Self::Default(value) => Some(value),
53            Self::Written(value) => value.value.as_u64(),
54        }
55    }
56}
57
58/// The mask function and, for the defined MGF1 schema, its original hash.
59#[derive(Debug, Clone, Copy)]
60pub enum Mask<'a> {
61    /// Omitted maskGenAlgorithm means MGF1 with SHA-1.
62    DefaultMgf1Sha1,
63    /// Written MGF1 requires a hash `AlgorithmIdentifier` parameter.
64    Mgf1 {
65        /// Original explicit mask field and function identifier.
66        function: Written<'a, AlgorithmIdentifier<'a>>,
67        /// Original nested hash identifier, not a manufactured default.
68        hash: AlgorithmIdentifier<'a>,
69    },
70    /// Another mask function, retaining opaque parameters for its provider.
71    Other(Written<'a, AlgorithmIdentifier<'a>>),
72}
73
74/// Original PSS schema values, not algorithm support or signature validity.
75#[derive(Debug, Clone, Copy)]
76pub struct Parameters<'a> {
77    element: Element<'a>,
78    hash: Option<Written<'a, AlgorithmIdentifier<'a>>>,
79    mask: Mask<'a>,
80    salt: Number<'a>,
81    trailer: Number<'a>,
82}
83
84impl<'a> Parameters<'a> {
85    /// Complete original parameter SEQUENCE, without copies or normalization.
86    #[must_use]
87    pub fn element(self) -> Element<'a> {
88        self.element
89    }
90
91    /// Written hash identifier; `None` is the schema's SHA-1 default.
92    #[must_use]
93    pub fn hash(self) -> Option<Written<'a, AlgorithmIdentifier<'a>>> {
94        self.hash
95    }
96
97    /// Mask identity and original MGF1 hash, including omitted default.
98    #[must_use]
99    pub fn mask(self) -> Mask<'a> {
100        self.mask
101    }
102
103    /// Salt octet length (default 20, independently of the selected hash).
104    /// Its applicability as an exact signature length or key minimum is a
105    /// caller role decision (RFC4055 §3.3); no backend maximum is imposed here.
106    #[must_use]
107    pub fn salt(self) -> Number<'a> {
108        self.salt
109    }
110
111    /// Trailer number (default 1). Other numbers are retained for inspection;
112    /// RFC8017 A.2.3 defines support only for trailer 1, not other schemes.
113    #[must_use]
114    pub fn trailer(self) -> Number<'a> {
115        self.trailer
116    }
117}
118
119/// Readable fields and retained encoding diagnostics, not conforming evidence.
120#[derive(Debug)]
121pub struct Reading<'a> {
122    parameters: Parameters<'a>,
123    faults: OwnedVec<Error>,
124}
125
126impl<'a> Reading<'a> {
127    /// Unmodified original fields, even when their spelling has a diagnostic.
128    #[must_use]
129    pub fn parameters(&self) -> Parameters<'a> {
130        self.parameters
131    }
132
133    /// Located DER DEFAULT spelling diagnostics.
134    /// Resource and foreign-reader failures are terminal errors, never faults.
135    #[must_use]
136    pub fn faults(&self) -> &[Error] {
137        &self.faults
138    }
139}
140
141/// Reads present PSS parameters in the producing session; preserves absence.
142///
143/// Callers reject absent signature parameters but may accept an unrestricted
144/// PSS key (RFC4055 §3.1). This function does neither role selection nor policy.
145/// Original encoding, offsets, depth and local/current work ceilings survive.
146///
147/// ```
148/// use std::sync::Arc;
149/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
150/// use structured_pkix::algorithm::{self, pss};
151/// let parser = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
152/// let bytes = b"\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a\x30\x00";
153/// let mut reader = parser.read(bytes, 400).unwrap();
154/// let element = reader.next_element().unwrap().unwrap();
155/// let algorithm = algorithm::read(&reader, &element, "signatureAlgorithm")?;
156/// let parameters = pss::read_in(algorithm, &reader)?.unwrap();
157/// assert!(parameters.hash().is_none());
158/// assert_eq!(parameters.salt().as_u64(), Some(20));
159/// assert_eq!(parameters.element().span().start, 413);
160/// # Ok::<(), structured_pkix::Error>(())
161/// ```
162pub fn read_in<'a>(
163    identifier: AlgorithmIdentifier<'a>,
164    parent: &Reader<'a, '_>,
165) -> Result<Option<Parameters<'a>>> {
166    let execution = parent.execution();
167    let Some((reader, element)) = open(identifier, parent, &execution)? else {
168        return Ok(None);
169    };
170    decode(&reader, element, Err).map(Some)
171}
172
173/// Inspection counterpart of [`read_in`], sharing its schema kernel.
174///
175/// RFC4055 §3.1 requires recognizing explicitly written defaults during
176/// verification. Inspection therefore retains those values plus a DER spelling
177/// fault (X.690 §11.5), rather than losing the algorithm's meaning. BER permits
178/// written defaults. Neither operation verifies a signature or accepts a key.
179/// <https://www.rfc-editor.org/rfc/rfc4055#section-3.1>
180pub fn inspect_in<'a>(
181    identifier: AlgorithmIdentifier<'a>,
182    parent: &Reader<'a, '_>,
183) -> Result<Option<Reading<'a>>> {
184    let execution = parent.execution();
185    let Some((reader, element)) = open(identifier, parent, &execution)? else {
186        return Ok(None);
187    };
188    let mut faults = execution.vector();
189    let parameters = decode(&reader, element, |fault| {
190        let at = fault.offset();
191        let field = fault.field();
192        faults
193            .try_push(fault)
194            .map_err(|reason| Error::resource(reason, field, at))
195    })?;
196    Ok(Some(Reading {
197        parameters,
198        faults: faults.into_owned(),
199    }))
200}
201
202fn open<'a: 'operation, 'operation, W: Work>(
203    identifier: AlgorithmIdentifier<'a>,
204    parent: &Reader<'a, '_>,
205    execution: &'operation W,
206) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
207    if !identifier.same_session(parent) {
208        return Err(Error::new(
209            ErrorCode::ForeignReader,
210            FIELD,
211            identifier.offset(),
212        ));
213    }
214    if !oid_is(
215        parent,
216        identifier.algorithm(),
217        crate::oid::RSASSA_PSS,
218        identifier.offset(),
219    )? {
220        return Err(Error::new(
221            ErrorCode::UnexpectedField,
222            FIELD,
223            identifier.offset(),
224        ));
225    }
226    let value = open_parameter(identifier, parent, execution, FIELD)?;
227    if let Some((_, element)) = &value
228        && element.tag() != Tag::constructed(universal::SEQUENCE)
229    {
230        return Err(Error::new(
231            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
232            FIELD,
233            element.span().start,
234        ));
235    }
236    Ok(value)
237}
238
239fn open_parameter<'a: 'operation, 'operation, W: Work>(
240    identifier: AlgorithmIdentifier<'a>,
241    parent: &Reader<'a, '_>,
242    execution: &'operation W,
243    field: &'static str,
244) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
245    charge(parent, 1, identifier.offset())?;
246    let Some(parameters) = identifier.parameters() else {
247        return Ok(None);
248    };
249    let at = identifier.offset()
250        + identifier
251            .parameter_start
252            .expect("written parameters")
253            .get() as u64;
254    let mut reader = execution
255        .read_scoped_nested(
256            identifier.parser,
257            parameters,
258            at,
259            identifier.encoding,
260            identifier.outer,
261        )
262        .field(field)?;
263    let element = reader
264        .next_element()
265        .field(field)?
266        .ok_or_else(|| Error::new(ErrorCode::MissingField, field, reader.offset()))?;
267    if !reader.is_empty() {
268        return Err(Error::new(ErrorCode::TrailingData, field, reader.offset()));
269    }
270    Ok(Some((reader, element)))
271}
272
273fn decode<'a>(
274    parent: &Reader<'a, '_>,
275    element: Element<'a>,
276    mut fault: impl FnMut(Error) -> Result<()>,
277) -> Result<Parameters<'a>> {
278    let mut fields = parent.enter(&element).field(FIELD)?;
279    let mut parameters = Parameters {
280        element,
281        hash: None,
282        mask: Mask::DefaultMgf1Sha1,
283        salt: Number::Default(20),
284        trailer: Number::Default(1),
285    };
286    let mut previous = None;
287    while let Some(element) = fields.next_element().field(FIELD)? {
288        fields.charge(1).field(FIELD)?;
289        let Some(number) = element.tag().number() else {
290            return Err(Error::new(
291                ErrorCode::UnexpectedField,
292                FIELD,
293                element.span().start,
294            ));
295        };
296        if number > 3
297            || element.tag() != Tag::context(true, number)
298            || previous.is_some_and(|last| number <= last)
299        {
300            return Err(Error::new(
301                ErrorCode::UnexpectedField,
302                FIELD,
303                element.span().start,
304            ));
305        }
306        previous = Some(number);
307        let mut inside = fields.enter(&element).field(FIELD)?;
308        let value = inside
309            .next_element()
310            .field(FIELD)?
311            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, inside.offset()))?;
312        if !inside.is_empty() {
313            return Err(Error::new(ErrorCode::TrailingData, FIELD, inside.offset()));
314        }
315        let defaulted = match number {
316            0 => {
317                let hash = super::read(&inside, &value, "algorithm.pss.hash")?;
318                parameters.hash = Some(Written {
319                    element,
320                    value: hash,
321                });
322                default_hash(&inside, hash)?
323            }
324            1 => {
325                let (mask, defaulted) = read_mask(&inside, element, value)?;
326                parameters.mask = mask;
327                defaulted
328            }
329            _ => {
330                if value.tag() != Tag::primitive(universal::INTEGER) {
331                    return Err(Error::new(
332                        ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
333                        FIELD,
334                        value.span().start,
335                    ));
336                }
337                let integer = inside.integer(&value).field(FIELD)?;
338                if number == 2 && integer.is_negative() {
339                    return Err(Error::new(
340                        ErrorCode::MalformedValue,
341                        FIELD,
342                        value.span().start,
343                    ));
344                }
345                charge(&inside, 9, value.span().start)?;
346                let written = Number::Written(Written {
347                    element,
348                    value: integer,
349                });
350                if number == 2 {
351                    parameters.salt = written;
352                    integer.as_u64() == Some(20)
353                } else {
354                    parameters.trailer = written;
355                    integer.as_u64() == Some(1)
356                }
357            }
358        };
359        // X.690 §11.5 restricts DER spelling, independently of RFC4055's
360        // requirement that verification recognize explicit default values.
361        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
362        if defaulted && fields.encoding().is_der() {
363            fault(Error::new(
364                ErrorCode::DefaultWritten,
365                FIELD,
366                element.span().start,
367            ))?;
368        }
369    }
370    Ok(parameters)
371}
372
373fn default_hash(parent: &Reader<'_, '_>, identifier: AlgorithmIdentifier<'_>) -> Result<bool> {
374    // RFC4055 §§2.1/6 defines the ASN.1 sha1Identifier DEFAULT with NULL.
375    // Absent parameters have equivalent algorithm semantics but are a
376    // distinct ASN.1 value; do not manufacture NULL for X.690 §11.5.
377    // https://www.rfc-editor.org/rfc/rfc4055#section-2.1
378    Ok(
379        oid_is(parent, identifier.algorithm(), SHA1, identifier.offset())?
380            && identifier.has_null_parameter(),
381    )
382}
383
384fn oid_is(parent: &Reader<'_, '_>, left: Oid<'_>, right: Oid<'_>, at: u64) -> Result<bool> {
385    charge(parent, 1, at)?;
386    if left.as_bytes().len() != right.as_bytes().len() {
387        return Ok(false);
388    }
389    charge(parent, left.as_bytes().len() as u64, at)?;
390    Ok(left == right)
391}
392
393fn charge(parent: &Reader<'_, '_>, amount: u64, at: u64) -> Result<()> {
394    parent
395        .charge(amount)
396        .map_err(|error| Error::new(ErrorCode::Encoding(error.code()), FIELD, at))
397}
398
399fn read_mask<'a>(
400    parent: &Reader<'a, '_>,
401    element: Element<'a>,
402    value: Element<'a>,
403) -> Result<(Mask<'a>, bool)> {
404    let algorithm = super::read(parent, &value, "algorithm.pss.mask")?;
405    let function = Written {
406        element,
407        value: algorithm,
408    };
409    if !oid_is(parent, algorithm.algorithm(), MGF1, algorithm.offset())? {
410        return Ok((Mask::Other(function), false));
411    }
412    let execution = parent.execution();
413    let (reader, element) =
414        open_parameter(algorithm, parent, &execution, "algorithm.pss.mask.hash")?.ok_or_else(
415            || {
416                Error::new(
417                    ErrorCode::MissingField,
418                    "algorithm.pss.mask.hash",
419                    algorithm.offset(),
420                )
421            },
422        )?;
423    let hash = super::read(&reader, &element, "algorithm.pss.mask.hash")?;
424    Ok((Mask::Mgf1 { function, hash }, default_hash(&reader, hash)?))
425}