From fe15d0edf46a07fdde2289597d0b70191a75c2e3 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 4 Apr 2026 22:35:29 +0300 Subject: [PATCH 1/3] test(xmldsig): add real saml idp integration test - add integration coverage for donor SAML IdP response verification - assert valid signature status and reference tamper detection Refs P1-026 --- tests/saml_idp_integration.rs | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/saml_idp_integration.rs diff --git a/tests/saml_idp_integration.rs b/tests/saml_idp_integration.rs new file mode 100644 index 0000000..a8d4a21 --- /dev/null +++ b/tests/saml_idp_integration.rs @@ -0,0 +1,49 @@ +//! Integration tests for real-world SAML response verification (ROADMAP P1-026). +//! +//! Uses a donor SAML 2.0 IdP response fixture to ensure XMLDSig verification +//! works against non-synthetic assertion payloads. + +use xml_sec::xmldsig::{DsigStatus, FailureReason, verify_signature_with_pem_key}; + +const IDP_RESPONSE_SIGNED_XML: &str = + include_str!("../donors/samael/test_vectors/response_signed_by_idp_ecdsa.xml"); + +const IDP_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyjU9gkG4ffc3WwyLF2Q4lmRlMmnw +lzJd31gHv5qBg74j1kKSaQWDZEkTHFt4g7AqIlRRqDt/u9euxVNa5RLqxg== +-----END PUBLIC KEY----- +"; + +#[test] +fn real_saml_idp_response_signature_is_valid() { + let result = verify_signature_with_pem_key(IDP_RESPONSE_SIGNED_XML, IDP_PUBLIC_KEY_PEM, true) + .expect("real SAML response should verify end-to-end"); + + assert!( + matches!(result.status, DsigStatus::Valid), + "expected Valid status, got {:?}", + result.status + ); + assert_eq!( + result.signed_info_references.len(), + 1, + "expected exactly one SignedInfo reference" + ); + assert!(matches!( + result.signed_info_references[0].status, + DsigStatus::Valid + )); +} + +#[test] +fn real_saml_idp_response_detects_reference_tampering() { + let tampered = IDP_RESPONSE_SIGNED_XML.replacen("test@example.com", "tampered@example.com", 1); + + let result = verify_signature_with_pem_key(&tampered, IDP_PUBLIC_KEY_PEM, false) + .expect("pipeline should complete with Invalid status on tampering"); + + assert!(matches!( + result.status, + DsigStatus::Invalid(FailureReason::ReferenceDigestMismatch { ref_index: 0 }) + )); +} From bf2e07111de26199e2d5b0b4acab6ddb7c62b919 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 4 Apr 2026 22:54:10 +0300 Subject: [PATCH 2/3] test(xmldsig): stabilize saml fixture integration - move SAML fixture to committed tests/fixtures path - assert tamper mutation is effective before verification - update fixture smoke file count for new fixture --- .../saml/response_signed_by_idp_ecdsa.xml | 68 +++++++++++++++++++ tests/fixtures_smoke.rs | 4 +- tests/saml_idp_integration.rs | 12 +++- 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/saml/response_signed_by_idp_ecdsa.xml diff --git a/tests/fixtures/saml/response_signed_by_idp_ecdsa.xml b/tests/fixtures/saml/response_signed_by_idp_ecdsa.xml new file mode 100644 index 0000000..e80f1db --- /dev/null +++ b/tests/fixtures/saml/response_signed_by_idp_ecdsa.xml @@ -0,0 +1,68 @@ + + + https://fujifish.github.io/samling/samling.html + + + + + + + + + + + W7iYqYBNLg7dS+ueqLf04nO5V+c= + + + THCZWgdX01bDRNyUHHS+u3U7URTI4c3+1cuXKeWFQDjX/yjrC6V/6wCwXtD4VyjU +aUxevxscW8FBCRTkwDR78A== + + +MIIBhzCCAS0CFGE3kR43hTxJz3hg+bsefDiZjTSiMAoGCCqGSM49BAMCMEUxCzAJ +BgNVBAYTAkNBMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwIBcNMjQwNjIzMTc0NTQ5WhgPMzAyMzEwMjUxNzQ1 +NDlaMEUxCzAJBgNVBAYTAkNBMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK +DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAATKNT2CQbh99zdbDIsXZDiWZGUyafCXMl3fWAe/moGDviPWQpJpBYNkSRMc +W3iDsCoiVFGoO3+7167FU1rlEurGMAoGCCqGSM49BAMCA0gAMEUCIQCdW4SacWlI +qj04IXo5QNWgbIrG6MKcXbvWEXDmMkiIewIgHkDlDn8Aq4reI+4BvUN+ZDmvOs1I +UevJyxGd/2RkolE= + + + + + + + + https://fujifish.github.io/samling/samling.html + + + _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 + + + + + + + http://test_accept_signed_with_correct_key.test + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + + test + + + test@example.com + + + users + examplerole1 + + + + diff --git a/tests/fixtures_smoke.rs b/tests/fixtures_smoke.rs index 0549b45..4bc643a 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, 77, - "expected 77 fixture files total (22 keys + 41 c14n + 14 donor xmldsig); \ + count, 78, + "expected 78 fixture files total (22 keys + 41 c14n + 14 donor xmldsig + 1 saml); \ if you added/removed files, update this count" ); } diff --git a/tests/saml_idp_integration.rs b/tests/saml_idp_integration.rs index a8d4a21..f3f5bee 100644 --- a/tests/saml_idp_integration.rs +++ b/tests/saml_idp_integration.rs @@ -6,7 +6,7 @@ use xml_sec::xmldsig::{DsigStatus, FailureReason, verify_signature_with_pem_key}; const IDP_RESPONSE_SIGNED_XML: &str = - include_str!("../donors/samael/test_vectors/response_signed_by_idp_ecdsa.xml"); + include_str!("fixtures/saml/response_signed_by_idp_ecdsa.xml"); const IDP_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyjU9gkG4ffc3WwyLF2Q4lmRlMmnw @@ -37,8 +37,18 @@ fn real_saml_idp_response_signature_is_valid() { #[test] fn real_saml_idp_response_detects_reference_tampering() { + assert!( + IDP_RESPONSE_SIGNED_XML.contains("test@example.com"), + "fixture must contain the signed value being tampered with" + ); + let tampered = IDP_RESPONSE_SIGNED_XML.replacen("test@example.com", "tampered@example.com", 1); + assert_ne!( + tampered, IDP_RESPONSE_SIGNED_XML, + "tampering must change the XML so this test exercises reference digest validation" + ); + let result = verify_signature_with_pem_key(&tampered, IDP_PUBLIC_KEY_PEM, false) .expect("pipeline should complete with Invalid status on tampering"); From c97fcb95aa2c92d0b176f83559ecfbaecec2d74e Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 4 Apr 2026 23:08:22 +0300 Subject: [PATCH 3/3] test(xmldsig): tighten saml idp fixture coverage - assert pre-digest capture when store_pre_digest=true - move SAML IdP public key into tracked fixture file - document PR linkage and intentional SHA-1 fixture usage --- tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem | 4 ++++ tests/fixtures_smoke.rs | 5 +++-- tests/saml_idp_integration.rs | 14 ++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem diff --git a/tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem b/tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem new file mode 100644 index 0000000..dd22360 --- /dev/null +++ b/tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyjU9gkG4ffc3WwyLF2Q4lmRlMmnw +lzJd31gHv5qBg74j1kKSaQWDZEkTHFt4g7AqIlRRqDt/u9euxVNa5RLqxg== +-----END PUBLIC KEY----- diff --git a/tests/fixtures_smoke.rs b/tests/fixtures_smoke.rs index 4bc643a..0819261 100644 --- a/tests/fixtures_smoke.rs +++ b/tests/fixtures_smoke.rs @@ -55,6 +55,7 @@ fn ec_p256_key_files_are_valid_pem() { assert_pem_file(&dir.join("ec-prime256v1-key.pem"), "PRIVATE KEY"); assert_pem_file(&dir.join("ec-prime256v1-cert.pem"), "CERTIFICATE"); assert_pem_file(&dir.join("ec-prime256v1-pubkey.pem"), "PUBLIC KEY"); + assert_pem_file(&dir.join("saml-idp-ecdsa-pubkey.pem"), "PUBLIC KEY"); } /// Verify EC P-384 key triplet exists and contains valid PEM markers. @@ -175,8 +176,8 @@ fn fixture_file_count_matches_expected() { let mut count = 0; count_files_recursive(fixtures_dir(), &mut count); assert_eq!( - count, 78, - "expected 78 fixture files total (22 keys + 41 c14n + 14 donor xmldsig + 1 saml); \ + count, 79, + "expected 79 fixture files total (23 keys + 41 c14n + 14 donor xmldsig + 1 saml); \ if you added/removed files, update this count" ); } diff --git a/tests/saml_idp_integration.rs b/tests/saml_idp_integration.rs index f3f5bee..71bba5c 100644 --- a/tests/saml_idp_integration.rs +++ b/tests/saml_idp_integration.rs @@ -1,4 +1,5 @@ -//! Integration tests for real-world SAML response verification (ROADMAP P1-026). +//! Integration tests for real-world SAML response verification. +//! Covers PR #44. //! //! Uses a donor SAML 2.0 IdP response fixture to ensure XMLDSig verification //! works against non-synthetic assertion payloads. @@ -8,11 +9,8 @@ use xml_sec::xmldsig::{DsigStatus, FailureReason, verify_signature_with_pem_key} const IDP_RESPONSE_SIGNED_XML: &str = include_str!("fixtures/saml/response_signed_by_idp_ecdsa.xml"); -const IDP_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyjU9gkG4ffc3WwyLF2Q4lmRlMmnw -lzJd31gHv5qBg74j1kKSaQWDZEkTHFt4g7AqIlRRqDt/u9euxVNa5RLqxg== ------END PUBLIC KEY----- -"; +// Fixture intentionally uses legacy SHA-1 DigestMethod for donor-compat coverage. +const IDP_PUBLIC_KEY_PEM: &str = include_str!("fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem"); #[test] fn real_saml_idp_response_signature_is_valid() { @@ -33,6 +31,10 @@ fn real_saml_idp_response_signature_is_valid() { result.signed_info_references[0].status, DsigStatus::Valid )); + assert!( + result.signed_info_references[0].pre_digest_data.is_some(), + "store_pre_digest=true must populate pre_digest_data for SignedInfo references" + ); } #[test]