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