Skip to content

Commit 06a205c

Browse files
committed
Finish fixing tests. Yay!
1 parent 9652b84 commit 06a205c

File tree

6 files changed

+44
-59
lines changed

6 files changed

+44
-59
lines changed

lib/ruby_saml/response.rb

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -819,23 +819,26 @@ def doc_to_validate
819819
#
820820
def validate_signature
821821
error_msg = "Invalid Signature on SAML Response"
822-
823822
doc = doc_to_validate
824-
subject_id = RubySaml::XML::SignedDocumentValidator.subject_id(document)
825-
return false unless subject_id
826823

827-
sig_elements = document.xpath(
828-
"/p:Response[@ID=$id]/ds:Signature",
829-
{ "p" => RubySaml::XML::NS_PROTOCOL, "ds" => RubySaml::XML::DSIG },
830-
id: subject_id
831-
)
824+
# TODO: document vs doc is super confusing
825+
subject_id = RubySaml::XML::SignedDocumentValidator.subject_id(document)
826+
sig_elements = []
827+
if subject_id
828+
sig_elements = document.xpath(
829+
"/p:Response[@ID=$id]/ds:Signature",
830+
{ "p" => RubySaml::XML::NS_PROTOCOL, "ds" => RubySaml::XML::DSIG },
831+
id: subject_id
832+
)
833+
end
832834

833835
# Check signature node inside assertion
834836
if sig_elements.empty?
837+
subject_id2 = RubySaml::XML::SignedDocumentValidator.subject_id(doc)
835838
sig_elements = doc.xpath(
836839
"/p:Response/a:Assertion[@ID=$id]/ds:Signature",
837840
SAML_NAMESPACES,
838-
id: subject_id
841+
id: subject_id2
839842
)
840843
end
841844

lib/ruby_saml/xml.rb

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,11 @@ def safe_load_nokogiri(document, check_malformed_doc: true)
9191
xml
9292
end
9393

94-
# def clone_node(node)
95-
# if node.is_a?(Nokogiri::XML::Node)
96-
# node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
97-
# end
98-
#
99-
# Nokogiri::XML(doc_str) do |config|
100-
# config.options = NOKOGIRI_OPTIONS
101-
# end
102-
# end
94+
def copy_nokogiri(noko)
95+
Nokogiri::XML(noko.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)) do |config|
96+
config.options = NOKOGIRI_OPTIONS
97+
end
98+
end
10399

104100
# Lookup XML canonicalization algorithm.
105101
# @api private

lib/ruby_saml/xml/signed_document_info.rb

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ class SignedDocumentInfo
1313
# @param noko [Nokogiri::XML] The XML document to validate
1414
# @param check_malformed_doc [Boolean] Whether to check for malformed documents
1515
def initialize(noko, check_malformed_doc: true)
16-
noko = RubySaml::XML.safe_load_nokogiri(noko, check_malformed_doc: check_malformed_doc) unless noko.is_a?(Nokogiri::XML::Document)
16+
noko = if noko.is_a?(Nokogiri::XML::Document)
17+
RubySaml::XML.copy_nokogiri(noko)
18+
else
19+
RubySaml::XML.safe_load_nokogiri(noko, check_malformed_doc: check_malformed_doc)
20+
end
1721
@noko = noko
1822
@check_malformed_doc = check_malformed_doc
1923
end
@@ -52,16 +56,8 @@ def validate_signature(cert)
5256

5357
# Compare digest
5458
calculated_digest = digest_algorithm.digest(canonicalized_subject)
55-
# puts "calculated_digest: #{calculated_digest.bytes}"
56-
# puts "digest_value: #{digest_value.bytes}"
57-
# puts "subject" + canonicalized_subject.inspect
58-
# puts "\n\n\n\n\n\n"
5959
raise RubySaml::ValidationError.new('Digest mismatch') unless calculated_digest == digest_value
6060

61-
# puts "signature_hash_algorithm: #{signature_hash_algorithm}"
62-
# puts "signature_value: #{signature_value.bytes}"
63-
# puts "canonicalized_signed_info: #{canonicalized_signed_info.inspect}"
64-
6561
# Verify signature
6662
signature_verified = false
6763
begin
@@ -113,10 +109,8 @@ def reference_node
113109
# Get the ID of the signed element
114110
# @return [String] The ID of the signed element
115111
def subject_id
116-
id = uri_from_reference_node || signature_node.parent&.[]('ID')
117-
return id unless !id || id.empty?
118-
119-
raise RubySaml::ValidationError.new('No signed subject ID found')
112+
# TODO: The error here is problematic, perhaps it can be checked elsewhere
113+
@subject_id ||= extract_subject_id || (raise RubySaml::ValidationError.new('No signed subject ID found'))
120114
end
121115

122116
# Get the subject node (the node being signed)
@@ -136,8 +130,10 @@ def canonicalized_subject
136130
# TODO: Destructive side-effect!! signature_node.remove
137131
# should possibly deep copy the noko object initially
138132
def remove_signature_node!
139-
inclusive_namespaces # memoize this
140-
canonicalized_signed_info # memoize this
133+
# memoize various elements
134+
subject_id
135+
inclusive_namespaces
136+
canonicalized_signed_info
141137

142138
signature_node.remove
143139
end
@@ -205,6 +201,12 @@ def inclusive_namespaces
205201

206202
private
207203

204+
def extract_subject_id
205+
return unless reference_node
206+
207+
reference_node['URI'][1..] || signature_node.parent['ID']
208+
end
209+
208210
# Get the ds:Signature element from the document
209211
# @return [Nokogiri::XML::Element] The Signature element
210212
def signature_node
@@ -236,11 +238,6 @@ def canon_algorithm_from_transforms
236238
transform_element = transforms.reverse.detect { |el| el['Algorithm'] }
237239
RubySaml::XML.canon_algorithm(transform_element, default: false)
238240
end
239-
240-
def uri_from_reference_node
241-
uri = reference_node&.[]('URI')&.delete_prefix('#')
242-
uri unless !uri || uri.empty?
243-
end
244241
end
245242
end
246243
end

lib/ruby_saml/xml/signed_document_validator.rb

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,10 @@ def validate_signature(document, base64_cert, errors = [], soft: true)
4040
end
4141

4242
# TODO: This is a workaround to avoid errors
43-
def subject_id(noko)
44-
# TODO: Should be this
45-
# SignedDocumentInfo.new(document).subject_id
46-
noko = RubySaml::XML.safe_load_nokogiri(noko) unless noko.is_a?(Nokogiri::XML::Document)
47-
reference_element = noko.at_xpath(
48-
'//ds:Signature/ds:SignedInfo/ds:Reference',
49-
{ 'ds' => RubySaml::XML::DSIG }
50-
)
51-
52-
return nil if reference_element.nil?
53-
54-
sei = reference_element['URI'].delete_prefix('#')
55-
return sei unless !sei || sei.empty?
56-
57-
reference_element.parent.parent.parent['ID']
43+
def subject_id(document)
44+
SignedDocumentInfo.new(document).subject_id
45+
rescue RubySaml::ValidationError
46+
# TODO: Consider removing the error in SignedDocumentInfo#subject_id
5847
end
5948

6049
def subject_node(document)

test/response_test.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,11 @@ def generate_audience_error(expected, actual)
376376
end
377377

378378
it "support dynamic namespace resolution on signature elements" do
379-
no_signature_response = RubySaml::Response.new(fixture("no_signature_ns.xml"))
379+
no_signature_response = RubySaml::Response.new(fixture("inclusive_namespaces.xml"))
380380
no_signature_response.stubs(:conditions).returns(nil)
381381
no_signature_response.stubs(:validate_subject_confirmation).returns(true)
382382
no_signature_response.settings = settings
383-
no_signature_response.settings.idp_cert_fingerprint = "3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7"
384-
RubySaml::XML::SignedDocumentValidator.expects(:validate_signature).returns(true)
383+
no_signature_response.settings.idp_cert_fingerprint = "E0:89:CF:86:E3:00:C0:C8:B9:BC:04:16:D7:F3:8D:8D:9C:8F:20:B3:FE:7C:EC:64:D5:5D:90:E3:7B:8B:5A:51"
385384
assert no_signature_response.is_valid?
386385
end
387386

test/xml_test.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,12 @@ class XmlTest < Minitest::Test
391391

392392
describe 'signature wrapping attack - concealed SAML response body' do
393393
let(:document_data) { read_invalid_response("response_with_concealed_signed_assertion.xml") }
394+
let(:document) { RubySaml::Response.new(document_data) }
394395
let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' }
395396

396-
it 'is valid, but fails to retrieve information' do
397-
assert RubySaml::XML::SignedDocumentValidator.validate_document(document, fingerprint), 'Document should be valid'
398-
assert response.name_id.nil?, 'Document should expose only signed, valid details'
397+
it 'is valid, but the unsigned information is ignored in favour of the signed information' do
398+
assert RubySaml::XML::SignedDocumentValidator.validate_document(document.document, fingerprint), 'Document should be valid'
399+
assert_equal 'someone@example.org', document.name_id, 'Document should expose only signed, valid details'
399400
end
400401
end
401402
end

0 commit comments

Comments
 (0)