Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyjU9gkG4ffc3WwyLF2Q4lmRlMmnw
lzJd31gHv5qBg74j1kKSaQWDZEkTHFt4g7AqIlRRqDt/u9euxVNa5RLqxg==
-----END PUBLIC KEY-----
68 changes: 68 additions & 0 deletions tests/fixtures/saml/response_signed_by_idp_ecdsa.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxf63324d7-7ba2-b371-90d6-171637d97253" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>https://fujifish.github.io/samling/samling.html</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
<ds:Reference URI="#pfxf63324d7-7ba2-b371-90d6-171637d97253">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>W7iYqYBNLg7dS+ueqLf04nO5V+c=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>THCZWgdX01bDRNyUHHS+u3U7URTI4c3+1cuXKeWFQDjX/yjrC6V/6wCwXtD4VyjU
aUxevxscW8FBCRTkwDR78A==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIBhzCCAS0CFGE3kR43hTxJz3hg+bsefDiZjTSiMAoGCCqGSM49BAMCMEUxCzAJ
BgNVBAYTAkNBMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQwIBcNMjQwNjIzMTc0NTQ5WhgPMzAyMzEwMjUxNzQ1
NDlaMEUxCzAJBgNVBAYTAkNBMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAATKNT2CQbh99zdbDIsXZDiWZGUyafCXMl3fWAe/moGDviPWQpJpBYNkSRMc
W3iDsCoiVFGoO3+7167FU1rlEurGMAoGCCqGSM49BAMCA0gAMEUCIQCdW4SacWlI
qj04IXo5QNWgbIrG6MKcXbvWEXDmMkiIewIgHkDlDn8Aq4reI+4BvUN+ZDmvOs1I
UevJyxGd/2RkolE=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>https://fujifish.github.io/samling/samling.html</saml:Issuer>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2030-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2030-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://test_accept_signed_with_correct_key.test</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2030-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
5 changes: 3 additions & 2 deletions tests/fixtures_smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -175,8 +176,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, 79,
"expected 79 fixture files total (23 keys + 41 c14n + 14 donor xmldsig + 1 saml); \
if you added/removed files, update this count"
);
}
Expand Down
61 changes: 61 additions & 0 deletions tests/saml_idp_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! 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.

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");

Comment thread
polaz marked this conversation as resolved.
// 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() {
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
));
assert!(
result.signed_info_references[0].pre_digest_data.is_some(),
"store_pre_digest=true must populate pre_digest_data for SignedInfo references"
);
}

#[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);

Comment thread
polaz marked this conversation as resolved.
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");

assert!(matches!(
result.status,
DsigStatus::Invalid(FailureReason::ReferenceDigestMismatch { ref_index: 0 })
));
}