Skip to content

Commit 864be35

Browse files
add tests to prove signature wrapping attack resistance
1 parent c7a7716 commit 864be35

File tree

3 files changed

+71
-3
lines changed

3 files changed

+71
-3
lines changed

lib/samlr/tools/metadata_builder.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ def self.build(options = {})
1313
name_identity_format = options[:name_identity_format]
1414
consumer_service_url = options[:consumer_service_url]
1515
consumer_service_binding = options[:consumer_service_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
16+
metadata_id = options[:metadata_id] || Samlr::Tools.uuid
17+
sign_metadata = options[:sign_metadata] || false
1618

1719
# Mandatory
1820
entity_id = options.fetch(:entity_id)
1921

2022
builder = Nokogiri::XML::Builder.new do |xml|
21-
xml.EntityDescriptor("xmlns:md" => NS_MAP["md"], "entityID" => entity_id) do
23+
xml.EntityDescriptor("xmlns:md" => NS_MAP["md"], "ID" => metadata_id, "entityID" => entity_id) do
2224
xml.doc.root.namespace = xml.doc.root.namespace_definitions.find { |ns| ns.prefix == "md" }
2325

2426
xml["md"].SPSSODescriptor("protocolSupportEnumeration" => NS_MAP["samlp"]) do
@@ -33,7 +35,14 @@ def self.build(options = {})
3335
end
3436
end
3537

36-
builder.to_xml(COMPACT)
38+
metadata = builder.doc
39+
40+
if sign_metadata
41+
metadata_options = options
42+
metadata = ResponseBuilder.sign(metadata, metadata_id, metadata_options)
43+
end
44+
45+
metadata.to_xml(COMPACT)
3746
end
3847

3948
end

lib/samlr/tools/response_builder.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ def self.sign(document, element_id, options)
125125
end unless skip_keyinfo
126126
end
127127
# digest.root.last_element_child.after "<SignatureValue>#{signature}</SignatureValue>"
128-
element.at("./saml:Issuer", NS_MAP).add_next_sibling(digest)
128+
if element.at("./saml:Issuer", NS_MAP)
129+
element.at("./saml:Issuer", NS_MAP).add_next_sibling(digest)
130+
else
131+
element.children.first.add_previous_sibling(digest)
132+
end
129133

130134
document
131135
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require File.expand_path("test/test_helper")
2+
3+
describe Samlr do
4+
describe "invalid multiple saml responses" do
5+
let(:shared_id) { Samlr::Tools.uuid }
6+
let(:xml_response_doc) do
7+
options = {
8+
:destination => "https://example.org/saml/endpoint",
9+
:in_response_to => Samlr::Tools.uuid,
10+
:name_id => "[email protected]",
11+
:audience => "example.org",
12+
:not_on_or_after => Samlr::Tools::Timestamp.stamp(Time.now + 60),
13+
:not_before => Samlr::Tools::Timestamp.stamp(Time.now - 60),
14+
:response_id => Samlr::Tools.uuid,
15+
skip_conditions: true,
16+
sign_response: false,
17+
sign_assertion: false,
18+
assertion_id: shared_id,
19+
certificate: TEST_CERTIFICATE
20+
}
21+
Samlr::Tools::ResponseBuilder.build(options)
22+
end
23+
let(:xml_metadata_doc) do
24+
options = {
25+
:entity_id => "https://sp.example.com/saml2",
26+
:name_identity_format => "identity_format",
27+
:consumer_service_url => "https://support.sp.example.com/",
28+
:sign_metadata => true,
29+
metadata_id: shared_id,
30+
certificate: TEST_CERTIFICATE
31+
}
32+
Samlr::Tools::MetadataBuilder.build(options)
33+
end
34+
35+
let(:fingerprint) { Samlr::Certificate.new(TEST_CERTIFICATE.x509).fingerprint.value }
36+
let(:saml_response) { Samlr::Response.new(Base64.encode64(xml_response_doc), fingerprint: fingerprint) }
37+
38+
it "succeeds" do
39+
metadata_doc = Nokogiri::XML(xml_metadata_doc)
40+
response_doc = Nokogiri::XML(xml_response_doc)
41+
42+
metadata_signature_doc = metadata_doc.xpath("md:EntityDescriptor/ds:Signature", Samlr::NS_MAP).first
43+
metadata_entity_descriptor_doc = metadata_doc.xpath("md:EntityDescriptor", Samlr::NS_MAP).first
44+
45+
assertion_doc = response_doc.xpath("/samlp:Response/saml:Assertion", Samlr::NS_MAP).first
46+
assertion_doc.xpath("saml:Subject/saml:NameID").first.content = "[email protected]"
47+
assertion_doc.xpath("saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData", Samlr::NS_MAP).first.add_child(metadata_entity_descriptor_doc.dup)
48+
response_doc.at("/samlp:Response/saml:Issuer", Samlr::NS_MAP).add_next_sibling(metadata_signature_doc.dup)
49+
50+
crafted_saml_response = Samlr::Response.new(Base64.encode64(response_doc.to_xml), fingerprint: fingerprint)
51+
error = assert_raises(Samlr::SignatureError) { crafted_saml_response.verify! }
52+
assert_equal "Expected 1 element with id #{shared_id}, found 2", error.details
53+
end
54+
end
55+
end

0 commit comments

Comments
 (0)