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