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, ¤t)?.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, ¤t)?.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}