structured_pkix/algorithm/signature.rs
1//! Original signature algorithm structure, without provider or policy selection.
2//!
3//! Recognizes SHA-family RSA PKCS#1 v1.5, RFC3279/5758 DSA, ECDSA,
4//! RSASSA-PSS, pure Ed25519/Ed448 and RFC9881 ML-DSA. It does not infer a scheme from a key,
5//! signature size or separately written CMS digest. Unknown OIDs, including
6//! legacy OIW aliases, MD2/MD5, additional DSA profiles and other PQ, remain unknown.
7
8use super::{AlgorithmIdentifier, digest, ml_dsa, pss};
9use crate::{Error, ErrorCode, Result};
10use structured_asn1::memory::Work;
11
12/// A recognized signature structure, not backend support or authorization.
13///
14/// Fixed digests are implied by the signature OID; no encoded digest identifier
15/// is fabricated. The caller keeps the original signature identifier for
16/// provenance, provider dispatch and unsupported-algorithm diagnostics.
17#[derive(Debug, Clone, Copy)]
18#[non_exhaustive]
19pub enum Algorithm<'a, 'parameters> {
20 /// RSA PKCS#1 v1.5 with the indicated digest.
21 RsaPkcs1v15(digest::Algorithm),
22 /// Original PSS parameters, without narrowing integers or choosing a backend.
23 RsaPss(&'parameters pss::Parameters<'a>),
24 /// DSA with SHA-1, SHA-224 or SHA-256.
25 Dsa(digest::Algorithm),
26 /// ECDSA with the indicated digest.
27 Ecdsa(digest::Algorithm),
28 /// Pure Ed25519: not Ed25519ph or Ed25519ctx.
29 Ed25519,
30 /// Pure Ed448: not Ed448ph.
31 Ed448,
32 /// Pure ML-DSA; certificate/CRL verification requires an empty context.
33 /// This does not authorize HashML-DSA or imply a finalized-message digest.
34 MlDsa(ml_dsa::ParameterSet),
35}
36
37#[derive(Clone, Copy)]
38enum Parameters {
39 Absent,
40 Null,
41 NullOrAbsent,
42}
43
44/// Visits a retained signature structure under original and current work.
45///
46/// RSA SHA-224/256/384/512 accepts absent or primitive NULL on reception
47/// (RFC4055 §5). Recognized RSA SHA-1, SHA-512/224, SHA-512/256 and SHA3 require
48/// NULL (RFC3279 §2.2.1, RFC8017 A.2.4, RFC9688 §3.1). DSA SHA-1/224/256,
49/// ECDSA SHA-1/SHA-2/SHA3 and pure Ed25519/Ed448 require absent parameters
50/// (RFC3279 §2.2, RFC5758 §3, RFC9688 §3.2, RFC8410 §6). BER NULL spellings are
51/// validated by the original reader; bytes are never compared to fabricated DER.
52/// ML-DSA-44/65/87 also require absent parameters (RFC9881 §2); the PKIX
53/// provider must use pure mode and an empty context (RFC9881 §3).
54///
55/// PSS signature parameters must be present (RFC4055 §3.1), unlike optional
56/// key restrictions. The existing strict PSS kernel preserves independent
57/// message/MGF hashes, wide salt/trailer fields and original ranges/depth;
58/// DER written DEFAULTs remain errors. Use [`pss::inspect`] for readable PSS
59/// diagnostics. Backend support, PSS compatibility, key strength, policy and
60/// signature math remain separate. A known structure does not promise that
61/// its provider implements it or accepts its parameters.
62///
63/// Visits `None` for identifiers outside the module's documented recognized
64/// set. Both original parser and current operation ceilings apply before any
65/// OID scan; a fresh account cannot reset source limits. Fixed cases allocate
66/// nothing and need no cursor. PSS fields are borrowed only for the visit,
67/// keeping the enum small without allocating a wrapper. The returned `R` may
68/// borrow original document bytes, or explicitly copy PSS fields, but cannot
69/// keep a reference to the temporary PSS descriptor. The visitor's result,
70/// including a caller-specific `Result`, is returned unchanged inside the
71/// outer processing result. The visitor is not called on a processing error.
72///
73/// # Errors
74/// Located missing/invalid parameters or strict PSS errors; terminal resource
75/// errors remain distinct from unknown algorithms and malformed parameters.
76///
77/// # Examples
78/// ```
79/// use std::sync::Arc;
80/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
81/// use structured_pkix::algorithm::{self, digest, signature};
82/// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
83/// // ecdsa-with-SHA256, with parameters absent.
84/// let bytes = b"\x30\x0a\x06\x08\x2a\x86\x48\xce\x3d\x04\x03\x02";
85/// let mut reader = source.read(bytes, 400).unwrap();
86/// let element = reader.next_element().unwrap().unwrap();
87/// let identifier = algorithm::read(&reader, &element, "signatureAlgorithm")?;
88/// let current = Memory::new(Arc::new(MemoryAccount::new(0, 4096)));
89/// let recognized = signature::with_algorithm(identifier, ¤t, |algorithm|
90/// matches!(algorithm, Some(signature::Algorithm::Ecdsa(digest::Algorithm::Sha256))))?;
91/// assert!(recognized);
92/// # Ok::<(), structured_pkix::Error>(())
93/// ```
94/// A reference to the temporary PSS descriptor cannot escape:
95/// ```compile_fail
96/// use structured_pkix::algorithm::{AlgorithmIdentifier, pss, signature};
97/// use structured_asn1::memory::Memory;
98/// fn escape<'a>(id: AlgorithmIdentifier<'a>, work: &Memory)
99/// -> structured_pkix::Result<&'a pss::Parameters<'a>> {
100/// signature::with_algorithm(id, work, |algorithm| match algorithm {
101/// Some(signature::Algorithm::RsaPss(parameters)) => parameters,
102/// _ => panic!("PSS expected"),
103/// })
104/// }
105/// ```
106pub fn with_algorithm<'a, W: Work, R>(
107 identifier: AlgorithmIdentifier<'a>,
108 execution: &W,
109 visitor: impl for<'parameters> FnOnce(Option<Algorithm<'a, 'parameters>>) -> R,
110) -> Result<R> {
111 use digest::Algorithm as Hash;
112 let bytes = identifier.algorithm().as_bytes();
113 identifier
114 .query_in(execution)
115 .charge_work(bytes.len() as u64 + 1)
116 .map_err(|reason| Error::resource(reason, identifier.field, identifier.offset()))?;
117 let (algorithm, parameters) = match bytes {
118 // RFC8017 A.2.4 assigns distinct signature OIDs, not digest OIDs.
119 // https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4
120 [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, last] => {
121 let (hash, parameters) = match last {
122 5 => (Hash::Sha1, Parameters::Null),
123 11 => (Hash::Sha256, Parameters::NullOrAbsent),
124 12 => (Hash::Sha384, Parameters::NullOrAbsent),
125 13 => (Hash::Sha512, Parameters::NullOrAbsent),
126 14 => (Hash::Sha224, Parameters::NullOrAbsent),
127 15 => (Hash::Sha512_224, Parameters::Null),
128 16 => (Hash::Sha512_256, Parameters::Null),
129 10 => {
130 let value = pss::read_known(identifier, execution)?.ok_or_else(|| {
131 Error::new(
132 ErrorCode::MissingField,
133 identifier.field,
134 identifier.offset(),
135 )
136 })?;
137 return Ok(visitor(Some(Algorithm::RsaPss(&value))));
138 }
139 _ => return Ok(visitor(None)),
140 };
141 (Algorithm::RsaPkcs1v15(hash), parameters)
142 }
143 [0x2a, 0x86, 0x48, 0xce, 0x38, 4, 3] => (Algorithm::Dsa(Hash::Sha1), Parameters::Absent),
144 [0x2a, 0x86, 0x48, 0xce, 0x3d, 4, 1] => (Algorithm::Ecdsa(Hash::Sha1), Parameters::Absent),
145 [0x2a, 0x86, 0x48, 0xce, 0x3d, 4, 3, last] => {
146 let hash = match last {
147 1 => Hash::Sha224,
148 2 => Hash::Sha256,
149 3 => Hash::Sha384,
150 4 => Hash::Sha512,
151 _ => return Ok(visitor(None)),
152 };
153 (Algorithm::Ecdsa(hash), Parameters::Absent)
154 }
155 [0x60, 0x86, 0x48, 1, 0x65, 3, 4, 3, last] => {
156 // SHA3 digest and signature parameter rules differ by role.
157 // https://www.rfc-editor.org/rfc/rfc9688#section-3
158 match last {
159 1 => (Algorithm::Dsa(Hash::Sha224), Parameters::Absent),
160 2 => (Algorithm::Dsa(Hash::Sha256), Parameters::Absent),
161 9..=16 => {
162 let hash = match last {
163 9 | 13 => Hash::Sha3_224,
164 10 | 14 => Hash::Sha3_256,
165 11 | 15 => Hash::Sha3_384,
166 _ => Hash::Sha3_512,
167 };
168 if *last <= 12 {
169 (Algorithm::Ecdsa(hash), Parameters::Absent)
170 } else {
171 (Algorithm::RsaPkcs1v15(hash), Parameters::Null)
172 }
173 }
174 _ => {
175 // The prefix was already paid and matched. Share the final
176 // arc and parameter kernel without another OID scan.
177 if let Some(set) = ml_dsa::from_arc(*last) {
178 ml_dsa::require_absent(identifier)?;
179 return Ok(visitor(Some(Algorithm::MlDsa(set))));
180 }
181 return Ok(visitor(None));
182 }
183 }
184 }
185 [0x2b, 0x65, 112] => (Algorithm::Ed25519, Parameters::Absent),
186 [0x2b, 0x65, 113] => (Algorithm::Ed448, Parameters::Absent),
187 _ => return Ok(visitor(None)),
188 };
189 check_parameters(identifier, parameters)?;
190 Ok(visitor(Some(algorithm)))
191}
192
193fn check_parameters(identifier: AlgorithmIdentifier<'_>, parameters: Parameters) -> Result<()> {
194 let present = identifier.parameters_nested();
195 let valid = match parameters {
196 // NULL is present, not an absent DSA/ECDSA or pure EdDSA parameter.
197 // https://www.rfc-editor.org/rfc/rfc5758#section-3
198 // https://www.rfc-editor.org/rfc/rfc8410#section-6
199 Parameters::Absent => present.is_none(),
200 // Signature AlgorithmIdentifier, not SHA3 digest AlgorithmIdentifier.
201 // https://www.rfc-editor.org/rfc/rfc9688#section-3.1
202 Parameters::Null => identifier.has_null_parameter(),
203 // RFC4055 §5 explicitly requires both forms on reception.
204 // https://www.rfc-editor.org/rfc/rfc4055#section-5
205 Parameters::NullOrAbsent => present.is_none() || identifier.has_null_parameter(),
206 };
207 if !valid {
208 let at = present.map_or(identifier.offset(), super::Nested::offset);
209 let code = if present.is_some() {
210 ErrorCode::MalformedValue
211 } else {
212 ErrorCode::MissingField
213 };
214 return Err(Error::new(code, identifier.field, at));
215 }
216 Ok(())
217}