Skip to main content

xml_sec/xmldsig/
signature.rs

1//! Signature verification helpers for XMLDSig.
2//!
3//! This module currently covers roadmap task P1-019 (RSA PKCS#1 v1.5) and
4//! P1-020 (ECDSA P-256/P-384) verification.
5//!
6//! Input public keys are accepted in SubjectPublicKeyInfo (SPKI) form because
7//! that is how the vendored PEM fixtures are stored. `ring` expects the inner
8//! SPKI payload for both algorithm families:
9//! - RSA: ASN.1 `RSAPublicKey`
10//! - ECDSA: uncompressed SEC1 EC point bytes from the SPKI bit string
11
12use ring::signature;
13use x509_parser::prelude::FromDer;
14use x509_parser::public_key::{ECPoint, PublicKey};
15use x509_parser::x509::SubjectPublicKeyInfo;
16
17use super::parse::SignatureAlgorithm;
18
19/// Errors while preparing or running XMLDSig signature verification.
20#[derive(Debug, thiserror::Error)]
21#[non_exhaustive]
22pub enum SignatureVerificationError {
23    /// The provided PEM block could not be parsed as PEM input.
24    #[error("invalid PEM public key")]
25    InvalidKeyPem,
26
27    /// The signature method is not an RSA PKCS#1 v1.5 algorithm.
28    #[error("unsupported signature algorithm: {uri}")]
29    UnsupportedAlgorithm {
30        /// XMLDSig algorithm URI used for diagnostics.
31        uri: String,
32    },
33
34    /// The provided PEM block was not a public key.
35    #[error("invalid key format: expected PUBLIC KEY PEM, got {label}")]
36    InvalidKeyFormat {
37        /// The PEM label that was actually supplied.
38        label: String,
39    },
40
41    /// The provided DER bytes were not a valid SPKI-encoded public key.
42    #[error("invalid SubjectPublicKeyInfo DER")]
43    InvalidKeyDer,
44
45    /// The provided public key does not match the signature algorithm.
46    #[error("public key does not match signature algorithm: {uri}")]
47    KeyAlgorithmMismatch {
48        /// XMLDSig algorithm URI used for diagnostics.
49        uri: String,
50    },
51
52    /// The provided ECDSA signature bytes were neither XMLDSig fixed-width
53    /// nor ASN.1 DER encoded.
54    #[error("invalid ECDSA signature encoding")]
55    InvalidSignatureFormat,
56}
57
58/// Verify an RSA XMLDSig signature using a PEM-encoded SPKI public key.
59///
60/// The PEM must contain a `PUBLIC KEY` block. Returns `Ok(false)` for signature
61/// mismatch and `Err` for algorithm/key preparation errors.
62#[must_use = "discarding the verification result skips signature validation"]
63pub fn verify_rsa_signature_pem(
64    algorithm: SignatureAlgorithm,
65    public_key_pem: &str,
66    signed_data: &[u8],
67    signature_value: &[u8],
68) -> Result<bool, SignatureVerificationError> {
69    let public_key_spki_der = parse_public_key_pem(public_key_pem)?;
70    verify_rsa_signature_spki(
71        algorithm,
72        &public_key_spki_der,
73        signed_data,
74        signature_value,
75    )
76}
77
78/// Verify an ECDSA XMLDSig signature using a PEM-encoded SPKI public key.
79///
80/// The PEM must contain a `PUBLIC KEY` block. The signature value is expected
81/// to use the XMLDSig fixed-width `r || s` format required by RFC 6931 /
82/// XMLDSig 1.1, but ASN.1 DER-encoded ECDSA signatures are also accepted as an
83/// interop fallback. Returns `Ok(false)` for signature mismatch and `Err` for
84/// algorithm/key/signature-format preparation errors (including
85/// `InvalidSignatureFormat` when the bytes are neither valid fixed-width
86/// `r || s` nor valid ASN.1 DER ECDSA).
87#[must_use = "discarding the verification result skips signature validation"]
88pub fn verify_ecdsa_signature_pem(
89    algorithm: SignatureAlgorithm,
90    public_key_pem: &str,
91    signed_data: &[u8],
92    signature_value: &[u8],
93) -> Result<bool, SignatureVerificationError> {
94    let public_key_spki_der = parse_public_key_pem(public_key_pem)?;
95    verify_ecdsa_signature_spki(
96        algorithm,
97        &public_key_spki_der,
98        signed_data,
99        signature_value,
100    )
101}
102
103fn parse_public_key_pem(public_key_pem: &str) -> Result<Vec<u8>, SignatureVerificationError> {
104    let (rest, pem) = x509_parser::pem::parse_x509_pem(public_key_pem.as_bytes())
105        .map_err(|_| SignatureVerificationError::InvalidKeyPem)?;
106    if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
107        return Err(SignatureVerificationError::InvalidKeyPem);
108    }
109    if pem.label != "PUBLIC KEY" {
110        return Err(SignatureVerificationError::InvalidKeyFormat { label: pem.label });
111    }
112
113    Ok(pem.contents)
114}
115
116/// Verify an RSA XMLDSig signature using DER-encoded SPKI public key bytes.
117///
118/// The input must be an X.509 `SubjectPublicKeyInfo` wrapping an RSA key.
119/// Returns `Ok(false)` for signature mismatch and `Err` for algorithm/key
120/// preparation errors.
121#[must_use = "discarding the verification result skips signature validation"]
122pub fn verify_rsa_signature_spki(
123    algorithm: SignatureAlgorithm,
124    public_key_spki_der: &[u8],
125    signed_data: &[u8],
126    signature_value: &[u8],
127) -> Result<bool, SignatureVerificationError> {
128    let verification_algorithm = verification_algorithm(algorithm)?;
129    let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der)
130        .map_err(|_| SignatureVerificationError::InvalidKeyDer)?;
131    if !rest.is_empty() {
132        return Err(SignatureVerificationError::InvalidKeyDer);
133    }
134    let public_key = spki
135        .parsed()
136        .map_err(|_| SignatureVerificationError::InvalidKeyDer)?;
137
138    match public_key {
139        PublicKey::RSA(rsa) => {
140            validate_rsa_public_key(&rsa, algorithm)?;
141            let key = signature::UnparsedPublicKey::new(
142                verification_algorithm,
143                spki.subject_public_key.data,
144            );
145            Ok(key.verify(signed_data, signature_value).is_ok())
146        }
147        _ => Err(SignatureVerificationError::InvalidKeyDer),
148    }
149}
150
151/// Verify an ECDSA XMLDSig signature using DER-encoded SPKI public key bytes.
152///
153/// The input must be an X.509 `SubjectPublicKeyInfo` wrapping an EC key. The
154/// signature value may be either XMLDSig fixed-width `r || s` bytes or ASN.1
155/// DER-encoded ECDSA for interop compatibility. Returns `Ok(false)` for
156/// signature mismatch and `Err` for algorithm/key/signature-format preparation
157/// errors.
158#[must_use = "discarding the verification result skips signature validation"]
159pub fn verify_ecdsa_signature_spki(
160    algorithm: SignatureAlgorithm,
161    public_key_spki_der: &[u8],
162    signed_data: &[u8],
163    signature_value: &[u8],
164) -> Result<bool, SignatureVerificationError> {
165    if !matches!(
166        algorithm,
167        SignatureAlgorithm::EcdsaP256Sha256 | SignatureAlgorithm::EcdsaP384Sha384
168    ) {
169        return Err(SignatureVerificationError::UnsupportedAlgorithm {
170            uri: algorithm.uri().to_string(),
171        });
172    }
173
174    let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der)
175        .map_err(|_| SignatureVerificationError::InvalidKeyDer)?;
176    if !rest.is_empty() {
177        return Err(SignatureVerificationError::InvalidKeyDer);
178    }
179    let public_key = spki
180        .parsed()
181        .map_err(|_| SignatureVerificationError::InvalidKeyDer)?;
182
183    match public_key {
184        PublicKey::EC(ec) => {
185            validate_ec_public_key_encoding(&ec, &spki.subject_public_key.data)?;
186            let (fixed_algorithm, asn1_algorithm, signature_encoding) =
187                ecdsa_verification_algorithms(&spki, &ec, algorithm, signature_value)?;
188            let public_key = &spki.subject_public_key.data;
189            let fixed_key = signature::UnparsedPublicKey::new(fixed_algorithm, public_key);
190            let asn1_key = signature::UnparsedPublicKey::new(asn1_algorithm, public_key);
191
192            match signature_encoding {
193                EcdsaSignatureEncoding::XmlDsigFixed => {
194                    Ok(fixed_key.verify(signed_data, signature_value).is_ok())
195                }
196                EcdsaSignatureEncoding::Asn1Der => {
197                    Ok(asn1_key.verify(signed_data, signature_value).is_ok())
198                }
199                EcdsaSignatureEncoding::Ambiguous => {
200                    if asn1_key.verify(signed_data, signature_value).is_ok() {
201                        return Ok(true);
202                    }
203
204                    Ok(fixed_key.verify(signed_data, signature_value).is_ok())
205                }
206            }
207        }
208        _ => Err(SignatureVerificationError::KeyAlgorithmMismatch {
209            uri: algorithm.uri().to_string(),
210        }),
211    }
212}
213
214fn validate_rsa_public_key(
215    rsa: &x509_parser::public_key::RSAPublicKey<'_>,
216    algorithm: SignatureAlgorithm,
217) -> Result<(), SignatureVerificationError> {
218    let min_modulus_bits = minimum_rsa_modulus_bits(algorithm)?;
219    let modulus_start = rsa
220        .modulus
221        .iter()
222        .position(|byte| *byte != 0)
223        .ok_or(SignatureVerificationError::InvalidKeyDer)?;
224    let modulus = &rsa.modulus[modulus_start..];
225    if modulus.is_empty() {
226        return Err(SignatureVerificationError::InvalidKeyDer);
227    }
228    // Match ring's RSA parameter checks: modulus length is evaluated after
229    // rounding up to the nearest whole byte, not by exact significant-bit
230    // length of the highest non-zero byte.
231    let modulus_bits = modulus
232        .len()
233        .checked_mul(8)
234        .ok_or(SignatureVerificationError::InvalidKeyDer)?;
235    if !(min_modulus_bits..=8192).contains(&modulus_bits) {
236        return Err(SignatureVerificationError::InvalidKeyDer);
237    }
238
239    let exponent = rsa
240        .try_exponent()
241        .map_err(|_| SignatureVerificationError::InvalidKeyDer)?;
242    if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 {
243        return Err(SignatureVerificationError::InvalidKeyDer);
244    }
245
246    Ok(())
247}
248
249fn minimum_rsa_modulus_bits(
250    algorithm: SignatureAlgorithm,
251) -> Result<usize, SignatureVerificationError> {
252    match algorithm {
253        SignatureAlgorithm::RsaSha1
254        | SignatureAlgorithm::RsaSha256
255        | SignatureAlgorithm::RsaSha384
256        | SignatureAlgorithm::RsaSha512 => Ok(2048),
257        _ => Err(SignatureVerificationError::UnsupportedAlgorithm {
258            uri: algorithm.uri().to_string(),
259        }),
260    }
261}
262
263fn verification_algorithm(
264    algorithm: SignatureAlgorithm,
265) -> Result<&'static dyn signature::VerificationAlgorithm, SignatureVerificationError> {
266    match algorithm {
267        SignatureAlgorithm::RsaSha1 => Ok(&signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY),
268        SignatureAlgorithm::RsaSha256 => Ok(&signature::RSA_PKCS1_2048_8192_SHA256),
269        SignatureAlgorithm::RsaSha384 => Ok(&signature::RSA_PKCS1_2048_8192_SHA384),
270        SignatureAlgorithm::RsaSha512 => Ok(&signature::RSA_PKCS1_2048_8192_SHA512),
271        _ => Err(SignatureVerificationError::UnsupportedAlgorithm {
272            uri: algorithm.uri().to_string(),
273        }),
274    }
275}
276
277fn ecdsa_verification_algorithms(
278    spki: &SubjectPublicKeyInfo<'_>,
279    ec: &ECPoint<'_>,
280    algorithm: SignatureAlgorithm,
281    signature_value: &[u8],
282) -> Result<
283    (
284        &'static dyn signature::VerificationAlgorithm,
285        &'static dyn signature::VerificationAlgorithm,
286        EcdsaSignatureEncoding,
287    ),
288    SignatureVerificationError,
289> {
290    let curve_oid = spki
291        .algorithm
292        .parameters
293        .as_ref()
294        .and_then(|params| params.as_oid().ok())
295        .ok_or(SignatureVerificationError::InvalidKeyDer)?;
296    let point_len = ec.key_size();
297
298    let curve_oid = curve_oid.to_id_string();
299    let (fixed_algorithm, asn1_algorithm, component_len) = match algorithm {
300        SignatureAlgorithm::EcdsaP256Sha256 => {
301            if curve_oid == "1.2.840.10045.3.1.7" && point_len == 256 {
302                (
303                    &signature::ECDSA_P256_SHA256_FIXED,
304                    &signature::ECDSA_P256_SHA256_ASN1,
305                    32,
306                )
307            } else {
308                return Err(SignatureVerificationError::KeyAlgorithmMismatch {
309                    uri: algorithm.uri().to_string(),
310                });
311            }
312        }
313        SignatureAlgorithm::EcdsaP384Sha384 => {
314            if curve_oid == "1.3.132.0.34" && point_len == 384 {
315                (
316                    &signature::ECDSA_P384_SHA384_FIXED,
317                    &signature::ECDSA_P384_SHA384_ASN1,
318                    48,
319                )
320            } else {
321                return Err(SignatureVerificationError::KeyAlgorithmMismatch {
322                    uri: algorithm.uri().to_string(),
323                });
324            }
325        }
326        _ => {
327            return Err(SignatureVerificationError::UnsupportedAlgorithm {
328                uri: algorithm.uri().to_string(),
329            });
330        }
331    };
332
333    Ok((
334        fixed_algorithm,
335        asn1_algorithm,
336        classify_ecdsa_signature_encoding(signature_value, component_len)?,
337    ))
338}
339
340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
341enum EcdsaSignatureEncoding {
342    XmlDsigFixed,
343    Asn1Der,
344    Ambiguous,
345}
346
347fn classify_ecdsa_signature_encoding(
348    signature_value: &[u8],
349    component_len: usize,
350) -> Result<EcdsaSignatureEncoding, SignatureVerificationError> {
351    let expected_len = component_len
352        .checked_mul(2)
353        .ok_or(SignatureVerificationError::InvalidSignatureFormat)?;
354
355    match inspect_der_encoded_ecdsa_signature(signature_value, component_len) {
356        Ok(Some(())) if signature_value.len() == expected_len => {
357            Ok(EcdsaSignatureEncoding::Ambiguous)
358        }
359        Ok(Some(())) => Ok(EcdsaSignatureEncoding::Asn1Der),
360        Ok(None) | Err(_) if signature_value.len() == expected_len => {
361            Ok(EcdsaSignatureEncoding::XmlDsigFixed)
362        }
363        Ok(None) | Err(_) => Err(SignatureVerificationError::InvalidSignatureFormat),
364    }
365}
366
367fn inspect_der_encoded_ecdsa_signature(
368    signature_value: &[u8],
369    component_len: usize,
370) -> Result<Option<()>, SignatureVerificationError> {
371    let Some((&tag, rest)) = signature_value.split_first() else {
372        return Ok(None);
373    };
374    if tag != 0x30 {
375        return Ok(None);
376    }
377
378    let sequence = parse_der_length(rest)
379        .ok_or(SignatureVerificationError::InvalidSignatureFormat)?
380        .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?;
381    let (sequence_len, sequence_rest) = sequence;
382    let (sequence_content, trailing) = sequence_rest
383        .split_at_checked(sequence_len)
384        .ok_or(SignatureVerificationError::InvalidSignatureFormat)?;
385    if !trailing.is_empty() {
386        return Err(SignatureVerificationError::InvalidSignatureFormat);
387    }
388
389    let after_r = parse_der_integer(sequence_content, component_len)?;
390    let after_s = parse_der_integer(after_r, component_len)?;
391    if !after_s.is_empty() {
392        return Err(SignatureVerificationError::InvalidSignatureFormat);
393    }
394
395    Ok(Some(()))
396}
397
398fn parse_der_integer(
399    input: &[u8],
400    component_len: usize,
401) -> Result<&[u8], SignatureVerificationError> {
402    let Some((&tag, rest)) = input.split_first() else {
403        return Err(SignatureVerificationError::InvalidSignatureFormat);
404    };
405    if tag != 0x02 {
406        return Err(SignatureVerificationError::InvalidSignatureFormat);
407    }
408
409    let (len, rest) = parse_der_length(rest)
410        .ok_or(SignatureVerificationError::InvalidSignatureFormat)?
411        .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?;
412    let (integer_bytes, remainder) = rest
413        .split_at_checked(len)
414        .ok_or(SignatureVerificationError::InvalidSignatureFormat)?;
415
416    if integer_bytes.is_empty() {
417        return Err(SignatureVerificationError::InvalidSignatureFormat);
418    }
419    if integer_bytes.len() > component_len + 1 {
420        return Err(SignatureVerificationError::InvalidSignatureFormat);
421    }
422    if integer_bytes.len() == component_len + 1 && integer_bytes[0] != 0 {
423        return Err(SignatureVerificationError::InvalidSignatureFormat);
424    }
425    if integer_bytes[0] & 0x80 != 0 {
426        return Err(SignatureVerificationError::InvalidSignatureFormat);
427    }
428    if integer_bytes.len() > 1 && integer_bytes[0] == 0 && integer_bytes[1] & 0x80 == 0 {
429        return Err(SignatureVerificationError::InvalidSignatureFormat);
430    }
431
432    Ok(remainder)
433}
434
435fn parse_der_length(input: &[u8]) -> Option<Result<(usize, &[u8]), ()>> {
436    let (&len_byte, rest) = input.split_first()?;
437
438    if len_byte & 0x80 == 0 {
439        return Some(Ok((usize::from(len_byte), rest)));
440    }
441
442    let len_len = usize::from(len_byte & 0x7f);
443    if len_len == 0 || len_len > std::mem::size_of::<usize>() || rest.len() < len_len {
444        return Some(Err(()));
445    }
446
447    let (len_bytes, remainder) = rest.split_at(len_len);
448    if len_bytes[0] == 0 {
449        return Some(Err(()));
450    }
451
452    let mut declared_len = 0_usize;
453    for &byte in len_bytes {
454        declared_len = match declared_len.checked_mul(256) {
455            Some(len) => len,
456            None => return Some(Err(())),
457        };
458        declared_len = match declared_len.checked_add(usize::from(byte)) {
459            Some(len) => len,
460            None => return Some(Err(())),
461        };
462    }
463
464    if declared_len < 128 {
465        return Some(Err(()));
466    }
467
468    Some(Ok((declared_len, remainder)))
469}
470
471fn validate_ec_public_key_encoding(
472    ec: &ECPoint<'_>,
473    public_key_bytes: &[u8],
474) -> Result<(), SignatureVerificationError> {
475    let coordinate_len = ec_coordinate_len_bytes(ec.key_size())?;
476    let expected_len = coordinate_len
477        .checked_mul(2)
478        .and_then(|len| len.checked_add(1))
479        .ok_or(SignatureVerificationError::InvalidKeyDer)?;
480
481    let is_uncompressed_sec1 =
482        public_key_bytes.len() == expected_len && public_key_bytes.first() == Some(&0x04);
483    if !is_uncompressed_sec1 {
484        return Err(SignatureVerificationError::InvalidKeyDer);
485    }
486
487    Ok(())
488}
489
490fn ec_coordinate_len_bytes(key_bits: usize) -> Result<usize, SignatureVerificationError> {
491    key_bits
492        .checked_add(7)
493        .and_then(|bits| bits.checked_div(8))
494        .ok_or(SignatureVerificationError::InvalidKeyDer)
495}
496
497#[cfg(test)]
498#[expect(clippy::unwrap_used, reason = "unit tests use fixed fixture data")]
499mod tests {
500    use super::*;
501
502    #[test]
503    fn ecdsa_algorithms_are_rejected_for_rsa_verification() {
504        for algorithm in [
505            SignatureAlgorithm::EcdsaP256Sha256,
506            SignatureAlgorithm::EcdsaP384Sha384,
507        ] {
508            let err = verification_algorithm(algorithm).unwrap_err();
509            assert!(matches!(
510                err,
511                SignatureVerificationError::UnsupportedAlgorithm { .. }
512            ));
513        }
514    }
515
516    #[test]
517    fn der_like_prefix_with_fixed_width_len_is_classified_as_raw() {
518        let mut signature = vec![0xAA_u8; 96];
519        signature[0] = 0x30;
520        signature[1] = 0x20;
521
522        let encoding = classify_ecdsa_signature_encoding(&signature, 48)
523            .expect("same-width signature with invalid DER must fall back to raw");
524        assert_eq!(encoding, EcdsaSignatureEncoding::XmlDsigFixed);
525    }
526
527    #[test]
528    fn overlong_der_length_below_128_is_rejected() {
529        let bad = [0x81_u8, 0x7f];
530        let parsed = parse_der_length(&bad).expect("length bytes should be present");
531        assert!(
532            matches!(parsed, Err(())),
533            "DER must reject long-form lengths below 128"
534        );
535    }
536
537    #[test]
538    fn ec_coordinate_length_rounds_up_for_non_byte_aligned_curves() {
539        assert_eq!(
540            ec_coordinate_len_bytes(521).expect("521-bit curves require rounded byte length"),
541            66
542        );
543    }
544
545    #[test]
546    fn same_width_valid_der_is_marked_ambiguous() {
547        let mut signature = Vec::with_capacity(64);
548        signature.extend_from_slice(&[0x30, 0x3e, 0x02, 0x1d]);
549        signature.extend(std::iter::repeat_n(0x11_u8, 29));
550        signature.extend_from_slice(&[0x02, 0x1d]);
551        signature.extend(std::iter::repeat_n(0x22_u8, 29));
552
553        let encoding = classify_ecdsa_signature_encoding(&signature, 32)
554            .expect("same-width structurally valid DER should classify as ambiguous");
555        assert_eq!(encoding, EcdsaSignatureEncoding::Ambiguous);
556    }
557
558    #[test]
559    fn der_integer_longer_than_component_requires_sign_byte() {
560        let mut signature = Vec::with_capacity(72);
561        signature.extend_from_slice(&[0x30, 0x46, 0x02, 0x21, 0x01]);
562        signature.extend(std::iter::repeat_n(0x11_u8, 32));
563        signature.extend_from_slice(&[0x02, 0x21, 0x01]);
564        signature.extend(std::iter::repeat_n(0x22_u8, 32));
565
566        let encoding = classify_ecdsa_signature_encoding(&signature, 32);
567        assert!(matches!(
568            encoding,
569            Err(SignatureVerificationError::InvalidSignatureFormat)
570        ));
571    }
572}