From 7d37bc2dda2664aa314a1d75848199d32fee778c Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 18:08:46 +0200 Subject: [PATCH 1/7] feat(xmldsig): add RSA signature verification - add RSA PKCS#1 v1.5 verification helpers for SHA-1/256/384/512 - cover donor RSA vectors and signature mismatch cases in integration tests - refresh README disclaimer and support footer for the current public status Closes #21 --- README.md | 26 +++- src/xmldsig/mod.rs | 4 + src/xmldsig/signature.rs | 116 +++++++++++++++++ tests/rsa_signature_integration.rs | 200 +++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/xmldsig/signature.rs create mode 100644 tests/rsa_signature_integration.rs diff --git a/README.md b/README.md index 06152b0..35b5ce5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Pure Rust XML Security library. Drop-in replacement for libxmlsec1. **No C dependencies. No cmake. No system libraries. Just `cargo add xml-sec`.** +> [!WARNING] +> Early-stage pre-release. The API is unstable, XMLDSig/XMLEnc coverage is still incomplete, +> and this crate should not yet be used for production security decisions. + ## Features - **C14N** — XML Canonicalization (inclusive + exclusive, W3C compliant) @@ -25,6 +29,16 @@ Every SAML, SOAP, and WS-Security implementation depends on libxmlsec1 — a C l **Pre-release.** API is unstable. Not ready for production use. +Currently implemented: +- C14N 1.0, C14N 1.1, and Exclusive C14N +- XMLDSig parsing, same-document URI dereference, transform chains, and digest verification +- RSA PKCS#1 v1.5 verification helpers for SHA-1 / SHA-256 / SHA-384 / SHA-512 + +Still in progress: +- End-to-end XMLDSig `VerifyContext` +- XMLDSig signing pipeline +- XMLEnc encryption/decryption pipeline + Current toolchain target: latest stable Rust. Current MSRV: Rust 1.92. @@ -32,11 +46,17 @@ Current MSRV: Rust 1.92. | Spec | Status | |------|--------| -| [Canonical XML 1.0](https://www.w3.org/TR/xml-c14n/) | Planned | -| [Exclusive C14N](https://www.w3.org/TR/xml-exc-c14n/) | Planned | -| [XMLDSig](https://www.w3.org/TR/xmldsig-core1/) | Planned | +| [Canonical XML 1.0](https://www.w3.org/TR/xml-c14n/) | In progress | +| [Exclusive C14N](https://www.w3.org/TR/xml-exc-c14n/) | In progress | +| [XMLDSig](https://www.w3.org/TR/xmldsig-core1/) | In progress | | [XMLEnc](https://www.w3.org/TR/xmlenc-core1/) | Planned | ## License Apache-2.0 + +## Support the Project + +If `xml-sec` is useful in your stack, you can help fund continued implementation and maintenance. + +**TRX / TRC-20:** `TFDsezHa1cBkoeZT5q2T49Wp66K8t2DmdA` diff --git a/src/xmldsig/mod.rs b/src/xmldsig/mod.rs index 7a3cbe5..1524fe4 100644 --- a/src/xmldsig/mod.rs +++ b/src/xmldsig/mod.rs @@ -10,6 +10,7 @@ pub mod digest; pub mod parse; +pub mod signature; pub mod transforms; pub mod types; pub mod uri; @@ -19,6 +20,9 @@ pub use digest::{DigestAlgorithm, compute_digest, constant_time_eq}; pub use parse::{ ParseError, Reference, SignatureAlgorithm, SignedInfo, find_signature_node, parse_signed_info, }; +pub use signature::{ + SignatureVerificationError, verify_rsa_signature_pem, verify_rsa_signature_spki, +}; pub use transforms::{Transform, execute_transforms, parse_transforms}; pub use types::{NodeSet, TransformData, TransformError}; pub use verify::{ diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs new file mode 100644 index 0000000..e22d3d9 --- /dev/null +++ b/src/xmldsig/signature.rs @@ -0,0 +1,116 @@ +//! RSA signature verification helpers for XMLDSig. +//! +//! This module covers roadmap task P1-019: RSA PKCS#1 v1.5 verification for +//! `rsa-sha1`, `rsa-sha256`, `rsa-sha384`, and `rsa-sha512`. +//! +//! Input public keys are accepted in SubjectPublicKeyInfo (SPKI) form because +//! that is how the vendored PEM fixtures are stored. `ring` expects the inner +//! ASN.1 `RSAPublicKey` bytes, so we validate and unwrap SPKI first. + +use ring::signature; +use x509_parser::prelude::FromDer; +use x509_parser::public_key::PublicKey; +use x509_parser::x509::SubjectPublicKeyInfo; + +use super::parse::SignatureAlgorithm; + +/// Errors while preparing or running RSA signature verification. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum SignatureVerificationError { + /// The signature method is not an RSA PKCS#1 v1.5 algorithm. + #[error("unsupported signature algorithm: {uri}")] + UnsupportedAlgorithm { + /// XMLDSig algorithm URI used for diagnostics. + uri: String, + }, + + /// The provided PEM block was not a public key. + #[error("invalid key format: expected PUBLIC KEY PEM, got {label}")] + InvalidKeyFormat { + /// The PEM label that was actually supplied. + label: String, + }, + + /// The provided DER bytes were not a valid SPKI-encoded RSA public key. + #[error("invalid RSA SubjectPublicKeyInfo DER")] + InvalidKeyDer, +} + +/// Verify an RSA XMLDSig signature using a PEM-encoded SPKI public key. +/// +/// The PEM must contain a `PUBLIC KEY` block. Returns `Ok(false)` for signature +/// mismatch and `Err` for algorithm/key preparation errors. +pub fn verify_rsa_signature_pem( + algorithm: SignatureAlgorithm, + public_key_pem: &str, + signed_data: &[u8], + signature_value: &[u8], +) -> Result { + let (_, pem) = x509_parser::pem::parse_x509_pem(public_key_pem.as_bytes()) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + if pem.label != "PUBLIC KEY" { + return Err(SignatureVerificationError::InvalidKeyFormat { label: pem.label }); + } + + verify_rsa_signature_spki(algorithm, &pem.contents, signed_data, signature_value) +} + +/// Verify an RSA XMLDSig signature using DER-encoded SPKI public key bytes. +/// +/// The input must be an X.509 `SubjectPublicKeyInfo` wrapping an RSA key. +/// Returns `Ok(false)` for signature mismatch and `Err` for algorithm/key +/// preparation errors. +pub fn verify_rsa_signature_spki( + algorithm: SignatureAlgorithm, + public_key_spki_der: &[u8], + signed_data: &[u8], + signature_value: &[u8], +) -> Result { + let verification_algorithm = verification_algorithm(algorithm)?; + let (_, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let public_key = spki + .parsed() + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + + match public_key { + PublicKey::RSA(_) => { + let key = signature::UnparsedPublicKey::new( + verification_algorithm, + spki.subject_public_key.data, + ); + Ok(key.verify(signed_data, signature_value).is_ok()) + } + _ => Err(SignatureVerificationError::InvalidKeyDer), + } +} + +fn verification_algorithm( + algorithm: SignatureAlgorithm, +) -> Result<&'static dyn signature::VerificationAlgorithm, SignatureVerificationError> { + match algorithm { + SignatureAlgorithm::RsaSha1 => Ok(&signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY), + SignatureAlgorithm::RsaSha256 => Ok(&signature::RSA_PKCS1_2048_8192_SHA256), + SignatureAlgorithm::RsaSha384 => Ok(&signature::RSA_PKCS1_2048_8192_SHA384), + SignatureAlgorithm::RsaSha512 => Ok(&signature::RSA_PKCS1_2048_8192_SHA512), + _ => Err(SignatureVerificationError::UnsupportedAlgorithm { + uri: algorithm.uri().to_string(), + }), + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used, reason = "unit tests use fixed fixture data")] +mod tests { + use super::*; + + #[test] + fn ecdsa_algorithms_are_rejected_for_rsa_verification() { + let err = verification_algorithm(SignatureAlgorithm::EcdsaP256Sha256).unwrap_err(); + assert!(matches!( + err, + SignatureVerificationError::UnsupportedAlgorithm { .. } + )); + } +} diff --git a/tests/rsa_signature_integration.rs b/tests/rsa_signature_integration.rs new file mode 100644 index 0000000..0469019 --- /dev/null +++ b/tests/rsa_signature_integration.rs @@ -0,0 +1,200 @@ +//! Integration tests for XMLDSig RSA signature verification. +//! +//! These tests validate the low-level crypto layer for roadmap task P1-019: +//! canonicalized `` bytes plus a real RSA public key must verify +//! against donor `SignatureValue` bytes for the declared `SignatureMethod`. + +use std::fs; +use std::path::Path; + +use base64::Engine; +use xml_sec::c14n::canonicalize; +use xml_sec::xmldsig::parse::{SignatureAlgorithm, find_signature_node, parse_signed_info}; +use xml_sec::xmldsig::signature::{ + SignatureVerificationError, verify_rsa_signature_pem, verify_rsa_signature_spki, +}; + +fn read_file(path: &str) -> String { + fs::read_to_string(path).unwrap_or_else(|err| panic!("failed to read {path}: {err}")) +} + +fn read_fixture(path: &Path) -> String { + fs::read_to_string(path) + .unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display())) +} + +fn canonicalized_signed_info_and_signature(xml: &str) -> (SignatureAlgorithm, Vec, Vec) { + let doc = roxmltree::Document::parse(xml).expect("fixture XML should parse"); + let signature_node = find_signature_node(&doc).expect("fixture XML should contain Signature"); + let signed_info_node = signature_node + .children() + .find(|node| node.is_element() && node.tag_name().name() == "SignedInfo") + .expect("fixture XML should contain SignedInfo"); + let signed_info = parse_signed_info(signed_info_node).expect("SignedInfo should parse"); + + let mut canonical = Vec::new(); + canonicalize( + &doc, + Some(&|node| { + node == signed_info_node + || node + .ancestors() + .any(|ancestor| ancestor == signed_info_node) + }), + &signed_info.c14n_method, + &mut canonical, + ) + .expect("SignedInfo canonicalization should succeed"); + + let signature_value_text = signature_node + .children() + .find(|node| node.is_element() && node.tag_name().name() == "SignatureValue") + .and_then(|node| node.text()) + .expect("fixture XML should contain SignatureValue text") + .chars() + .filter(|ch| !ch.is_whitespace()) + .collect::(); + let signature_value = base64::engine::general_purpose::STANDARD + .decode(signature_value_text) + .expect("SignatureValue should be valid base64"); + + (signed_info.signature_method, canonical, signature_value) +} + +fn assert_donor_signature_valid( + xml_path: &Path, + public_key_path: &Path, + expected_algorithm: SignatureAlgorithm, +) { + let xml = read_fixture(xml_path); + let public_key_pem = read_fixture(public_key_path); + let (algorithm, canonical_signed_info, signature_value) = + canonicalized_signed_info_and_signature(&xml); + + assert_eq!(algorithm, expected_algorithm, "unexpected SignatureMethod"); + + let valid = verify_rsa_signature_pem( + algorithm, + &public_key_pem, + &canonical_signed_info, + &signature_value, + ) + .expect("RSA verification should not error on valid fixtures"); + assert!(valid, "donor RSA signature should verify"); +} + +#[test] +fn donor_rsa_sha1_signature_matches() { + assert_donor_signature_valid( + Path::new("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml"), + Path::new("tests/fixtures/keys/rsa/rsa-4096-pubkey.pem"), + SignatureAlgorithm::RsaSha1, + ); +} + +#[test] +fn donor_rsa_sha256_signature_matches() { + assert_donor_signature_valid( + Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"), + Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"), + SignatureAlgorithm::RsaSha256, + ); +} + +#[test] +fn donor_rsa_sha384_signature_matches() { + assert_donor_signature_valid( + Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml"), + Path::new("tests/fixtures/keys/rsa/rsa-4096-pubkey.pem"), + SignatureAlgorithm::RsaSha384, + ); +} + +#[test] +fn donor_rsa_sha512_signature_matches() { + assert_donor_signature_valid( + Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml"), + Path::new("tests/fixtures/keys/rsa/rsa-4096-pubkey.pem"), + SignatureAlgorithm::RsaSha512, + ); +} + +#[test] +fn tampered_signed_info_fails_verification() { + let xml = read_file( + "/Users/polaz/projects/sw/xml-sec/donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", + ); + let public_key_pem = + read_file("/Users/polaz/projects/sw/xml-sec/tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"); + let (algorithm, mut canonical_signed_info, signature_value) = + canonicalized_signed_info_and_signature(&xml); + + let last_index = canonical_signed_info.len() - 1; + canonical_signed_info[last_index] ^= 0x01; + + let valid = verify_rsa_signature_pem( + algorithm, + &public_key_pem, + &canonical_signed_info, + &signature_value, + ) + .expect("tampered data should still be a valid verification attempt"); + + assert!( + !valid, + "tampering SignedInfo bytes must break signature verification" + ); +} + +#[test] +fn wrong_spki_key_fails_verification() { + let xml = read_file( + "/Users/polaz/projects/sw/xml-sec/donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", + ); + let public_key_pem = + read_file("/Users/polaz/projects/sw/xml-sec/tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"); + let (algorithm, canonical_signed_info, signature_value) = + canonicalized_signed_info_and_signature(&xml); + + let valid = verify_rsa_signature_pem( + algorithm, + &public_key_pem, + &canonical_signed_info, + &signature_value, + ) + .expect("wrong key should still be a valid verification attempt"); + + assert!( + !valid, + "verification must fail with a different RSA public key" + ); +} + +#[test] +fn malformed_pem_returns_typed_error() { + let err = verify_rsa_signature_pem( + SignatureAlgorithm::RsaSha256, + "-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----\n", + b"payload", + b"signature", + ) + .expect_err("non-public-key PEM should be rejected"); + + assert!(matches!( + err, + SignatureVerificationError::InvalidKeyFormat { .. } + )); +} + +#[test] +fn malformed_spki_der_returns_typed_error() { + let err = verify_rsa_signature_spki( + SignatureAlgorithm::RsaSha256, + &[0x01, 0x02, 0x03], + b"payload", + b"signature", + ) + .expect_err("malformed SPKI DER should be rejected"); + + assert!(matches!(err, SignatureVerificationError::InvalidKeyDer)); +} From c1e265f6992d506f201d8eba3ba911489c0bb360 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 18:38:19 +0200 Subject: [PATCH 2/7] test(xmldsig): vendor donor RSA fixtures --- .../enveloping-sha256-rsa-sha256.xml | 111 ++++++++++++++++ .../enveloping-sha384-rsa-sha384.xml | 121 +++++++++++++++++ .../enveloping-sha512-rsa-sha512.xml | 122 ++++++++++++++++++ tests/fixtures_smoke.rs | 4 +- tests/rsa_signature_integration.rs | 31 ++--- 5 files changed, 368 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml create mode 100644 tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml create mode 100644 tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml diff --git a/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml new file mode 100644 index 0000000..051a9ef --- /dev/null +++ b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml @@ -0,0 +1,111 @@ + + + + + + + + iDhYt78o294fA6pzQ7k44+eejrQMi+WX3l3UrUdtL1Q= + + + 1UQLUaUhBSZLindLf9zDhunnNA/0yTP0GkjSVqxWw1NuhgjvSo17VygyjatZa+fm +jU52ZSRGGjmpSygFwj5HwTFNAmzMLo2r8RdB+hSU9xLL+51pPCnK9oSJXs/dFmDh +b0bwPDvlLVvP5XA4fCKy9ZObljCAh6yRzTjavqOt5XFEf+bf8NzC2afilfwOFUVn +fKKIHmPyy3oYZGxbNP8RXRlqFz35779oftfAKGFP6jzks+gLQ3Lj9xsfZvqcCEMj +yWufppGXTOM2ttiEcFEaRN8hUjeNsKsExFeLfikAijKp3UYRtiqphBibojsmahU3 +koRumVQ6L/tDhTJv9lf7dQ== + + TestKeyName-rsa-2048 + +MIIFFjCCA/6gAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT1MwDQYJKoZIhvcNAQEL +BQAwgbYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRgwFgYDVQQLEw9TZWNvbmQgbGV2ZWwgQ0ExFjAUBgNVBAMTDUFsZWtz +ZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTAgFw0y +NjAzMDgyMjE0MTZaGA8yMTI2MDIxMjIyMTQxNlowfTELMAkGA1UEBhMCVVMxEzAR +BgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJyYXJ5 +IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxGjAYBgNVBAMTEVRlc3Qg +S2V5IHJzYS0yMDQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3iVn +hDXlgGiWvV2f21bCP4NAeTkwouWvN9K94SNeV01xzuvPg2GRb+ozF0/YbQ8jj7UD +euIgcLoBrC/jSMtp7gJp6zj3oHhX97NZVv5SBUmBOJRbB8efy3apTvTvWlzJQhO4 +WVXBRDqmA0dGHPRRuMB6l125wy+WBMWxO6BzUooe9m0OpQXjKokJKFcl9Zd4ht3E +qXW8cuHgiyrtYzXTcO63W9+J6dFOi1DTYhKMkK53jMsModlleEUdqUHgTbxK0TBS +YMa2sB1rGfcVq+QanOlsRHXAXiZM/BE6uPqlFq+g5osSZbHfH2qC/0G/wKKlWzIx +0YPjQSBq/MbroodKLQIDAQABo4IBUDCCAUwwDAYDVR0TBAUwAwEB/zAsBglghkgB +hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE +FG3Dlzf57FZfBmrUW3Cqz28yG8NGMIHuBgNVHSMEgeYwgeOAFNF9F6xFQoqO+bAX +JdU8cpidiDoloYG0pIGxMIGuMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZv +cm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cu +YWxla3NleS5jb20veG1sc2VjKTEQMA4GA1UECxMHUm9vdCBDQTEWMBQGA1UEAxMN +QWxla3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29t +ghR3Ne5If2hi2vGzlW2WHMsPpvNPTzANBgkqhkiG9w0BAQsFAAOCAQEATAu+Gt18 +Kg0CW8kT+l92sfsNysxS/eYJD3iNyku0oE72jmWVsOvS9phHDF0q01tv8SsIjio6 +sUQXoQ+C+YDAI3g9M5imN5l41TGZF1yRS0i5VucZpnmMcWtNWEpkJd5mB6l4VRDK +IarRS3UuA2cmZdtfRNsXAnG7sCLiiQB5wWF0Gbe6oAb0Y+hURG7D2vnIAimi2lcH +LgCD9eXbGfMNhNYnN+hbTNZuwvbJIDWMLu5VWpOdeX+Axm5MXI7lNPMRda75uPRQ +O52QICz4Cz3lbq4SEhiF4CQ5h/FRj6Yc8m+ZY3/5khICap0dUjAmmezcVwMJlxXr +hwvZjgian+dyQw== + +MIIFEjCCA/qgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT04wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NDQ3WhgPMjEyNjAyMTIyMjA0NDdaMIGuMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEQMA4GA1UECxMHUm9vdCBDQTEWMBQG +A1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtz +ZXkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbKBr4EAoOm8 +zAW/RL8Wrd1+24EPUbz1RYQuSlcuHcyBwt3uGXVvXNQ6fCGLq5ikIi3NkMymecIB +9u1Jc96T0FkpSxQrqIIvlCP6gpaLBa+lz8ix8Xeb0uJ4Dg8RDmwTBfQU2ENagYLc +v0mpW7myAmqzGq1+xdgd2Cbt1FRB5t4YKqNl6+pFbeXL9EGbRoNPyuu+CfWrWVEe +JfWD1YzM6fhB0c/zqCxC32Y1h/sAzNFyYRmYUULh2MwVBVyt839h0jAUBzzBh0/q +EoVL5a9daDx3m6+PS1ACb+nXUSYaOu8lVM2rPxRjVITHBo+NHn012T8JNrwcExMn +FgeaVXo+NwIDAQABo4IBIjCCAR4wHQYDVR0OBBYEFDN5WuQBQ05geQStksygwwDM +bhBEMIHuBgNVHSMEgeYwgeOAFDN5WuQBQ05geQStksygwwDMbhBEoYG0pIGxMIGu +MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1M +IFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2Vj +KTEQMA4GA1UECxMHUm9vdCBDQTEWMBQGA1UEAxMNQWxla3NleSBTYW5pbjEhMB8G +CSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tghR3Ne5If2hi2vGzlW2WHMsP +pvNPTjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBVUqxGkYxvFZ7s +/Zkmjj1u88PvOjdj36LnGQCyVDwJPXXAXoqW9I3W3BPra/Xy1vjFo5erkdjvNh0f ++iyZVS/9EVdPKssPdZd39p0YIiUyG1RUYmN/IBDzSX/LwBTiLGlMBHFTRj6Lfs+e +Nfu6PISqABh+It3/3jlB882eixesdsjvtZc0J6sDka1byMoqe40twfI0tTMMv4Jr +QwGn7YeiVuWZ/GKUQxYrA+FfIWR5maBWtnhdmjaOBgmUhNtJB8yBcH82cRrxRIMX +f2quQrB3P4/WQC2bw3YPGxSv5wQoZwcwCn6f7g7oQFy6cANONMBG7GJpbw5B/8uB +S1a6d6Rg + +MIIFSDCCBDCgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT08wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NzQyWhgPMjEyNjAyMTIyMjA3NDJaMIG2MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEYMBYGA1UECxMPU2Vjb25kIGxldmVs +IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxz +ZWNAYWxla3NleS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDw +fEPVxQD3TLiNRpoh7g1KRYODmnJcdJzi7FMXfKuAgkhNmQaoAHQd7/pcwtg3oNUH +QukupST89AC7/qakF7ykdEQnVzxggYgdXbhfDZhLcaVUuMtFGgM6lHL0hnSZo8U9 +LHKWOlPIhJemE/XziHqgAsQposis7IRhuUlSsDa2xFW7MfS2xF/+UhiclaHgyBZ/ +RDzn2b5K14VAJdt1xRaoMC5zVIzu1uk33+j97L78+z65VRG7fxGTau2c94Mcl2V+ +KjDulHAnxLVJkjczo0mVi+u0Vczq9VhUqbNlig9TERQAPBC3D4ZJHhJsBmJz+47i +7pNL/Pms8qr9U0PgbZaPAgMBAAGjggFQMIIBTDAMBgNVHRMEBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E +FgQU0X0XrEVCio75sBcl1TxymJ2IOiUwge4GA1UdIwSB5jCB44AUM3la5AFDTmB5 +BK2SzKDDAMxuEEShgbSkgbEwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3 +dy5hbGVrc2V5LmNvbS94bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQD +Ew1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5j +b22CFHc17kh/aGLa8bOVbZYcyw+m809OMA0GCSqGSIb3DQEBCwUAA4IBAQAhrm/J +FdnYclb8HwQJdgGSYtUw2Wdrl1950H/ZUGwSKs6lGX8YT5xnj55AELLhbetTo+Be +Wwmg9kZbqnRC9tt0vIhFMko/uQZkn7vzrFEIfXgnEm2UGkkULfXH9pgtO4A9EQ2s +bbR4Oyi3n9q1w39aBdkUZnw3uthWKVHjcMW+n4m0RZBh4/snhHHlnxaIJzm4lB/s +DKNcXJTJHUbd1Kch5aOuSXCCmltwpEdEM9yaY1mr+jH9aD7lfo3FEJQxpO6M+AH6 +JDdmS2LzQUSXDO4fibegrI/IeTQeST92mZI4foLxqp6SG19WGs9sNFFDYCIl6lNq +LDAhZycGntLtGaZF + + + + some text + diff --git a/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml new file mode 100644 index 0000000..6bb0b2c --- /dev/null +++ b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml @@ -0,0 +1,121 @@ + + + + + + + + uTx8AeqrTmv+nijRsWW7TOs1pCIuCudsFRVloP6hPin8Q4x9fFX2j/zj53XB37OG + + + BztbqWVeHltMfsEw04bMEfKq94IwCDoo2/26g3bAVXSzizarWR0h2ZLlrdQf2XiD +J2LMgqnsEX9e1HgnVBNqkkCizvJcO5uxpxcAqp2DttDe2tBl3ySnkjtBfhfrGYfv +tzl0j0OHnXfn0fZJO077O5lcGjyBZlzxr0MnT4KUIINV9+eqvVndHV5E6wCD2J5H +wWSrUX+57LZC7MmvaZ8xoLjAefyc/nUwDUJco52zhspV0t7FZq0q6DyPBIVJDI52 +aNKE2mUvWB49ZdecOxeYlhhrhT22jAfoXa7Bkkmgkm9/NI9FDYTLqZvNZ7704UXd +1D+l2Gvec8nRrWNwu9HxhYnMJt0INuflmOcXDfwmoEJeySwX5E2dAwD+Cpig7DDN +d6NqV0x+nY+snZlDyPBI8Bif9Uek1M1KgdLVcfrxvKibBivx5KupBk1/Zs+9yeh8 +vreHZknIOWI6UbKWv02MOftPdA6fZBG7s76XlHZP4izLK2D+PXdyHjYgPUitAaTL +ZILTYgdz3uuzSujutWDXxbmOwDil1ImabvQQZRbB60agupRGmaZ5fdBkF+OVA5vw +/F76c2pKM4pri4OegD+lDdPzX8nI67fHHCOFWhBnqdhnomtN6P1BvF0i9Yq2n0uL +0v0IVhW1uMNbTsCijr4hxpYyKwmP01pNl3koAepbfAs= + + TestKeyName-rsa-4096 + +MIIGFjCCBP6gAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT1QwDQYJKoZIhvcNAQEL +BQAwgbYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRgwFgYDVQQLEw9TZWNvbmQgbGV2ZWwgQ0ExFjAUBgNVBAMTDUFsZWtz +ZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTAgFw0y +NjAzMDgyMjE0MjdaGA8yMTI2MDIxMjIyMTQyN1owfTELMAkGA1UEBhMCVVMxEzAR +BgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJyYXJ5 +IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxGjAYBgNVBAMTEVRlc3Qg +S2V5IHJzYS00MDk2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApFht +8oSsDMTV1GOWqg7o7NVllW2835GtOiLPJ/dnRy9vEQGYqB+KSSX9axIVyrFNfiS6 +SsH7Tam9A4bEHSBP6risPS9wyoTpyy7q2a31ZUETmI0pFej9db2vgAvpmx8DX3e8 +Y03SecMYqNLr2HLj7daWhu9/OY+xrd6q567NLFmG3335Hr8wHNwl/HbdCwmXBWaw +16D1g1RSV0kkFvD1gHMZqAjANJsqmuKJogMj2waXFAy/KAsUK3cPDCDKu7KHxtAT +LZ0UWqPNgz8GLBPI87vmyxSGgbtw1XQWQa4zxdknz80qRGExROc8DMszs575N7+Z +vjSusT+PT+CQxmDSyiqtp/v2OIQS3zAR7uFv0Xb3DS5Cs0VWkGU/N0mrdU1nd4WB +ecdTZCXbG959r1HKtqiWKa7nOOxKTrwHr9cDrlzSW2kyXb/1tvdEE5gy1HNOil07 +SVUv4Vi6CRvUa3Jv/B6ifb0lY9wp82gc42TOLv9Udx9xm7Barh8lfzqc4T+VR1ON +jbHzeLyMC6aG54rZZpcorUOT7f6SfPHcbx2Jmz3T4g3UaML4nlJkBfpBSkBRloRp +dKffhnpyru5OQEb7fLxlDePEAOkYrTNbBaHEXFqO1gKSxz3Mg+4gCA9mZOXP9yLC +Dz10S9vROUVSu9H4CT4ucUPT2cEg95X5H/2fqRMCAwEAAaOCAVAwggFMMAwGA1Ud +EwQFMAMBAf8wLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp +ZmljYXRlMB0GA1UdDgQWBBTrTMwsoJ/NDeqdcDMAHNE2l2BDxDCB7gYDVR0jBIHm +MIHjgBTRfResRUKKjvmwFyXVPHKYnYg6JaGBtKSBsTCBrjELMAkGA1UEBhMCVVMx +EzARBgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJy +YXJ5IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxEDAOBgNVBAsTB1Jv +b3QgQ0ExFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnht +bHNlY0BhbGVrc2V5LmNvbYIUdzXuSH9oYtrxs5VtlhzLD6bzT08wDQYJKoZIhvcN +AQELBQADggEBAIlPInAPcM4cRhUqeRmWcwi4Ue5xkn5UL8hNPKsfbNDvRoa3aOKz +jc0WNd7LQZJzALP4Q+VoE9+TDYbM0fwIGpUr0RMcNUt1DzwVKyDKtbv/tOayIS2j +L8pI9AALHzjGdRjk713LuOd1ixwvEyaVBJtQ+vz39c3baSJQtSvXeVU0KLxJSqy8 +fgbBuiNx+hZiGsee9BzocAyCoSyKuyEqhxbvcBSDIIgLyCoroR/uXk+wsBgy/15X +QGa1GHVWwSLdaqACDlqvu5PYMMQq4IfY+fADf3tYl81ZiIJerRcHwZMa2zgof/lZ +0tzrqr9lKyHcVitF2kkwC50xi2hvkDrZhvI= + +MIIFEjCCA/qgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT04wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NDQ3WhgPMjEyNjAyMTIyMjA0NDdaMIGuMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEQMA4GA1UECxMHUm9vdCBDQTEWMBQG +A1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtz +ZXkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbKBr4EAoOm8 +zAW/RL8Wrd1+24EPUbz1RYQuSlcuHcyBwt3uGXVvXNQ6fCGLq5ikIi3NkMymecIB +9u1Jc96T0FkpSxQrqIIvlCP6gpaLBa+lz8ix8Xeb0uJ4Dg8RDmwTBfQU2ENagYLc +v0mpW7myAmqzGq1+xdgd2Cbt1FRB5t4YKqNl6+pFbeXL9EGbRoNPyuu+CfWrWVEe +JfWD1YzM6fhB0c/zqCxC32Y1h/sAzNFyYRmYUULh2MwVBVyt839h0jAUBzzBh0/q +EoVL5a9daDx3m6+PS1ACb+nXUSYaOu8lVM2rPxRjVITHBo+NHn012T8JNrwcExMn +FgeaVXo+NwIDAQABo4IBIjCCAR4wHQYDVR0OBBYEFDN5WuQBQ05geQStksygwwDM +bhBEMIHuBgNVHSMEgeYwgeOAFDN5WuQBQ05geQStksygwwDMbhBEoYG0pIGxMIGu +MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1M +IFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2Vj +KTEQMA4GA1UECxMHUm9vdCBDQTEWMBQGA1UEAxMNQWxla3NleSBTYW5pbjEhMB8G +CSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tghR3Ne5If2hi2vGzlW2WHMsP +pvNPTjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBVUqxGkYxvFZ7s +/Zkmjj1u88PvOjdj36LnGQCyVDwJPXXAXoqW9I3W3BPra/Xy1vjFo5erkdjvNh0f ++iyZVS/9EVdPKssPdZd39p0YIiUyG1RUYmN/IBDzSX/LwBTiLGlMBHFTRj6Lfs+e +Nfu6PISqABh+It3/3jlB882eixesdsjvtZc0J6sDka1byMoqe40twfI0tTMMv4Jr +QwGn7YeiVuWZ/GKUQxYrA+FfIWR5maBWtnhdmjaOBgmUhNtJB8yBcH82cRrxRIMX +f2quQrB3P4/WQC2bw3YPGxSv5wQoZwcwCn6f7g7oQFy6cANONMBG7GJpbw5B/8uB +S1a6d6Rg + +MIIFSDCCBDCgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT08wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NzQyWhgPMjEyNjAyMTIyMjA3NDJaMIG2MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEYMBYGA1UECxMPU2Vjb25kIGxldmVs +IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxz +ZWNAYWxla3NleS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDw +fEPVxQD3TLiNRpoh7g1KRYODmnJcdJzi7FMXfKuAgkhNmQaoAHQd7/pcwtg3oNUH +QukupST89AC7/qakF7ykdEQnVzxggYgdXbhfDZhLcaVUuMtFGgM6lHL0hnSZo8U9 +LHKWOlPIhJemE/XziHqgAsQposis7IRhuUlSsDa2xFW7MfS2xF/+UhiclaHgyBZ/ +RDzn2b5K14VAJdt1xRaoMC5zVIzu1uk33+j97L78+z65VRG7fxGTau2c94Mcl2V+ +KjDulHAnxLVJkjczo0mVi+u0Vczq9VhUqbNlig9TERQAPBC3D4ZJHhJsBmJz+47i +7pNL/Pms8qr9U0PgbZaPAgMBAAGjggFQMIIBTDAMBgNVHRMEBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E +FgQU0X0XrEVCio75sBcl1TxymJ2IOiUwge4GA1UdIwSB5jCB44AUM3la5AFDTmB5 +BK2SzKDDAMxuEEShgbSkgbEwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3 +dy5hbGVrc2V5LmNvbS94bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQD +Ew1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5j +b22CFHc17kh/aGLa8bOVbZYcyw+m809OMA0GCSqGSIb3DQEBCwUAA4IBAQAhrm/J +FdnYclb8HwQJdgGSYtUw2Wdrl1950H/ZUGwSKs6lGX8YT5xnj55AELLhbetTo+Be +Wwmg9kZbqnRC9tt0vIhFMko/uQZkn7vzrFEIfXgnEm2UGkkULfXH9pgtO4A9EQ2s +bbR4Oyi3n9q1w39aBdkUZnw3uthWKVHjcMW+n4m0RZBh4/snhHHlnxaIJzm4lB/s +DKNcXJTJHUbd1Kch5aOuSXCCmltwpEdEM9yaY1mr+jH9aD7lfo3FEJQxpO6M+AH6 +JDdmS2LzQUSXDO4fibegrI/IeTQeST92mZI4foLxqp6SG19WGs9sNFFDYCIl6lNq +LDAhZycGntLtGaZF + + + + some text + diff --git a/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml new file mode 100644 index 0000000..1a6b967 --- /dev/null +++ b/tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml @@ -0,0 +1,122 @@ + + + + + + + + E2Jo801uUCgAIa65niLU7jPSWPWUbsgT+okPgBcw/h72V7bmI0J2faJ+8EbwVwah +XDnbRaf22WqerzX1vL0QzA== + + + BaSRnXHpRml6kvWLtbeUtzUp+iFDhdLyUsbGUmMv93j2L9ioh0t4n6bTCrF42uSb +UtL1zl9fxavLxUnrdRnvmsJXAycJqq9Y6nycafzshl90cCVlz+dOKj4luY8+bxME +V8jnzvq6drzk73wqbrHtpCcRVflHGiGqYz7kdU42Mk/DM2KNBJd1u6wHppY/X3JR +56K/jEU2dBft9pjD0bc46teS5ibSx+EP5obTR6NpwJwLPtlZsm1Z17Q0wlWDKhot +n1S139XOX8CZA7rtlpOxLbW1+DEVm127o3F8NKFWoSpnSWGkzgkJ/b4KvNr0NQfu +Dc0dTOBNkJC33OrqzKN8K32aYVli5uB+fL68HNZ2x5NzXSvxDk4f/6NVoELcksth +5uy5T/yPbfotDHPHpIx7fBZfCz9m2rfo+8/ah7SFwbAbiePDcDO/CsUUBUg7vwZc +2OW9odhrVDGJj3rKaPt3ZnUXFsl7ABtCyEBvoA0MvtyEW+Pz17fVditwr1Cyz+w6 +lX9Ucj/hpZLT/R35/7Brn329k5Uhwa2HebPBFSdWZWYtzVIGhkPMaklwkEYVLpRC +ntf5FixKG4G+3BdrJYecMrhRWVxuT5cqn+ndIXzh54UxOQmwf4uCwoMSi+tKgDXI +5Y2AsYS8GJLaVolfva0Yw7blT7RUBExPX7rrm0+IHxY= + + TestKeyName-rsa-4096 + +MIIGFjCCBP6gAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT1QwDQYJKoZIhvcNAQEL +BQAwgbYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRgwFgYDVQQLEw9TZWNvbmQgbGV2ZWwgQ0ExFjAUBgNVBAMTDUFsZWtz +ZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTAgFw0y +NjAzMDgyMjE0MjdaGA8yMTI2MDIxMjIyMTQyN1owfTELMAkGA1UEBhMCVVMxEzAR +BgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJyYXJ5 +IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxGjAYBgNVBAMTEVRlc3Qg +S2V5IHJzYS00MDk2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApFht +8oSsDMTV1GOWqg7o7NVllW2835GtOiLPJ/dnRy9vEQGYqB+KSSX9axIVyrFNfiS6 +SsH7Tam9A4bEHSBP6risPS9wyoTpyy7q2a31ZUETmI0pFej9db2vgAvpmx8DX3e8 +Y03SecMYqNLr2HLj7daWhu9/OY+xrd6q567NLFmG3335Hr8wHNwl/HbdCwmXBWaw +16D1g1RSV0kkFvD1gHMZqAjANJsqmuKJogMj2waXFAy/KAsUK3cPDCDKu7KHxtAT +LZ0UWqPNgz8GLBPI87vmyxSGgbtw1XQWQa4zxdknz80qRGExROc8DMszs575N7+Z +vjSusT+PT+CQxmDSyiqtp/v2OIQS3zAR7uFv0Xb3DS5Cs0VWkGU/N0mrdU1nd4WB +ecdTZCXbG959r1HKtqiWKa7nOOxKTrwHr9cDrlzSW2kyXb/1tvdEE5gy1HNOil07 +SVUv4Vi6CRvUa3Jv/B6ifb0lY9wp82gc42TOLv9Udx9xm7Barh8lfzqc4T+VR1ON +jbHzeLyMC6aG54rZZpcorUOT7f6SfPHcbx2Jmz3T4g3UaML4nlJkBfpBSkBRloRp +dKffhnpyru5OQEb7fLxlDePEAOkYrTNbBaHEXFqO1gKSxz3Mg+4gCA9mZOXP9yLC +Dz10S9vROUVSu9H4CT4ucUPT2cEg95X5H/2fqRMCAwEAAaOCAVAwggFMMAwGA1Ud +EwQFMAMBAf8wLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp +ZmljYXRlMB0GA1UdDgQWBBTrTMwsoJ/NDeqdcDMAHNE2l2BDxDCB7gYDVR0jBIHm +MIHjgBTRfResRUKKjvmwFyXVPHKYnYg6JaGBtKSBsTCBrjELMAkGA1UEBhMCVVMx +EzARBgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJy +YXJ5IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxEDAOBgNVBAsTB1Jv +b3QgQ0ExFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnht +bHNlY0BhbGVrc2V5LmNvbYIUdzXuSH9oYtrxs5VtlhzLD6bzT08wDQYJKoZIhvcN +AQELBQADggEBAIlPInAPcM4cRhUqeRmWcwi4Ue5xkn5UL8hNPKsfbNDvRoa3aOKz +jc0WNd7LQZJzALP4Q+VoE9+TDYbM0fwIGpUr0RMcNUt1DzwVKyDKtbv/tOayIS2j +L8pI9AALHzjGdRjk713LuOd1ixwvEyaVBJtQ+vz39c3baSJQtSvXeVU0KLxJSqy8 +fgbBuiNx+hZiGsee9BzocAyCoSyKuyEqhxbvcBSDIIgLyCoroR/uXk+wsBgy/15X +QGa1GHVWwSLdaqACDlqvu5PYMMQq4IfY+fADf3tYl81ZiIJerRcHwZMa2zgof/lZ +0tzrqr9lKyHcVitF2kkwC50xi2hvkDrZhvI= + +MIIFEjCCA/qgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT04wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NDQ3WhgPMjEyNjAyMTIyMjA0NDdaMIGuMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEQMA4GA1UECxMHUm9vdCBDQTEWMBQG +A1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtz +ZXkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbKBr4EAoOm8 +zAW/RL8Wrd1+24EPUbz1RYQuSlcuHcyBwt3uGXVvXNQ6fCGLq5ikIi3NkMymecIB +9u1Jc96T0FkpSxQrqIIvlCP6gpaLBa+lz8ix8Xeb0uJ4Dg8RDmwTBfQU2ENagYLc +v0mpW7myAmqzGq1+xdgd2Cbt1FRB5t4YKqNl6+pFbeXL9EGbRoNPyuu+CfWrWVEe +JfWD1YzM6fhB0c/zqCxC32Y1h/sAzNFyYRmYUULh2MwVBVyt839h0jAUBzzBh0/q +EoVL5a9daDx3m6+PS1ACb+nXUSYaOu8lVM2rPxRjVITHBo+NHn012T8JNrwcExMn +FgeaVXo+NwIDAQABo4IBIjCCAR4wHQYDVR0OBBYEFDN5WuQBQ05geQStksygwwDM +bhBEMIHuBgNVHSMEgeYwgeOAFDN5WuQBQ05geQStksygwwDMbhBEoYG0pIGxMIGu +MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1M +IFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2Vj +KTEQMA4GA1UECxMHUm9vdCBDQTEWMBQGA1UEAxMNQWxla3NleSBTYW5pbjEhMB8G +CSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tghR3Ne5If2hi2vGzlW2WHMsP +pvNPTjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBVUqxGkYxvFZ7s +/Zkmjj1u88PvOjdj36LnGQCyVDwJPXXAXoqW9I3W3BPra/Xy1vjFo5erkdjvNh0f ++iyZVS/9EVdPKssPdZd39p0YIiUyG1RUYmN/IBDzSX/LwBTiLGlMBHFTRj6Lfs+e +Nfu6PISqABh+It3/3jlB882eixesdsjvtZc0J6sDka1byMoqe40twfI0tTMMv4Jr +QwGn7YeiVuWZ/GKUQxYrA+FfIWR5maBWtnhdmjaOBgmUhNtJB8yBcH82cRrxRIMX +f2quQrB3P4/WQC2bw3YPGxSv5wQoZwcwCn6f7g7oQFy6cANONMBG7GJpbw5B/8uB +S1a6d6Rg + +MIIFSDCCBDCgAwIBAgIUdzXuSH9oYtrxs5VtlhzLD6bzT08wDQYJKoZIhvcNAQEL +BQAwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQK +EzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94 +bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmlu +MSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb20wIBcNMjYwMzA4MjIw +NzQyWhgPMjEyNjAyMTIyMjA3NDJaMIG2MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6 +Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEYMBYGA1UECxMPU2Vjb25kIGxldmVs +IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxz +ZWNAYWxla3NleS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDw +fEPVxQD3TLiNRpoh7g1KRYODmnJcdJzi7FMXfKuAgkhNmQaoAHQd7/pcwtg3oNUH +QukupST89AC7/qakF7ykdEQnVzxggYgdXbhfDZhLcaVUuMtFGgM6lHL0hnSZo8U9 +LHKWOlPIhJemE/XziHqgAsQposis7IRhuUlSsDa2xFW7MfS2xF/+UhiclaHgyBZ/ +RDzn2b5K14VAJdt1xRaoMC5zVIzu1uk33+j97L78+z65VRG7fxGTau2c94Mcl2V+ +KjDulHAnxLVJkjczo0mVi+u0Vczq9VhUqbNlig9TERQAPBC3D4ZJHhJsBmJz+47i +7pNL/Pms8qr9U0PgbZaPAgMBAAGjggFQMIIBTDAMBgNVHRMEBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E +FgQU0X0XrEVCio75sBcl1TxymJ2IOiUwge4GA1UdIwSB5jCB44AUM3la5AFDTmB5 +BK2SzKDDAMxuEEShgbSkgbEwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3 +dy5hbGVrc2V5LmNvbS94bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQD +Ew1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5j +b22CFHc17kh/aGLa8bOVbZYcyw+m809OMA0GCSqGSIb3DQEBCwUAA4IBAQAhrm/J +FdnYclb8HwQJdgGSYtUw2Wdrl1950H/ZUGwSKs6lGX8YT5xnj55AELLhbetTo+Be +Wwmg9kZbqnRC9tt0vIhFMko/uQZkn7vzrFEIfXgnEm2UGkkULfXH9pgtO4A9EQ2s +bbR4Oyi3n9q1w39aBdkUZnw3uthWKVHjcMW+n4m0RZBh4/snhHHlnxaIJzm4lB/s +DKNcXJTJHUbd1Kch5aOuSXCCmltwpEdEM9yaY1mr+jH9aD7lfo3FEJQxpO6M+AH6 +JDdmS2LzQUSXDO4fibegrI/IeTQeST92mZI4foLxqp6SG19WGs9sNFFDYCIl6lNq +LDAhZycGntLtGaZF + + + + some text + diff --git a/tests/fixtures_smoke.rs b/tests/fixtures_smoke.rs index 7bdeba3..d336107 100644 --- a/tests/fixtures_smoke.rs +++ b/tests/fixtures_smoke.rs @@ -175,8 +175,8 @@ fn fixture_file_count_matches_expected() { let mut count = 0; count_files_recursive(fixtures_dir(), &mut count); assert_eq!( - count, 66, - "expected 66 fixture files total (21 keys + 41 c14n + 4 donor xmldsig); \ + count, 69, + "expected 69 fixture files total (21 keys + 41 c14n + 7 donor xmldsig); \ if you added/removed files, update this count" ); } diff --git a/tests/rsa_signature_integration.rs b/tests/rsa_signature_integration.rs index 0469019..4c3d12a 100644 --- a/tests/rsa_signature_integration.rs +++ b/tests/rsa_signature_integration.rs @@ -4,7 +4,6 @@ //! canonicalized `` bytes plus a real RSA public key must verify //! against donor `SignatureValue` bytes for the declared `SignatureMethod`. -use std::fs; use std::path::Path; use base64::Engine; @@ -14,12 +13,8 @@ use xml_sec::xmldsig::signature::{ SignatureVerificationError, verify_rsa_signature_pem, verify_rsa_signature_spki, }; -fn read_file(path: &str) -> String { - fs::read_to_string(path).unwrap_or_else(|err| panic!("failed to read {path}: {err}")) -} - fn read_fixture(path: &Path) -> String { - fs::read_to_string(path) + std::fs::read_to_string(path) .unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display())) } @@ -95,7 +90,7 @@ fn donor_rsa_sha1_signature_matches() { #[test] fn donor_rsa_sha256_signature_matches() { assert_donor_signature_valid( - Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"), + Path::new("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"), Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"), SignatureAlgorithm::RsaSha256, ); @@ -104,7 +99,7 @@ fn donor_rsa_sha256_signature_matches() { #[test] fn donor_rsa_sha384_signature_matches() { assert_donor_signature_valid( - Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml"), + Path::new("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml"), Path::new("tests/fixtures/keys/rsa/rsa-4096-pubkey.pem"), SignatureAlgorithm::RsaSha384, ); @@ -113,7 +108,7 @@ fn donor_rsa_sha384_signature_matches() { #[test] fn donor_rsa_sha512_signature_matches() { assert_donor_signature_valid( - Path::new("donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml"), + Path::new("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml"), Path::new("tests/fixtures/keys/rsa/rsa-4096-pubkey.pem"), SignatureAlgorithm::RsaSha512, ); @@ -121,11 +116,10 @@ fn donor_rsa_sha512_signature_matches() { #[test] fn tampered_signed_info_fails_verification() { - let xml = read_file( - "/Users/polaz/projects/sw/xml-sec/donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", - ); - let public_key_pem = - read_file("/Users/polaz/projects/sw/xml-sec/tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"); + let xml = read_fixture(Path::new( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", + )); + let public_key_pem = read_fixture(Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")); let (algorithm, mut canonical_signed_info, signature_value) = canonicalized_signed_info_and_signature(&xml); @@ -148,11 +142,10 @@ fn tampered_signed_info_fails_verification() { #[test] fn wrong_spki_key_fails_verification() { - let xml = read_file( - "/Users/polaz/projects/sw/xml-sec/donors/xmlsec/tests/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", - ); - let public_key_pem = - read_file("/Users/polaz/projects/sw/xml-sec/tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"); + let xml = read_fixture(Path::new( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", + )); + let public_key_pem = read_fixture(Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")); let (algorithm, canonical_signed_info, signature_value) = canonicalized_signed_info_and_signature(&xml); From 559021fee65112514b0b2c70064fa5adc986c591 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 18:44:07 +0200 Subject: [PATCH 3/7] fix(xmldsig): harden RSA key parsing --- .github/FUNDING.yml | 2 ++ src/xmldsig/signature.rs | 16 +++++++-- tests/rsa_signature_integration.rs | 54 +++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4b199da --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +custom: + - "https://github.com/structured-world/xml-sec/blob/main/README.md#support-the-project" diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index e22d3d9..24ab316 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -18,6 +18,10 @@ use super::parse::SignatureAlgorithm; #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum SignatureVerificationError { + /// The provided PEM block could not be parsed as PEM input. + #[error("invalid PEM public key")] + InvalidKeyPem, + /// The signature method is not an RSA PKCS#1 v1.5 algorithm. #[error("unsupported signature algorithm: {uri}")] UnsupportedAlgorithm { @@ -47,8 +51,11 @@ pub fn verify_rsa_signature_pem( signed_data: &[u8], signature_value: &[u8], ) -> Result { - let (_, pem) = x509_parser::pem::parse_x509_pem(public_key_pem.as_bytes()) - .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let (rest, pem) = x509_parser::pem::parse_x509_pem(public_key_pem.as_bytes()) + .map_err(|_| SignatureVerificationError::InvalidKeyPem)?; + if !rest.iter().all(|byte| byte.is_ascii_whitespace()) { + return Err(SignatureVerificationError::InvalidKeyPem); + } if pem.label != "PUBLIC KEY" { return Err(SignatureVerificationError::InvalidKeyFormat { label: pem.label }); } @@ -68,8 +75,11 @@ pub fn verify_rsa_signature_spki( signature_value: &[u8], ) -> Result { let verification_algorithm = verification_algorithm(algorithm)?; - let (_, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der) + let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der) .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + if !rest.is_empty() { + return Err(SignatureVerificationError::InvalidKeyDer); + } let public_key = spki .parsed() .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; diff --git a/tests/rsa_signature_integration.rs b/tests/rsa_signature_integration.rs index 4c3d12a..bf0d019 100644 --- a/tests/rsa_signature_integration.rs +++ b/tests/rsa_signature_integration.rs @@ -164,7 +164,7 @@ fn wrong_spki_key_fails_verification() { } #[test] -fn malformed_pem_returns_typed_error() { +fn non_public_key_pem_returns_invalid_key_format() { let err = verify_rsa_signature_pem( SignatureAlgorithm::RsaSha256, "-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----\n", @@ -179,6 +179,37 @@ fn malformed_pem_returns_typed_error() { )); } +#[test] +fn malformed_pem_returns_typed_error() { + let err = verify_rsa_signature_pem( + SignatureAlgorithm::RsaSha256, + "-----BEGIN PUBLIC KEY-----\n%%%%\n-----END PUBLIC KEY-----\n", + b"payload", + b"signature", + ) + .expect_err("corrupt PEM should be rejected"); + + assert!(matches!(err, SignatureVerificationError::InvalidKeyPem)); +} + +#[test] +fn pem_with_trailing_garbage_returns_typed_error() { + let public_key_pem = format!( + "{}TRAILING", + read_fixture(Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")) + ); + + let err = verify_rsa_signature_pem( + SignatureAlgorithm::RsaSha256, + &public_key_pem, + b"payload", + b"signature", + ) + .expect_err("PEM with trailing garbage should be rejected"); + + assert!(matches!(err, SignatureVerificationError::InvalidKeyPem)); +} + #[test] fn malformed_spki_der_returns_typed_error() { let err = verify_rsa_signature_spki( @@ -191,3 +222,24 @@ fn malformed_spki_der_returns_typed_error() { assert!(matches!(err, SignatureVerificationError::InvalidKeyDer)); } + +#[test] +fn spki_der_with_trailing_garbage_returns_typed_error() { + let mut public_key_der = x509_parser::pem::parse_x509_pem( + read_fixture(Path::new("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")).as_bytes(), + ) + .expect("fixture PEM should parse") + .1 + .contents; + public_key_der.extend_from_slice(b"TRAILING"); + + let err = verify_rsa_signature_spki( + SignatureAlgorithm::RsaSha256, + &public_key_der, + b"payload", + b"signature", + ) + .expect_err("SPKI DER with trailing garbage should be rejected"); + + assert!(matches!(err, SignatureVerificationError::InvalidKeyDer)); +} From 1d255c2d2c9348728b4d00f6dcdf469e7f9c929d Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 19:48:54 +0200 Subject: [PATCH 4/7] docs(xmldsig): tighten review follow-ups --- README.md | 8 ++++---- src/xmldsig/signature.rs | 15 ++++++++++----- tests/rsa_signature_integration.rs | 8 +++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 35b5ce5..c131402 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Every SAML, SOAP, and WS-Security implementation depends on libxmlsec1 — a C l **Pre-release.** API is unstable. Not ready for production use. -Currently implemented: +Currently implemented (core paths): - C14N 1.0, C14N 1.1, and Exclusive C14N - XMLDSig parsing, same-document URI dereference, transform chains, and digest verification - RSA PKCS#1 v1.5 verification helpers for SHA-1 / SHA-256 / SHA-384 / SHA-512 @@ -46,9 +46,9 @@ Current MSRV: Rust 1.92. | Spec | Status | |------|--------| -| [Canonical XML 1.0](https://www.w3.org/TR/xml-c14n/) | In progress | -| [Exclusive C14N](https://www.w3.org/TR/xml-exc-c14n/) | In progress | -| [XMLDSig](https://www.w3.org/TR/xmldsig-core1/) | In progress | +| [Canonical XML 1.0](https://www.w3.org/TR/xml-c14n/) | Partially implemented | +| [Exclusive C14N](https://www.w3.org/TR/xml-exc-c14n/) | Partially implemented | +| [XMLDSig](https://www.w3.org/TR/xmldsig-core1/) | Partially implemented | | [XMLEnc](https://www.w3.org/TR/xmlenc-core1/) | Planned | ## License diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 24ab316..3d63601 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -117,10 +117,15 @@ mod tests { #[test] fn ecdsa_algorithms_are_rejected_for_rsa_verification() { - let err = verification_algorithm(SignatureAlgorithm::EcdsaP256Sha256).unwrap_err(); - assert!(matches!( - err, - SignatureVerificationError::UnsupportedAlgorithm { .. } - )); + for algorithm in [ + SignatureAlgorithm::EcdsaP256Sha256, + SignatureAlgorithm::EcdsaP384Sha384, + ] { + let err = verification_algorithm(algorithm).unwrap_err(); + assert!(matches!( + err, + SignatureVerificationError::UnsupportedAlgorithm { .. } + )); + } } } diff --git a/tests/rsa_signature_integration.rs b/tests/rsa_signature_integration.rs index bf0d019..32c0830 100644 --- a/tests/rsa_signature_integration.rs +++ b/tests/rsa_signature_integration.rs @@ -123,8 +123,10 @@ fn tampered_signed_info_fails_verification() { let (algorithm, mut canonical_signed_info, signature_value) = canonicalized_signed_info_and_signature(&xml); - let last_index = canonical_signed_info.len() - 1; - canonical_signed_info[last_index] ^= 0x01; + let last = canonical_signed_info + .last_mut() + .expect("canonical SignedInfo should not be empty"); + *last ^= 0x01; let valid = verify_rsa_signature_pem( algorithm, @@ -141,7 +143,7 @@ fn tampered_signed_info_fails_verification() { } #[test] -fn wrong_spki_key_fails_verification() { +fn wrong_public_key_pem_fails_verification() { let xml = read_fixture(Path::new( "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", )); From 6b2f0d360d4949f9409b801030c031aca41d3903 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 20:08:56 +0200 Subject: [PATCH 5/7] fix(xmldsig): prevalidate RSA key constraints --- src/xmldsig/signature.rs | 49 +++++++++++++++++++++++++++++- tests/rsa_signature_integration.rs | 13 ++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 3d63601..f440929 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -85,7 +85,8 @@ pub fn verify_rsa_signature_spki( .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; match public_key { - PublicKey::RSA(_) => { + PublicKey::RSA(rsa) => { + validate_rsa_public_key(&rsa, algorithm)?; let key = signature::UnparsedPublicKey::new( verification_algorithm, spki.subject_public_key.data, @@ -96,6 +97,52 @@ pub fn verify_rsa_signature_spki( } } +fn validate_rsa_public_key( + rsa: &x509_parser::public_key::RSAPublicKey<'_>, + algorithm: SignatureAlgorithm, +) -> Result<(), SignatureVerificationError> { + let min_modulus_bits = minimum_rsa_modulus_bits(algorithm)?; + let modulus_start = rsa + .modulus + .iter() + .position(|byte| *byte != 0) + .ok_or(SignatureVerificationError::InvalidKeyDer)?; + let modulus = &rsa.modulus[modulus_start..]; + if modulus.is_empty() { + return Err(SignatureVerificationError::InvalidKeyDer); + } + let modulus_bits = modulus + .len() + .checked_mul(8) + .ok_or(SignatureVerificationError::InvalidKeyDer)?; + if !(min_modulus_bits..=8192).contains(&modulus_bits) { + return Err(SignatureVerificationError::InvalidKeyDer); + } + + let exponent = rsa + .try_exponent() + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 { + return Err(SignatureVerificationError::InvalidKeyDer); + } + + Ok(()) +} + +fn minimum_rsa_modulus_bits( + algorithm: SignatureAlgorithm, +) -> Result { + match algorithm { + SignatureAlgorithm::RsaSha1 + | SignatureAlgorithm::RsaSha256 + | SignatureAlgorithm::RsaSha384 + | SignatureAlgorithm::RsaSha512 => Ok(2048), + _ => Err(SignatureVerificationError::UnsupportedAlgorithm { + uri: algorithm.uri().to_string(), + }), + } +} + fn verification_algorithm( algorithm: SignatureAlgorithm, ) -> Result<&'static dyn signature::VerificationAlgorithm, SignatureVerificationError> { diff --git a/tests/rsa_signature_integration.rs b/tests/rsa_signature_integration.rs index 32c0830..6466739 100644 --- a/tests/rsa_signature_integration.rs +++ b/tests/rsa_signature_integration.rs @@ -225,6 +225,19 @@ fn malformed_spki_der_returns_typed_error() { assert!(matches!(err, SignatureVerificationError::InvalidKeyDer)); } +#[test] +fn undersized_rsa_public_key_returns_typed_error() { + let err = verify_rsa_signature_pem( + SignatureAlgorithm::RsaSha256, + "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfWcQXSKc3SPo1GnqN+GKZ3gH5\nJk0nOlH6sWB2ebIOyZBL/+DsgZIHxa4kz3Ic9DFGQChtdmC13UmJU5tFzs32GCVY\nVJsTbO9uQbhE/5oiP82lGbSQdSqN5vLGK23w8jhI554bk+G8ejquNDezOuQUfa9B\ndyE0GTD54npN9GrtSQIDAQAB\n-----END PUBLIC KEY-----\n", + b"payload", + b"signature", + ) + .expect_err("1024-bit RSA key should be rejected before verification"); + + assert!(matches!(err, SignatureVerificationError::InvalidKeyDer)); +} + #[test] fn spki_der_with_trailing_garbage_returns_typed_error() { let mut public_key_der = x509_parser::pem::parse_x509_pem( From bd28e48a81a4658a88a9ed62666dc4dac8726ad7 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 21:35:24 +0200 Subject: [PATCH 6/7] fix(xmldsig): harden verifier API usage --- README.md | 2 +- src/xmldsig/signature.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c131402..5c9e184 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Pure Rust XML Security library. Drop-in replacement for libxmlsec1. > [!WARNING] > Early-stage pre-release. The API is unstable, XMLDSig/XMLEnc coverage is still incomplete, -> and this crate should not yet be used for production security decisions. +> and this crate should not yet be used in production. ## Features diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index f440929..80240e1 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -45,6 +45,7 @@ pub enum SignatureVerificationError { /// /// The PEM must contain a `PUBLIC KEY` block. Returns `Ok(false)` for signature /// mismatch and `Err` for algorithm/key preparation errors. +#[must_use = "discarding the verification result skips signature validation"] pub fn verify_rsa_signature_pem( algorithm: SignatureAlgorithm, public_key_pem: &str, @@ -68,6 +69,7 @@ pub fn verify_rsa_signature_pem( /// The input must be an X.509 `SubjectPublicKeyInfo` wrapping an RSA key. /// Returns `Ok(false)` for signature mismatch and `Err` for algorithm/key /// preparation errors. +#[must_use = "discarding the verification result skips signature validation"] pub fn verify_rsa_signature_spki( algorithm: SignatureAlgorithm, public_key_spki_der: &[u8], @@ -111,6 +113,9 @@ fn validate_rsa_public_key( if modulus.is_empty() { return Err(SignatureVerificationError::InvalidKeyDer); } + // Match ring's RSA parameter checks: modulus length is evaluated after + // rounding up to the nearest whole byte, not by exact significant-bit + // length of the highest non-zero byte. let modulus_bits = modulus .len() .checked_mul(8) From af7629e4b0d348977cbaaa3fceb950032ba60f7a Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 26 Mar 2026 22:08:44 +0200 Subject: [PATCH 7/7] docs(readme): add c14n 1.1 spec row --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5c9e184..3c1612b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Current MSRV: Rust 1.92. | Spec | Status | |------|--------| | [Canonical XML 1.0](https://www.w3.org/TR/xml-c14n/) | Partially implemented | +| [Canonical XML 1.1](https://www.w3.org/TR/xml-c14n11/) | Partially implemented | | [Exclusive C14N](https://www.w3.org/TR/xml-exc-c14n/) | Partially implemented | | [XMLDSig](https://www.w3.org/TR/xmldsig-core1/) | Partially implemented | | [XMLEnc](https://www.w3.org/TR/xmlenc-core1/) | Planned |