Skip to main content

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, &current, |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}