From 5a56ea80a0747846c0b669fe26c0c28023704576 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 30 Mar 2026 06:04:45 +0300 Subject: [PATCH 01/16] test(xmldsig): add donor full verification suite accounting - add P1-025 integration suite with deterministic pass/fail/skip tracking - validate aleksey RSA/ECDSA vectors through verify pipeline - record explicit skip reasons for deferred merlin DSA/KeyInfo/X509 vectors Closes #40 --- tests/donor_full_verification_suite.rs | 171 +++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 tests/donor_full_verification_suite.rs diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs new file mode 100644 index 0000000..517a77d --- /dev/null +++ b/tests/donor_full_verification_suite.rs @@ -0,0 +1,171 @@ +//! Donor full verification suite for ROADMAP task P1-025. +//! +//! This suite tracks pass/fail/skip accounting across donor vectors and +//! enforces that all currently supported algorithms verify end-to-end. + +use std::path::{Path, PathBuf}; + +use xml_sec::xmldsig::{DsigStatus, verify_signature_with_pem_key}; + +#[derive(Clone, Copy)] +enum Expectation { + ValidWithKey { key_path: &'static str }, + Skip { reason: &'static str }, +} + +struct VectorCase { + name: &'static str, + xml_path: &'static str, + expectation: Expectation, +} + +fn read_fixture(path: &Path) -> String { + std::fs::read_to_string(path) + .unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display())) +} + +fn project_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +fn cases() -> Vec { + vec![ + // Aleksey donor vectors: supported algorithms must pass end-to-end. + VectorCase { + name: "aleksey-rsa-sha1", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", + }, + }, + VectorCase { + name: "aleksey-rsa-sha256", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/rsa/rsa-2048-pubkey.pem", + }, + }, + VectorCase { + name: "aleksey-rsa-sha384", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", + }, + }, + VectorCase { + name: "aleksey-rsa-sha512", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", + }, + }, + VectorCase { + name: "aleksey-ecdsa-p256-sha256", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha256-ecdsa-sha256.xml", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem", + }, + }, + VectorCase { + name: "aleksey-ecdsa-p384-sha384", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", + expectation: Expectation::Skip { + reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", + }, + }, + // Merlin "basic signatures" required by P1-025. + // These are tracked explicitly as skips until P2/P4 capabilities exist. + VectorCase { + name: "merlin-enveloped-dsa", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", + expectation: Expectation::Skip { + reason: "DSA signature method is not implemented yet (planned P4-009)", + }, + }, + VectorCase { + name: "merlin-enveloping-rsa-keyvalue", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", + expectation: Expectation::Skip { + reason: "KeyValue auto-resolution is not implemented yet (planned P2-009)", + }, + }, + VectorCase { + name: "merlin-x509-crt", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-crt.xml", + expectation: Expectation::Skip { + reason: "X509 KeyInfo resolution is not implemented yet (planned P2-009)", + }, + }, + VectorCase { + name: "merlin-x509-crt-crl", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", + expectation: Expectation::Skip { + reason: "X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", + }, + }, + VectorCase { + name: "merlin-x509-is", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-is.xml", + expectation: Expectation::Skip { + reason: "X509IssuerSerial resolution is not implemented yet (planned P2-009)", + }, + }, + VectorCase { + name: "merlin-x509-ski", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-ski.xml", + expectation: Expectation::Skip { + reason: "X509SKI resolution is not implemented yet (planned P2-009)", + }, + }, + VectorCase { + name: "merlin-x509-sn", + xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-sn.xml", + expectation: Expectation::Skip { + reason: "X509SubjectName resolution is not implemented yet (planned P2-009)", + }, + }, + ] +} + +#[test] +fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { + let root = project_root(); + let mut passed = 0usize; + let mut failed = Vec::::new(); + let mut skipped = Vec::::new(); + + for case in cases() { + let xml = read_fixture(&root.join(case.xml_path)); + match case.expectation { + Expectation::ValidWithKey { key_path } => { + let key = read_fixture(&root.join(key_path)); + match verify_signature_with_pem_key(&xml, &key, false) { + Ok(result) if matches!(result.status, DsigStatus::Valid) => { + passed += 1; + } + Ok(result) => { + failed.push(format!("{}: expected Valid, got {:?}", case.name, result.status)); + } + Err(err) => { + failed.push(format!("{}: verification error {err}", case.name)); + } + } + } + Expectation::Skip { reason } => { + skipped.push(format!("{}: {}", case.name, reason)); + } + } + } + + assert!( + failed.is_empty(), + "donor full verification suite had failures:\n{}", + failed.join("\n") + ); + + // P1-025 minimum expected accounting: + // - all supported aleksey RSA/ECDSA vectors pass + // - unsupported/deferred merlin vectors are tracked as skips with explicit reasons + assert_eq!(passed, 5, "unexpected pass count"); + assert_eq!(skipped.len(), 8, "unexpected skip count"); +} From 272381b6ef8c0024032c753e7e391a35434da2ad Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 30 Mar 2026 06:13:38 +0300 Subject: [PATCH 02/16] style(tests): format donor verification suite --- tests/donor_full_verification_suite.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 517a77d..16c58ad 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -144,7 +144,10 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { passed += 1; } Ok(result) => { - failed.push(format!("{}: expected Valid, got {:?}", case.name, result.status)); + failed.push(format!( + "{}: expected Valid, got {:?}", + case.name, result.status + )); } Err(err) => { failed.push(format!("{}: verification error {err}", case.name)); From 0dd410f6244c45aa2d1d1b61863aafe0044d254a Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 30 Mar 2026 06:28:28 +0300 Subject: [PATCH 03/16] test(xmldsig): skip fixture reads for skipped vectors --- tests/donor_full_verification_suite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 16c58ad..eefcd72 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -135,9 +135,9 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { let mut skipped = Vec::::new(); for case in cases() { - let xml = read_fixture(&root.join(case.xml_path)); match case.expectation { Expectation::ValidWithKey { key_path } => { + let xml = read_fixture(&root.join(case.xml_path)); let key = read_fixture(&root.join(key_path)); match verify_signature_with_pem_key(&xml, &key, false) { Ok(result) if matches!(result.status, DsigStatus::Valid) => { From 736e3397e36fd4f1412dedc9c6e640361eb56c07 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 30 Mar 2026 06:36:14 +0300 Subject: [PATCH 04/16] test(xmldsig): use assert_eq for failure count --- tests/donor_full_verification_suite.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index eefcd72..75edbac 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -160,8 +160,9 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } - assert!( - failed.is_empty(), + assert_eq!( + failed.len(), + 0, "donor full verification suite had failures:\n{}", failed.join("\n") ); From 75f07d0dc6e31968a2dfbd6d94954f6f60d271f1 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 11:21:33 +0300 Subject: [PATCH 05/16] test(xmldsig): align donor skip case metadata --- tests/donor_full_verification_suite.rs | 45 ++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 75edbac..0d99f81 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -1,7 +1,7 @@ //! Donor full verification suite for ROADMAP task P1-025. //! //! This suite tracks pass/fail/skip accounting across donor vectors and -//! enforces that all currently supported algorithms verify end-to-end. +//! enforces that all supported donor vectors verify end-to-end. use std::path::{Path, PathBuf}; @@ -15,7 +15,7 @@ enum Expectation { struct VectorCase { name: &'static str, - xml_path: &'static str, + xml_path: Option<&'static str>, expectation: Expectation, } @@ -33,42 +33,50 @@ fn cases() -> Vec { // Aleksey donor vectors: supported algorithms must pass end-to-end. VectorCase { name: "aleksey-rsa-sha1", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml", + xml_path: Some("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml"), expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha256", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", + xml_path: Some( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", + ), expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-2048-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha384", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml", + xml_path: Some( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml", + ), expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha512", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", + xml_path: Some( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", + ), expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-ecdsa-p256-sha256", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha256-ecdsa-sha256.xml", + xml_path: Some( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha256-ecdsa-sha256.xml", + ), expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem", }, }, VectorCase { - name: "aleksey-ecdsa-p384-sha384", - xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", + name: "aleksey-ecdsa-p521-sha384", + xml_path: None, expectation: Expectation::Skip { reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", }, @@ -77,49 +85,49 @@ fn cases() -> Vec { // These are tracked explicitly as skips until P2/P4 capabilities exist. VectorCase { name: "merlin-enveloped-dsa", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", + xml_path: None, expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009)", }, }, VectorCase { name: "merlin-enveloping-rsa-keyvalue", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", + xml_path: None, expectation: Expectation::Skip { reason: "KeyValue auto-resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-crt.xml", + xml_path: None, expectation: Expectation::Skip { reason: "X509 KeyInfo resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt-crl", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", + xml_path: None, expectation: Expectation::Skip { reason: "X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", }, }, VectorCase { name: "merlin-x509-is", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-is.xml", + xml_path: None, expectation: Expectation::Skip { reason: "X509IssuerSerial resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-ski", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-ski.xml", + xml_path: None, expectation: Expectation::Skip { reason: "X509SKI resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-sn", - xml_path: "donors/xmlsec/tests/merlin-xmldsig-twenty-three/signature-x509-sn.xml", + xml_path: None, expectation: Expectation::Skip { reason: "X509SubjectName resolution is not implemented yet (planned P2-009)", }, @@ -137,7 +145,10 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { for case in cases() { match case.expectation { Expectation::ValidWithKey { key_path } => { - let xml = read_fixture(&root.join(case.xml_path)); + let xml_path = case + .xml_path + .expect("ValidWithKey vectors must provide xml_path"); + let xml = read_fixture(&root.join(xml_path)); let key = read_fixture(&root.join(key_path)); match verify_signature_with_pem_key(&xml, &key, false) { Ok(result) if matches!(result.status, DsigStatus::Valid) => { From ba269ff3a2e0cb078f9589b369c0d12073894ae6 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 12:26:09 +0300 Subject: [PATCH 06/16] test(xmldsig): validate skip vector fixture paths --- tests/donor_full_verification_suite.rs | 34 ++++++++++++++----- .../signature-enveloped-dsa.xml | 4 +++ .../signature-enveloping-rsa.xml | 4 +++ .../signature-x509-crt-crl.xml | 4 +++ .../signature-x509-crt.xml | 4 +++ .../signature-x509-is.xml | 4 +++ .../signature-x509-ski.xml | 4 +++ .../signature-x509-sn.xml | 4 +++ tests/fixtures_smoke.rs | 4 +-- 9 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml create mode 100644 tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 0d99f81..20e0b33 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -76,7 +76,9 @@ fn cases() -> Vec { }, VectorCase { name: "aleksey-ecdsa-p521-sha384", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", + ), expectation: Expectation::Skip { reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", }, @@ -85,49 +87,63 @@ fn cases() -> Vec { // These are tracked explicitly as skips until P2/P4 capabilities exist. VectorCase { name: "merlin-enveloped-dsa", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", + ), expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009)", }, }, VectorCase { name: "merlin-enveloping-rsa-keyvalue", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", + ), expectation: Expectation::Skip { reason: "KeyValue auto-resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml", + ), expectation: Expectation::Skip { reason: "X509 KeyInfo resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt-crl", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", + ), expectation: Expectation::Skip { reason: "X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", }, }, VectorCase { name: "merlin-x509-is", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml", + ), expectation: Expectation::Skip { reason: "X509IssuerSerial resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-ski", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml", + ), expectation: Expectation::Skip { reason: "X509SKI resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-sn", - xml_path: None, + xml_path: Some( + "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml", + ), expectation: Expectation::Skip { reason: "X509SubjectName resolution is not implemented yet (planned P2-009)", }, @@ -166,6 +182,8 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } Expectation::Skip { reason } => { + let xml_path = case.xml_path.expect("Skip vectors must provide xml_path"); + let _ = read_fixture(&root.join(xml_path)); skipped.push(format!("{}: {}", case.name, reason)); } } diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml new file mode 100644 index 0000000..1de7dd5 --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml new file mode 100644 index 0000000..9f691b0 --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml new file mode 100644 index 0000000..636b7c0 --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml new file mode 100644 index 0000000..8b3e9c1 --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml new file mode 100644 index 0000000..c4bf1db --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml new file mode 100644 index 0000000..1798d53 --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml new file mode 100644 index 0000000..28db3ef --- /dev/null +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml @@ -0,0 +1,4 @@ + + + Presence marker for deferred donor vector coverage. + diff --git a/tests/fixtures_smoke.rs b/tests/fixtures_smoke.rs index d336107..899a274 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, 69, - "expected 69 fixture files total (21 keys + 41 c14n + 7 donor xmldsig); \ + count, 76, + "expected 76 fixture files total (21 keys + 41 c14n + 14 donor xmldsig); \ if you added/removed files, update this count" ); } From 9b345617e59e7e6c38445eda9397c825eb207b63 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 12:51:58 +0300 Subject: [PATCH 07/16] refactor(test): make donor vector xml_path required --- tests/donor_full_verification_suite.rs | 60 +++++++------------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 20e0b33..f099090 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -15,7 +15,7 @@ enum Expectation { struct VectorCase { name: &'static str, - xml_path: Option<&'static str>, + xml_path: &'static str, expectation: Expectation, } @@ -33,52 +33,42 @@ fn cases() -> Vec { // Aleksey donor vectors: supported algorithms must pass end-to-end. VectorCase { name: "aleksey-rsa-sha1", - xml_path: Some("tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml"), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha1-rsa-sha1.xml", expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha256", - xml_path: Some( - "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", - ), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml", expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-2048-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha384", - xml_path: Some( - "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml", - ), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha384-rsa-sha384.xml", expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-rsa-sha512", - xml_path: Some( - "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", - ), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha512-rsa-sha512.xml", expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/rsa/rsa-4096-pubkey.pem", }, }, VectorCase { name: "aleksey-ecdsa-p256-sha256", - xml_path: Some( - "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha256-ecdsa-sha256.xml", - ), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha256-ecdsa-sha256.xml", expectation: Expectation::ValidWithKey { key_path: "tests/fixtures/keys/ec/ec-prime256v1-pubkey.pem", }, }, VectorCase { name: "aleksey-ecdsa-p521-sha384", - xml_path: Some( - "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", - ), + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", expectation: Expectation::Skip { reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", }, @@ -87,63 +77,49 @@ fn cases() -> Vec { // These are tracked explicitly as skips until P2/P4 capabilities exist. VectorCase { name: "merlin-enveloped-dsa", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009)", }, }, VectorCase { name: "merlin-enveloping-rsa-keyvalue", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", expectation: Expectation::Skip { reason: "KeyValue auto-resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml", expectation: Expectation::Skip { reason: "X509 KeyInfo resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt-crl", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", expectation: Expectation::Skip { reason: "X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", }, }, VectorCase { name: "merlin-x509-is", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml", expectation: Expectation::Skip { reason: "X509IssuerSerial resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-ski", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml", expectation: Expectation::Skip { reason: "X509SKI resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-sn", - xml_path: Some( - "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml", - ), + xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml", expectation: Expectation::Skip { reason: "X509SubjectName resolution is not implemented yet (planned P2-009)", }, @@ -161,10 +137,7 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { for case in cases() { match case.expectation { Expectation::ValidWithKey { key_path } => { - let xml_path = case - .xml_path - .expect("ValidWithKey vectors must provide xml_path"); - let xml = read_fixture(&root.join(xml_path)); + let xml = read_fixture(&root.join(case.xml_path)); let key = read_fixture(&root.join(key_path)); match verify_signature_with_pem_key(&xml, &key, false) { Ok(result) if matches!(result.status, DsigStatus::Valid) => { @@ -182,8 +155,7 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } Expectation::Skip { reason } => { - let xml_path = case.xml_path.expect("Skip vectors must provide xml_path"); - let _ = read_fixture(&root.join(xml_path)); + let _ = read_fixture(&root.join(case.xml_path)); skipped.push(format!("{}: {}", case.name, reason)); } } From bff2b9585dcfc843b6d272c5f38abeb1699c01d3 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 13:30:56 +0300 Subject: [PATCH 08/16] test(xmldsig): align donor vector fixture coverage --- tests/donor_full_verification_suite.rs | 9 +++- .../signature-enveloped-dsa.xml | 45 +++++++++++++++-- .../signature-enveloping-rsa.xml | 33 +++++++++++-- .../signature-x509-crt-crl.xml | 49 +++++++++++++++++-- .../signature-x509-crt.xml | 40 +++++++++++++-- .../signature-x509-is.xml | 26 ++++++++-- .../signature-x509-ski.xml | 23 +++++++-- .../signature-x509-sn.xml | 23 +++++++-- 8 files changed, 226 insertions(+), 22 deletions(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index f099090..72de4bd 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -73,6 +73,13 @@ fn cases() -> Vec { reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", }, }, + VectorCase { + name: "aleksey-rsa-sha512-x509-digest", + xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha512.xml", + expectation: Expectation::Skip { + reason: "X509Digest key resolution is not implemented yet (planned P2-009)", + }, + }, // Merlin "basic signatures" required by P1-025. // These are tracked explicitly as skips until P2/P4 capabilities exist. VectorCase { @@ -172,5 +179,5 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { // - all supported aleksey RSA/ECDSA vectors pass // - unsupported/deferred merlin vectors are tracked as skips with explicit reasons assert_eq!(passed, 5, "unexpected pass count"); - assert_eq!(skipped.len(), 8, "unexpected skip count"); + assert_eq!(skipped.len(), 9, "unexpected skip count"); } diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml index 1de7dd5..f5ff1f5 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml @@ -1,4 +1,43 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + + + + + fdy6S2NLpnT4fMdokUHSHsmpcvo= + + + + Z4pBb+o+XOKWME7CpLyXuNqyIYdXOcGvthfUf+ZDLL5immPx+3tK8Q== + + + + +

+ 3eOeAvqnEyFpW+uTSgrdj7YLjaTkpyHecKFIoLu8QZNkGTQI1ciITBH0lqfIkdCH + Si8fiUC3DTq3J9FsJef4YVtDF7JpUvHTOQqtq7Zgx6KC8Wxkz6rQCxOr7F0ApOYi + 89zLRoe4MkDGe6ux0+WtyOTQoVIGNTDDUFXrUQNbLrE= +

+ + hDLcFK0GO/Hz1arxOOvsgM/VLyU= + + + nnx7hbdWozGbtnFgnbFnopfRl7XRacpkPJRGf5P2IUgVspEUSUoN6i1fDBfBg43z + Kt7dlEaQL7b5+JTZt3MhZNPosxsgxVuT7Ts/g5k7EnpdYv0a5hw5Bw29fjbGHfgM + 8d2rhd2Ui0xHbk0D451nhLxVWulviOSPhzKKvXrbySA= + + + cfYpihpAQeepbNFS4MAbQRhdXpDi5wLrwxE5hIvoYqo1L8BQVu8fY1TFAPtoae1i + Bg/GIJyP3iLfyuBJaDvJJLP30wBH9i/s5J3656PevpOVdTfi777Fi9Gj6y/ib2Vv + +OZfJkkp4L50+p5TUhPmQLJtREsgtl+tnIOyJT++G9U= + +
+
+
+
+
diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml index 9f691b0..1580d83 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml @@ -1,4 +1,31 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 7/XTsHaBSOnJ/jXD5v0zL6VKYsk= + + + + ov3HOoPN0w71N3DdGNhN+dSzQm6NJFUB5qGKRp9Q986nVzMb8wCIVxCQu+x3vMtq + p4/R3KEcPtEJSaoR+thGq++GPIh2mZXyWJs3xHy9P4xmoTVwli7/l7s8ebDSmnbZ + 7xZU4Iy1BSMZSxGKnRG+Z/0GJIfTz8jhH6wCe3l03L4= + + + + + + q07hpxA5DGFfvJFZueFl/LI85XxQxrvqgVugL25V090A9MrlLBg5PmAsxFTe+G6a + xvWJQwYOVHj/nuiCnNLa9a7uAtPFiTtW+v5H3wlLaY3ws4atRBNOQlYkIBp38sTf + QBkk4i8PEU1GQ2M0CLIJq4/2Akfv1wxzSQ9+8oWkArc= + + + AQAB + + + + + some text + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml index 636b7c0..fe01797 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml @@ -1,4 +1,47 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 60NvZvtdTB+7UnlLp/H24p7h4bs= + + + + WF6EaX66f8CdGE6NafmzdLpb/1OVYX4kBNsqgGIqHR5JZAu4HpbVQQ== + + + + + MIIDTjCCAw6gAwIBAgIGAOz5Id5/MAkGByqGSM44BAMwdjELMAkGA1UEBhMCSUUx + DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll + cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEdMBsGA1UEAxMUQW5vdGhlciBUcmFu + c2llbnQgQ0EwHhcNMDIwNDAzMDAwMDI4WhcNMTIwNDAyMjI1OTQ2WjBmMQswCQYD + VQQGEwJJRTEPMA0GA1UECBMGRHVibGluMSQwIgYDVQQKExtCYWx0aW1vcmUgVGVj + aG5vbG9naWVzIEx0ZC4xETAPBgNVBAsTCFgvU2VjdXJlMQ0wCwYDVQQDEwRCcmVz + MIIBtjCCASsGByqGSM44BAEwggEeAoGBAISKsEonjNGgHs/uh+9YKgnwZ8Bt3T7u + yQBJW9dxpMF0cPUXz4dFbSFY4QyW8igCLswpOa+eHHEYsWvE0Nr1lcKHUPXq7u41 + JJwHNq1RAFeZiU6wa+1FL3v1/T1rAgzepV7xS4iafz4vxdHMlfwgKfoyKfq6JU1z + oVM/ahI5xWDDAhUAmEv6eIJrB4KN0fPRABPx3NHYclkCgYAlhuYZ/AzPta7+bE5C + QasmSVzc8uM/e+LN7ABlEXwQRk6QfZBcX8TbePNE8ZFng4Uft/QzAOUxALET7kKA + ek4Jeytpzc0XYCYyuGJATm4F9ZY1pAJ5yQmUmwvDYdlaZJ4ldGzO/R57Evngn/G4 + tqjjoi0sx3jq7czvDwdGHnky0AOBhAACgYBgvDFxw1U6Ou2G6P/+347Jfk2wPB1/ + atr4p3JUVLuT0ExZG6np+rKiXmcBbYKbAhMY37zVkroR9bwo+NgaJGubQ4ex5Y1X + N2Q5gIHNhNfKr8G4LPVqWGxf/lFPDYxX3ezqBJPpJCJTREX7s6Hp/VTV2SpQlySv + +GRcFKJFPlhD9aM6MDgwDgYDVR0PAQH/BAQDAgeAMBEGA1UdDgQKBAiC+5gx0MHL + hTATBgNVHSMEDDAKgAiKHFYwWjISfTAJBgcqhkjOOAQDAy8AMCwCFDTcM5i61uqq + /aveERhOJ6NG/LubAhREVDtAeNbTEywXr4O7KvEEvFLUjg== + + + MIIBJDCB5AIBATAJBgcqhkjOOAQDMHYxCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZE + dWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8G + A1UECxMIWC9TZWN1cmUxHTAbBgNVBAMTFEFub3RoZXIgVHJhbnNpZW50IENBFw0w + MjA0MDQwMjE2NThaFw0xMTA0MDIwMjE2NThaMBkwFwIGAOz5Id5/Fw0wMjA0MDQw + MjE2NThaoCMwITATBgNVHSMEDDAKgAiKHFYwWjISfTAKBgNVHRQEAwIBADAJBgcq + hkjOOAQDAzAAMC0CFCEIm38fvGzSJHms284hUs9dNB8nAhUAjEtZr0TGgc6sVRVk + krEgltdo7Jw= + + + + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml index 8b3e9c1..2048fd2 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml @@ -1,4 +1,38 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 60NvZvtdTB+7UnlLp/H24p7h4bs= + + + + GCQVmBq+1H7e9IjvKfe+egLM1Jlp3L1JCGkl9SlJ0eaDh2MKYUUnHA== + + + + + MIIDUDCCAxCgAwIBAgIGAOz5IVHTMAkGByqGSM44BAMwdjELMAkGA1UEBhMCSUUx + DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll + cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEdMBsGA1UEAxMUQW5vdGhlciBUcmFu + c2llbnQgQ0EwHhcNMDIwNDAyMjM1OTUyWhcNMTIwNDAyMjI1OTQ2WjBoMQswCQYD + VQQGEwJJRTEPMA0GA1UECBMGRHVibGluMSQwIgYDVQQKExtCYWx0aW1vcmUgVGVj + aG5vbG9naWVzIEx0ZC4xETAPBgNVBAsTCFgvU2VjdXJlMQ8wDQYDVQQDEwZNb3Jp + Z3UwggG2MIIBKwYHKoZIzjgEATCCAR4CgYEAhIqwSieM0aAez+6H71gqCfBnwG3d + Pu7JAElb13GkwXRw9RfPh0VtIVjhDJbyKAIuzCk5r54ccRixa8TQ2vWVwodQ9eru + 7jUknAc2rVEAV5mJTrBr7UUve/X9PWsCDN6lXvFLiJp/Pi/F0cyV/CAp+jIp+rol + TXOhUz9qEjnFYMMCFQCYS/p4gmsHgo3R89EAE/Hc0dhyWQKBgCWG5hn8DM+1rv5s + TkJBqyZJXNzy4z974s3sAGURfBBGTpB9kFxfxNt480TxkWeDhR+39DMA5TEAsRPu + QoB6Tgl7K2nNzRdgJjK4YkBObgX1ljWkAnnJCZSbC8Nh2VpkniV0bM79HnsS+eCf + 8bi2qOOiLSzHeOrtzO8PB0YeeTLQA4GEAAKBgH1NBJ9Az5TwY4tDE0dPYVHHABt+ + yLspnT3k9G6YWUMFhZ/+3RuqEPjnKrPfUoXTTJGIACgPU3/PkqwrPVD0JMdpOcnZ + LHiJ/P7QRQeMwDRoBrs7genB1bDd4pSJrEUcjrkA5uRrIj2Z5fL+UuLiLGPO2rM7 + BNQRIq3QFPdX++NuozowODAOBgNVHQ8BAf8EBAMCB4AwEQYDVR0OBAoECIK7Ljjh + +EsfMBMGA1UdIwQMMAqACIocVjBaMhJ9MAkGByqGSM44BAMDLwAwLAIUEJJCOHw8 + ppxoRyz3s+Vmb4NKIfMCFDgJoZn9zh/3WoYNBURODwLvyBOy + + + + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml index c4bf1db..b7a01f8 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml @@ -1,4 +1,24 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 60NvZvtdTB+7UnlLp/H24p7h4bs= + + + + bmKMy/w1DO9dHA6E7Dt0B8IFkYAj1/UD3TqcdqIcfkMT7evE8+NBgg== + + + + + + CN=Another Transient CA,OU=X/Secure,O=Baltimore Technologies Ltd.,ST=Dublin,C=IE + + 1017792003066 + + + + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml index 1798d53..c71bfce 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml @@ -1,4 +1,21 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 60NvZvtdTB+7UnlLp/H24p7h4bs= + + + + F9nEU1Us48iKTml8n7E4wt7HtFJ5gaLIgox0J9WbujGndW0oQJbeGg== + + + + + hf10xKfSnIg= + + + + diff --git a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml index 28db3ef..d5b0808 100644 --- a/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml +++ b/tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml @@ -1,4 +1,21 @@ - - Presence marker for deferred donor vector coverage. - + + + + + + + 60NvZvtdTB+7UnlLp/H24p7h4bs= + + + + MUOjiqG0dbjvR6+qYYPL85nKSt2FeZGQBQkYudv48KyJhJLG1Bp+bA== + + + + + CN=Badb,OU=X/Secure,O=Baltimore Technologies Ltd.,ST=Dublin,C=IE + + + + From 55c23ffedf94d7e3db5142affad4f50f79c29bbb Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 15:39:12 +0300 Subject: [PATCH 09/16] refactor(xmldsig): migrate ring to rustcrypto - replace ring digests/signature verification with RustCrypto crates - add ECDSA P-521 (ecdsa-sha384) verification path and donor fixture coverage - update integration tests and fixture inventory to match new crypto backend --- Cargo.toml | 9 +- src/xmldsig/digest.rs | 32 +- src/xmldsig/signature.rs | 288 +++++++++++++----- tests/c14n_golden.rs | 5 +- tests/donor_full_verification_suite.rs | 19 +- tests/ecdsa_signature_integration.rs | 31 +- .../fixtures/keys/ec/ec-prime521v1-pubkey.pem | 6 + tests/fixtures_smoke.rs | 4 +- 8 files changed, 271 insertions(+), 123 deletions(-) create mode 100644 tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem diff --git a/Cargo.toml b/Cargo.toml index 951a188..5dd1c22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,14 @@ readme = "README.md" roxmltree = { version = "0.21", features = ["positions"] } # Crypto -ring = "0.17" +rsa = "0.9" +sha1 = { version = "0.10", features = ["oid"] } +sha2 = { version = "0.10", features = ["oid"] } +p256 = { version = "0.13", features = ["ecdsa"] } +p384 = { version = "0.13", features = ["ecdsa"] } +p521 = { version = "0.13", features = ["ecdsa"] } +signature = "2" +subtle = "2" # X.509 certificates x509-parser = "0.18" diff --git a/src/xmldsig/digest.rs b/src/xmldsig/digest.rs index 9b2d505..a602142 100644 --- a/src/xmldsig/digest.rs +++ b/src/xmldsig/digest.rs @@ -3,9 +3,11 @@ //! Implements [XMLDSig §6.1](https://www.w3.org/TR/xmldsig-core1/#sec-DigestMethod): //! compute message digests over transform output bytes using SHA-family algorithms. //! -//! All digest computation uses `ring::digest` (no custom implementations). +//! All digest computation uses RustCrypto hash implementations. -use ring::digest; +use sha1::Sha1; +use sha2::{Digest, Sha256, Sha384, Sha512}; +use subtle::ConstantTimeEq; /// Digest algorithms supported by XMLDSig. /// @@ -73,24 +75,18 @@ impl DigestAlgorithm { Self::Sha512 => 64, } } - - /// Map to the corresponding `ring::digest` algorithm. - fn ring_algorithm(self) -> &'static digest::Algorithm { - match self { - Self::Sha1 => &digest::SHA1_FOR_LEGACY_USE_ONLY, - Self::Sha256 => &digest::SHA256, - Self::Sha384 => &digest::SHA384, - Self::Sha512 => &digest::SHA512, - } - } } /// Compute the digest of `data` using the specified algorithm. /// /// Returns the raw digest bytes (not base64-encoded). pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec { - let result = digest::digest(algorithm.ring_algorithm(), data); - result.as_ref().to_vec() + match algorithm { + DigestAlgorithm::Sha1 => Sha1::digest(data).to_vec(), + DigestAlgorithm::Sha256 => Sha256::digest(data).to_vec(), + DigestAlgorithm::Sha384 => Sha384::digest(data).to_vec(), + DigestAlgorithm::Sha512 => Sha512::digest(data).to_vec(), + } } /// Constant-time comparison of two byte slices. @@ -100,13 +96,9 @@ pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec { /// where they differ — preventing timing side-channel attacks on digest /// comparison. /// -/// Uses `ring::constant_time::verify_slices_are_equal` internally. +/// Uses `subtle` constant-time equality. pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { - #[expect( - deprecated, - reason = "legacy ring constant-time helper is still used here" - )] - ring::constant_time::verify_slices_are_equal(a, b).is_ok() + a.ct_eq(b).into() } #[cfg(test)] diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 3e6d13e..102b277 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -9,7 +9,15 @@ //! - RSA: ASN.1 `RSAPublicKey` //! - ECDSA: uncompressed SEC1 EC point bytes from the SPKI bit string -use ring::signature; +use p256::ecdsa::{Signature as P256Signature, VerifyingKey as P256VerifyingKey}; +use p384::ecdsa::{Signature as P384Signature, VerifyingKey as P384VerifyingKey}; +use p521::ecdsa::{Signature as P521Signature, VerifyingKey as P521VerifyingKey}; +use rsa::pkcs1v15::{Signature as RsaPkcs1v15Signature, VerifyingKey as RsaVerifyingKey}; +use rsa::pkcs8::DecodePublicKey; +use sha1::Sha1; +use sha2::{Digest, Sha256, Sha384, Sha512}; +use signature::hazmat::PrehashVerifier; +use signature::{DigestVerifier, Verifier}; use x509_parser::prelude::FromDer; use x509_parser::public_key::{ECPoint, PublicKey}; use x509_parser::x509::SubjectPublicKeyInfo; @@ -125,7 +133,7 @@ pub fn verify_rsa_signature_spki( signed_data: &[u8], signature_value: &[u8], ) -> Result { - let verification_algorithm = verification_algorithm(algorithm)?; + minimum_rsa_modulus_bits(algorithm)?; let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der) .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; if !rest.is_empty() { @@ -138,11 +146,37 @@ pub fn verify_rsa_signature_spki( match public_key { PublicKey::RSA(rsa) => { validate_rsa_public_key(&rsa, algorithm)?; - let key = signature::UnparsedPublicKey::new( - verification_algorithm, - spki.subject_public_key.data, - ); - Ok(key.verify(signed_data, signature_value).is_ok()) + let key = rsa::RsaPublicKey::from_public_key_der(public_key_spki_der) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let Ok(signature) = RsaPkcs1v15Signature::try_from(signature_value) else { + return Ok(false); + }; + + let verified = match algorithm { + SignatureAlgorithm::RsaSha1 => { + let key = RsaVerifyingKey::::new(key); + key.verify(signed_data, &signature).is_ok() + } + SignatureAlgorithm::RsaSha256 => { + let key = RsaVerifyingKey::::new(key); + key.verify(signed_data, &signature).is_ok() + } + SignatureAlgorithm::RsaSha384 => { + let key = RsaVerifyingKey::::new(key); + key.verify(signed_data, &signature).is_ok() + } + SignatureAlgorithm::RsaSha512 => { + let key = RsaVerifyingKey::::new(key); + key.verify(signed_data, &signature).is_ok() + } + _ => { + return Err(SignatureVerificationError::UnsupportedAlgorithm { + uri: algorithm.uri().to_string(), + }); + } + }; + + Ok(verified) } _ => Err(SignatureVerificationError::InvalidKeyDer), } @@ -183,26 +217,28 @@ pub fn verify_ecdsa_signature_spki( match public_key { PublicKey::EC(ec) => { validate_ec_public_key_encoding(&ec, &spki.subject_public_key.data)?; - let (fixed_algorithm, asn1_algorithm, signature_encoding) = - ecdsa_verification_algorithms(&spki, &ec, algorithm, signature_value)?; - let public_key = &spki.subject_public_key.data; - let fixed_key = signature::UnparsedPublicKey::new(fixed_algorithm, public_key); - let asn1_key = signature::UnparsedPublicKey::new(asn1_algorithm, public_key); - - match signature_encoding { - EcdsaSignatureEncoding::XmlDsigFixed => { - Ok(fixed_key.verify(signed_data, signature_value).is_ok()) - } - EcdsaSignatureEncoding::Asn1Der => { - Ok(asn1_key.verify(signed_data, signature_value).is_ok()) - } - EcdsaSignatureEncoding::Ambiguous => { - if asn1_key.verify(signed_data, signature_value).is_ok() { - return Ok(true); - } - - Ok(fixed_key.verify(signed_data, signature_value).is_ok()) - } + let (curve, component_len) = ecdsa_curve_and_component_len(&spki, &ec, algorithm)?; + let signature_encoding = + classify_ecdsa_signature_encoding(signature_value, component_len)?; + match curve { + EcCurve::P256 => verify_ecdsa_p256_sha256( + &spki.subject_public_key.data, + signed_data, + signature_value, + signature_encoding, + ), + EcCurve::P384 => verify_ecdsa_p384_sha384( + &spki.subject_public_key.data, + signed_data, + signature_value, + signature_encoding, + ), + EcCurve::P521 => verify_ecdsa_p521_sha384( + &spki.subject_public_key.data, + signed_data, + signature_value, + signature_encoding, + ), } } _ => Err(SignatureVerificationError::KeyAlgorithmMismatch { @@ -260,33 +296,18 @@ fn minimum_rsa_modulus_bits( } } -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(), - }), - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum EcCurve { + P256, + P384, + P521, } -fn ecdsa_verification_algorithms( +fn ecdsa_curve_and_component_len( spki: &SubjectPublicKeyInfo<'_>, ec: &ECPoint<'_>, algorithm: SignatureAlgorithm, - signature_value: &[u8], -) -> Result< - ( - &'static dyn signature::VerificationAlgorithm, - &'static dyn signature::VerificationAlgorithm, - EcdsaSignatureEncoding, - ), - SignatureVerificationError, -> { +) -> Result<(EcCurve, usize), SignatureVerificationError> { let curve_oid = spki .algorithm .parameters @@ -296,45 +317,162 @@ fn ecdsa_verification_algorithms( let point_len = ec.key_size(); let curve_oid = curve_oid.to_id_string(); - let (fixed_algorithm, asn1_algorithm, component_len) = match algorithm { + match algorithm { SignatureAlgorithm::EcdsaP256Sha256 => { if curve_oid == "1.2.840.10045.3.1.7" && point_len == 256 { - ( - &signature::ECDSA_P256_SHA256_FIXED, - &signature::ECDSA_P256_SHA256_ASN1, - 32, - ) + Ok((EcCurve::P256, 32)) } else { - return Err(SignatureVerificationError::KeyAlgorithmMismatch { + Err(SignatureVerificationError::KeyAlgorithmMismatch { uri: algorithm.uri().to_string(), - }); + }) } } SignatureAlgorithm::EcdsaP384Sha384 => { if curve_oid == "1.3.132.0.34" && point_len == 384 { - ( - &signature::ECDSA_P384_SHA384_FIXED, - &signature::ECDSA_P384_SHA384_ASN1, - 48, - ) + Ok((EcCurve::P384, 48)) + } else if curve_oid == "1.3.132.0.35" { + Ok((EcCurve::P521, 66)) } else { - return Err(SignatureVerificationError::KeyAlgorithmMismatch { + Err(SignatureVerificationError::KeyAlgorithmMismatch { uri: algorithm.uri().to_string(), - }); + }) + } + } + _ => Err(SignatureVerificationError::UnsupportedAlgorithm { + uri: algorithm.uri().to_string(), + }), + } +} + +fn verify_ecdsa_p256_sha256( + public_key: &[u8], + signed_data: &[u8], + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, +) -> Result { + let key = P256VerifyingKey::from_sec1_bytes(public_key) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let mut digest = Sha256::new(); + digest.update(signed_data); + verify_p256_signature(&key, signature_value, signature_encoding, digest) +} + +fn verify_ecdsa_p384_sha384( + public_key: &[u8], + signed_data: &[u8], + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, +) -> Result { + let key = P384VerifyingKey::from_sec1_bytes(public_key) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let mut digest = Sha384::new(); + digest.update(signed_data); + verify_p384_signature(&key, signature_value, signature_encoding, digest) +} + +fn verify_ecdsa_p521_sha384( + public_key: &[u8], + signed_data: &[u8], + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, +) -> Result { + let key = P521VerifyingKey::from_sec1_bytes(public_key) + .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; + let prehash = Sha384::digest(signed_data); + verify_p521_signature(&key, signature_value, signature_encoding, &prehash) +} + +fn verify_p256_signature( + key: &P256VerifyingKey, + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, + digest: Sha256, +) -> Result { + match signature_encoding { + EcdsaSignatureEncoding::XmlDsigFixed => { + let signature = P256Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) + } + EcdsaSignatureEncoding::Asn1Der => { + let signature = P256Signature::from_der(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) + } + EcdsaSignatureEncoding::Ambiguous => { + if let Ok(signature) = P256Signature::from_der(signature_value) + && key.verify_digest(digest.clone(), &signature).is_ok() + { + return Ok(true); } + + let signature = P256Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) } - _ => { - return Err(SignatureVerificationError::UnsupportedAlgorithm { - uri: algorithm.uri().to_string(), - }); + } +} + +fn verify_p384_signature( + key: &P384VerifyingKey, + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, + digest: Sha384, +) -> Result { + match signature_encoding { + EcdsaSignatureEncoding::XmlDsigFixed => { + let signature = P384Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) } - }; + EcdsaSignatureEncoding::Asn1Der => { + let signature = P384Signature::from_der(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) + } + EcdsaSignatureEncoding::Ambiguous => { + if let Ok(signature) = P384Signature::from_der(signature_value) + && key.verify_digest(digest.clone(), &signature).is_ok() + { + return Ok(true); + } + + let signature = P384Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_digest(digest, &signature).is_ok()) + } + } +} - Ok(( - fixed_algorithm, - asn1_algorithm, - classify_ecdsa_signature_encoding(signature_value, component_len)?, - )) +fn verify_p521_signature( + key: &P521VerifyingKey, + signature_value: &[u8], + signature_encoding: EcdsaSignatureEncoding, + prehash: &[u8], +) -> Result { + match signature_encoding { + EcdsaSignatureEncoding::XmlDsigFixed => { + let signature = P521Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_prehash(prehash, &signature).is_ok()) + } + EcdsaSignatureEncoding::Asn1Der => { + let signature = P521Signature::from_der(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_prehash(prehash, &signature).is_ok()) + } + EcdsaSignatureEncoding::Ambiguous => { + if let Ok(signature) = P521Signature::from_der(signature_value) + && key.verify_prehash(prehash, &signature).is_ok() + { + return Ok(true); + } + + let signature = P521Signature::from_slice(signature_value) + .map_err(|_| SignatureVerificationError::InvalidSignatureFormat)?; + Ok(key.verify_prehash(prehash, &signature).is_ok()) + } + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -505,7 +643,7 @@ mod tests { SignatureAlgorithm::EcdsaP256Sha256, SignatureAlgorithm::EcdsaP384Sha384, ] { - let err = verification_algorithm(algorithm).unwrap_err(); + let err = minimum_rsa_modulus_bits(algorithm).unwrap_err(); assert!(matches!( err, SignatureVerificationError::UnsupportedAlgorithm { .. } diff --git a/tests/c14n_golden.rs b/tests/c14n_golden.rs index f8b0d77..aac56a4 100644 --- a/tests/c14n_golden.rs +++ b/tests/c14n_golden.rs @@ -20,7 +20,7 @@ use std::collections::HashSet; use std::fs; -use ring::digest; +use sha1::{Digest, Sha1}; use xml_sec::c14n::{C14nAlgorithm, C14nMode, canonicalize, canonicalize_xml}; // ─── Helpers ──────────────────────────────────────────────────────────────── @@ -36,8 +36,7 @@ fn fixture_bytes(path: &str) -> Vec { } fn sha1_base64(data: &[u8]) -> String { - let hash = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, data); - base64_encode(hash.as_ref()) + base64_encode(&Sha1::digest(data)) } fn base64_encode(data: &[u8]) -> String { diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 72de4bd..ab62231 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -69,8 +69,8 @@ fn cases() -> Vec { VectorCase { name: "aleksey-ecdsa-p521-sha384", xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-sha384-ecdsa-sha384.xml", - expectation: Expectation::Skip { - reason: "vector uses KeyName ec-prime521v1 (P-521), which is not supported yet", + expectation: Expectation::ValidWithKey { + key_path: "tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem", }, }, VectorCase { @@ -175,9 +175,20 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { failed.join("\n") ); + let expected_skipped = vec![ + "aleksey-rsa-sha512-x509-digest: X509Digest key resolution is not implemented yet (planned P2-009)", + "merlin-enveloped-dsa: DSA signature method is not implemented yet (planned P4-009)", + "merlin-enveloping-rsa-keyvalue: KeyValue auto-resolution is not implemented yet (planned P2-009)", + "merlin-x509-crt: X509 KeyInfo resolution is not implemented yet (planned P2-009)", + "merlin-x509-crt-crl: X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", + "merlin-x509-is: X509IssuerSerial resolution is not implemented yet (planned P2-009)", + "merlin-x509-ski: X509SKI resolution is not implemented yet (planned P2-009)", + "merlin-x509-sn: X509SubjectName resolution is not implemented yet (planned P2-009)", + ]; + // P1-025 minimum expected accounting: // - all supported aleksey RSA/ECDSA vectors pass // - unsupported/deferred merlin vectors are tracked as skips with explicit reasons - assert_eq!(passed, 5, "unexpected pass count"); - assert_eq!(skipped.len(), 9, "unexpected skip count"); + assert_eq!(passed, 6, "unexpected pass count"); + assert_eq!(skipped, expected_skipped, "unexpected skip inventory"); } diff --git a/tests/ecdsa_signature_integration.rs b/tests/ecdsa_signature_integration.rs index 58aec8b..b39fb5a 100644 --- a/tests/ecdsa_signature_integration.rs +++ b/tests/ecdsa_signature_integration.rs @@ -7,10 +7,9 @@ use std::path::Path; use base64::Engine; -use ring::rand::SystemRandom; -use ring::signature::{ - ECDSA_P384_SHA384_ASN1_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair, -}; +use p384::ecdsa::signature::Signer; +use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey}; +use p384::pkcs8::DecodePrivateKey; use xml_sec::c14n::canonicalize; use xml_sec::xmldsig::parse::{SignatureAlgorithm, find_signature_node, parse_signed_info}; use xml_sec::xmldsig::{ @@ -110,18 +109,16 @@ fn local_p384_signature_matches() { .expect("fixture PEM should parse") .1 .contents; - let rng = SystemRandom::new(); - let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &pkcs8_der, &rng) - .expect("fixture PKCS#8 should parse"); - let signature = key_pair - .sign(&rng, &canonical_signed_info) - .expect("fixture P-384 key should sign"); + let signing_key = + P384SigningKey::from_pkcs8_der(&pkcs8_der).expect("fixture PKCS#8 should parse"); + let signature: P384Signature = signing_key.sign(&canonical_signed_info); + let signature_bytes = signature.to_bytes(); let valid = verify_ecdsa_signature_pem( SignatureAlgorithm::EcdsaP384Sha384, &public_key_pem, &canonical_signed_info, - signature.as_ref(), + signature_bytes.as_ref(), ) .expect("P-384 verification should not error on valid fixtures"); @@ -147,18 +144,16 @@ fn local_p384_der_signature_matches() { .expect("fixture PEM should parse") .1 .contents; - let rng = SystemRandom::new(); - let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_ASN1_SIGNING, &pkcs8_der, &rng) - .expect("fixture PKCS#8 should parse"); - let signature = key_pair - .sign(&rng, &canonical_signed_info) - .expect("fixture P-384 key should sign"); + let signing_key = + P384SigningKey::from_pkcs8_der(&pkcs8_der).expect("fixture PKCS#8 should parse"); + let signature: P384Signature = signing_key.sign(&canonical_signed_info); + let signature_der = signature.to_der(); let valid = verify_ecdsa_signature_pem( SignatureAlgorithm::EcdsaP384Sha384, &public_key_pem, &canonical_signed_info, - signature.as_ref(), + signature_der.as_bytes(), ) .expect("P-384 DER verification should not error on valid fixtures"); diff --git a/tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem b/tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem new file mode 100644 index 0000000..0168b66 --- /dev/null +++ b/tests/fixtures/keys/ec/ec-prime521v1-pubkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBr/AHhJsVuacixRwxwUVB7/ERZtvS +Jx8Xqg8T9lwp2qag97WQVSbYEc6pft5WO46vP0xHfDlu8Dy9SMCRNvJP6kIBRmNK +hgj0Hgsv9Ac/rYVlm/63cad5L0sslWiv3m6wT3crJQePmZRBGZ7d8hUB73vm/BbS +ATM3JZVDq0LDcQGtpJs= +-----END PUBLIC KEY----- diff --git a/tests/fixtures_smoke.rs b/tests/fixtures_smoke.rs index 899a274..0549b45 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, 76, - "expected 76 fixture files total (21 keys + 41 c14n + 14 donor xmldsig); \ + count, 77, + "expected 77 fixture files total (22 keys + 41 c14n + 14 donor xmldsig); \ if you added/removed files, update this count" ); } From 2a9e4df6fedd2eea8224bad9ce4a50f3eb88ed19 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 16:13:38 +0300 Subject: [PATCH 10/16] fix(xmldsig): tighten p521 checks and docs - enforce P-521 point_len validation with byte-aligned interop handling - refresh signature/docs to reflect RustCrypto backend and ecdsa-sha384 curve mapping - apply clippy suggestions in donor skip/failure assertions --- src/xmldsig/parse.rs | 6 +++++- src/xmldsig/signature.rs | 15 ++++++++++----- tests/donor_full_verification_suite.rs | 7 +++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/xmldsig/parse.rs b/src/xmldsig/parse.rs index 4d5266a..0c82a0d 100644 --- a/src/xmldsig/parse.rs +++ b/src/xmldsig/parse.rs @@ -38,7 +38,11 @@ pub enum SignatureAlgorithm { RsaSha512, /// ECDSA P-256 with SHA-256. EcdsaP256Sha256, - /// ECDSA P-384 with SHA-384. + /// XMLDSig `ecdsa-sha384` URI. + /// + /// The variant name is historical; verification may allow multiple curves + /// for this URI in interop mode (for example P-384 and specific donor + /// P-521 fixtures). EcdsaP384Sha384, } diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 102b277..026ec85 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -4,10 +4,11 @@ //! P1-020 (ECDSA P-256/P-384) verification. //! //! Input public keys are accepted in SubjectPublicKeyInfo (SPKI) form because -//! that is how the vendored PEM fixtures are stored. `ring` expects the inner -//! SPKI payload for both algorithm families: -//! - RSA: ASN.1 `RSAPublicKey` -//! - ECDSA: uncompressed SEC1 EC point bytes from the SPKI bit string +//! that is how the vendored PEM fixtures are stored. +//! - RSA keys are parsed from full SPKI DER (`PUBLIC KEY`) and verified via +//! RustCrypto `rsa::pkcs1v15`. +//! - ECDSA keys are validated as uncompressed SEC1 points from the SPKI bit +//! string and verified with RustCrypto curve crates (`p256`/`p384`/`p521`). use p256::ecdsa::{Signature as P256Signature, VerifyingKey as P256VerifyingKey}; use p384::ecdsa::{Signature as P384Signature, VerifyingKey as P384VerifyingKey}; @@ -330,7 +331,11 @@ fn ecdsa_curve_and_component_len( SignatureAlgorithm::EcdsaP384Sha384 => { if curve_oid == "1.3.132.0.34" && point_len == 384 { Ok((EcCurve::P384, 48)) - } else if curve_oid == "1.3.132.0.35" { + // XMLDSig `ecdsa-sha384` identifies the digest/signature method URI, + // not a single curve. For interop we accept secp521r1 donor vectors; + // x509-parser reports ECPoint::key_size() as byte-aligned bits (528) + // for P-521 uncompressed points, so allow both exact and aligned size. + } else if curve_oid == "1.3.132.0.35" && matches!(point_len, 521 | 528) { Ok((EcCurve::P521, 66)) } else { Err(SignatureVerificationError::KeyAlgorithmMismatch { diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index ab62231..6ed007f 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -162,15 +162,14 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } Expectation::Skip { reason } => { - let _ = read_fixture(&root.join(case.xml_path)); + read_fixture(&root.join(case.xml_path)); skipped.push(format!("{}: {}", case.name, reason)); } } } - assert_eq!( - failed.len(), - 0, + assert!( + failed.is_empty(), "donor full verification suite had failures:\n{}", failed.join("\n") ); From 394e4dcfb0660b81886bf627c227cbc957bec504 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 17:23:50 +0300 Subject: [PATCH 11/16] fix(xmldsig): align review feedback on docs and mismatch errors --- src/xmldsig/parse.rs | 7 ++++--- src/xmldsig/signature.rs | 4 +++- tests/donor_full_verification_suite.rs | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/xmldsig/parse.rs b/src/xmldsig/parse.rs index 0c82a0d..1d6c19e 100644 --- a/src/xmldsig/parse.rs +++ b/src/xmldsig/parse.rs @@ -40,9 +40,10 @@ pub enum SignatureAlgorithm { EcdsaP256Sha256, /// XMLDSig `ecdsa-sha384` URI. /// - /// The variant name is historical; verification may allow multiple curves - /// for this URI in interop mode (for example P-384 and specific donor - /// P-521 fixtures). + /// The variant name is historical. + /// + /// Verification currently accepts this XMLDSig URI for P-384 and for the + /// donor P-521 interop case. EcdsaP384Sha384, } diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 026ec85..77ffc89 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -179,7 +179,9 @@ pub fn verify_rsa_signature_spki( Ok(verified) } - _ => Err(SignatureVerificationError::InvalidKeyDer), + _ => Err(SignatureVerificationError::KeyAlgorithmMismatch { + uri: algorithm.uri().to_string(), + }), } } diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 6ed007f..0885da3 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -168,8 +168,9 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } - assert!( - failed.is_empty(), + assert_eq!( + failed.len(), + 0, "donor full verification suite had failures:\n{}", failed.join("\n") ); From f82bf209f0d8f3bce9193b0ab5b09491f7e0389d Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 17:46:38 +0300 Subject: [PATCH 12/16] test(xmldsig): document intentional fixture discard pattern --- tests/donor_full_verification_suite.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 0885da3..98703c2 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -162,6 +162,7 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } Expectation::Skip { reason } => { + // Keep this as a plain call: `let _ = ...` triggers clippy::let_underscore_drop. read_fixture(&root.join(case.xml_path)); skipped.push(format!("{}: {}", case.name, reason)); } From 74fd6f84ac55792696767c19bdab0945304432ef Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 18:16:27 +0300 Subject: [PATCH 13/16] fix(xmldsig): remove redundant rsa modulus check --- src/xmldsig/signature.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 77ffc89..662eeac 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -134,7 +134,6 @@ pub fn verify_rsa_signature_spki( signed_data: &[u8], signature_value: &[u8], ) -> Result { - minimum_rsa_modulus_bits(algorithm)?; let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_spki_der) .map_err(|_| SignatureVerificationError::InvalidKeyDer)?; if !rest.is_empty() { From 9117630e97a1456e5c0874b515e8814d12a2da6b Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 23:12:42 +0300 Subject: [PATCH 14/16] test(xmldsig): tighten skipped vector guardrails --- src/xmldsig/signature.rs | 3 ++- tests/donor_full_verification_suite.rs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/xmldsig/signature.rs b/src/xmldsig/signature.rs index 662eeac..af95091 100644 --- a/src/xmldsig/signature.rs +++ b/src/xmldsig/signature.rs @@ -1,7 +1,8 @@ //! Signature verification helpers for XMLDSig. //! //! This module currently covers roadmap task P1-019 (RSA PKCS#1 v1.5) and -//! P1-020 (ECDSA P-256/P-384) verification. +//! P1-020 (ECDSA P-256/P-384) verification, plus donor P-521 interop under +//! the XMLDSig `ecdsa-sha384` URI. //! //! Input public keys are accepted in SubjectPublicKeyInfo (SPKI) form because //! that is how the vendored PEM fixtures are stored. diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 98703c2..5c1f707 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -137,6 +137,7 @@ fn cases() -> Vec { #[test] fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { let root = project_root(); + let skip_probe_key = read_fixture(&root.join("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")); let mut passed = 0usize; let mut failed = Vec::::new(); let mut skipped = Vec::::new(); @@ -162,8 +163,18 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } Expectation::Skip { reason } => { - // Keep this as a plain call: `let _ = ...` triggers clippy::let_underscore_drop. - read_fixture(&root.join(case.xml_path)); + let xml = read_fixture(&root.join(case.xml_path)); + roxmltree::Document::parse(&xml) + .unwrap_or_else(|err| panic!("{}: fixture XML must parse: {err}", case.name)); + if let Ok(result) = verify_signature_with_pem_key(&xml, &skip_probe_key, false) + && matches!(result.status, DsigStatus::Valid) + { + failed.push(format!( + "{}: expected non-Valid for skipped vector, got {:?}", + case.name, result.status + )); + continue; + } skipped.push(format!("{}: {}", case.name, reason)); } } From 68fa06796ef5fc531b57541cc9af5e603b1bbd8a Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Fri, 3 Apr 2026 23:40:08 +0300 Subject: [PATCH 15/16] test(xmldsig): clarify x509 skip blocker reasons --- tests/donor_full_verification_suite.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index 5c1f707..d27d163 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -100,35 +100,35 @@ fn cases() -> Vec { name: "merlin-x509-crt", xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml", expectation: Expectation::Skip { - reason: "X509 KeyInfo resolution is not implemented yet (planned P2-009)", + reason: "DSA signature method is not implemented yet (planned P4-009); X509 KeyInfo resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-crt-crl", xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", expectation: Expectation::Skip { - reason: "X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", + reason: "DSA signature method is not implemented yet (planned P4-009); X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", }, }, VectorCase { name: "merlin-x509-is", xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml", expectation: Expectation::Skip { - reason: "X509IssuerSerial resolution is not implemented yet (planned P2-009)", + reason: "DSA signature method is not implemented yet (planned P4-009); X509IssuerSerial resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-ski", xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml", expectation: Expectation::Skip { - reason: "X509SKI resolution is not implemented yet (planned P2-009)", + reason: "DSA signature method is not implemented yet (planned P4-009); X509SKI resolution is not implemented yet (planned P2-009)", }, }, VectorCase { name: "merlin-x509-sn", xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml", expectation: Expectation::Skip { - reason: "X509SubjectName resolution is not implemented yet (planned P2-009)", + reason: "DSA signature method is not implemented yet (planned P4-009); X509SubjectName resolution is not implemented yet (planned P2-009)", }, }, ] @@ -191,11 +191,11 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { "aleksey-rsa-sha512-x509-digest: X509Digest key resolution is not implemented yet (planned P2-009)", "merlin-enveloped-dsa: DSA signature method is not implemented yet (planned P4-009)", "merlin-enveloping-rsa-keyvalue: KeyValue auto-resolution is not implemented yet (planned P2-009)", - "merlin-x509-crt: X509 KeyInfo resolution is not implemented yet (planned P2-009)", - "merlin-x509-crt-crl: X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", - "merlin-x509-is: X509IssuerSerial resolution is not implemented yet (planned P2-009)", - "merlin-x509-ski: X509SKI resolution is not implemented yet (planned P2-009)", - "merlin-x509-sn: X509SubjectName resolution is not implemented yet (planned P2-009)", + "merlin-x509-crt: DSA signature method is not implemented yet (planned P4-009); X509 KeyInfo resolution is not implemented yet (planned P2-009)", + "merlin-x509-crt-crl: DSA signature method is not implemented yet (planned P4-009); X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", + "merlin-x509-is: DSA signature method is not implemented yet (planned P4-009); X509IssuerSerial resolution is not implemented yet (planned P2-009)", + "merlin-x509-ski: DSA signature method is not implemented yet (planned P4-009); X509SKI resolution is not implemented yet (planned P2-009)", + "merlin-x509-sn: DSA signature method is not implemented yet (planned P4-009); X509SubjectName resolution is not implemented yet (planned P2-009)", ]; // P1-025 minimum expected accounting: From 1b6631eacec4c967e1d5f8b42824aea0826cc11d Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 4 Apr 2026 09:13:50 +0300 Subject: [PATCH 16/16] test(xmldsig): assert concrete skip outcomes --- Cargo.toml | 33 ++++++++----- tests/donor_full_verification_suite.rs | 68 +++++++++++++++++++++----- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5dd1c22..40269f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,18 +17,18 @@ readme = "README.md" roxmltree = { version = "0.21", features = ["positions"] } # Crypto -rsa = "0.9" -sha1 = { version = "0.10", features = ["oid"] } -sha2 = { version = "0.10", features = ["oid"] } -p256 = { version = "0.13", features = ["ecdsa"] } -p384 = { version = "0.13", features = ["ecdsa"] } -p521 = { version = "0.13", features = ["ecdsa"] } -signature = "2" -subtle = "2" +rsa = { version = "0.9", optional = true } +sha1 = { version = "0.10", features = ["oid"], optional = true } +sha2 = { version = "0.10", features = ["oid"], optional = true } +p256 = { version = "0.13", features = ["ecdsa"], optional = true } +p384 = { version = "0.13", features = ["ecdsa"], optional = true } +p521 = { version = "0.13", features = ["ecdsa"], optional = true } +signature = { version = "2", optional = true } +subtle = { version = "2", optional = true } # X.509 certificates -x509-parser = "0.18" -der = "0.8" +x509-parser = { version = "0.18", optional = true } +der = { version = "0.8", optional = true } # Base64 encoding/decoding base64 = "0.22" @@ -38,6 +38,17 @@ thiserror = "2" [features] default = ["xmldsig", "c14n"] -xmldsig = [] # XML Digital Signatures (sign + verify) +xmldsig = [ # XML Digital Signatures (sign + verify) + "dep:der", + "dep:p256", + "dep:p384", + "dep:p521", + "dep:rsa", + "dep:sha1", + "dep:sha2", + "dep:signature", + "dep:subtle", + "dep:x509-parser", +] xmlenc = [] # XML Encryption (encrypt + decrypt) c14n = [] # XML Canonicalization (inclusive + exclusive) diff --git a/tests/donor_full_verification_suite.rs b/tests/donor_full_verification_suite.rs index d27d163..a16bc8e 100644 --- a/tests/donor_full_verification_suite.rs +++ b/tests/donor_full_verification_suite.rs @@ -5,12 +5,25 @@ use std::path::{Path, PathBuf}; -use xml_sec::xmldsig::{DsigStatus, verify_signature_with_pem_key}; +use xml_sec::xmldsig::{ + DsigError, DsigStatus, FailureReason, ParseError, VerifyContext, verify_signature_with_pem_key, +}; + +#[derive(Clone, Copy)] +enum SkipProbe { + KeyNotFound, + UnsupportedSignatureAlgorithm, +} #[derive(Clone, Copy)] enum Expectation { - ValidWithKey { key_path: &'static str }, - Skip { reason: &'static str }, + ValidWithKey { + key_path: &'static str, + }, + Skip { + reason: &'static str, + probe: SkipProbe, + }, } struct VectorCase { @@ -78,6 +91,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha512.xml", expectation: Expectation::Skip { reason: "X509Digest key resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::KeyNotFound, }, }, // Merlin "basic signatures" required by P1-025. @@ -87,6 +101,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloped-dsa.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, VectorCase { @@ -94,6 +109,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml", expectation: Expectation::Skip { reason: "KeyValue auto-resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::KeyNotFound, }, }, VectorCase { @@ -101,6 +117,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009); X509 KeyInfo resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, VectorCase { @@ -108,6 +125,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009); X509/CRL KeyInfo resolution is not implemented yet (planned P2-009/P2-005)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, VectorCase { @@ -115,6 +133,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-is.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009); X509IssuerSerial resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, VectorCase { @@ -122,6 +141,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-ski.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009); X509SKI resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, VectorCase { @@ -129,6 +149,7 @@ fn cases() -> Vec { xml_path: "tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-sn.xml", expectation: Expectation::Skip { reason: "DSA signature method is not implemented yet (planned P4-009); X509SubjectName resolution is not implemented yet (planned P2-009)", + probe: SkipProbe::UnsupportedSignatureAlgorithm, }, }, ] @@ -137,7 +158,6 @@ fn cases() -> Vec { #[test] fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { let root = project_root(); - let skip_probe_key = read_fixture(&root.join("tests/fixtures/keys/rsa/rsa-2048-pubkey.pem")); let mut passed = 0usize; let mut failed = Vec::::new(); let mut skipped = Vec::::new(); @@ -162,18 +182,40 @@ fn donor_full_verification_suite_tracks_pass_fail_skip_counts() { } } } - Expectation::Skip { reason } => { + Expectation::Skip { reason, probe } => { let xml = read_fixture(&root.join(case.xml_path)); roxmltree::Document::parse(&xml) .unwrap_or_else(|err| panic!("{}: fixture XML must parse: {err}", case.name)); - if let Ok(result) = verify_signature_with_pem_key(&xml, &skip_probe_key, false) - && matches!(result.status, DsigStatus::Valid) - { - failed.push(format!( - "{}: expected non-Valid for skipped vector, got {:?}", - case.name, result.status - )); - continue; + match probe { + SkipProbe::KeyNotFound => match VerifyContext::new().verify(&xml) { + Ok(result) + if matches!( + result.status, + DsigStatus::Invalid(FailureReason::KeyNotFound) + ) => {} + Ok(result) => failed.push(format!( + "{}: expected Invalid(KeyNotFound) for skipped vector, got {:?}", + case.name, result.status + )), + Err(err) => failed.push(format!( + "{}: expected Invalid(KeyNotFound) for skipped vector, got error {err}", + case.name + )), + }, + SkipProbe::UnsupportedSignatureAlgorithm => match VerifyContext::new().verify(&xml) + { + Err(DsigError::ParseSignedInfo(ParseError::UnsupportedAlgorithm { + .. + })) => {} + Ok(result) => failed.push(format!( + "{}: expected unsupported signature algorithm error for skipped vector, got {:?}", + case.name, result.status + )), + Err(err) => failed.push(format!( + "{}: expected unsupported signature algorithm error for skipped vector, got {err}", + case.name + )), + }, } skipped.push(format!("{}: {}", case.name, reason)); }