Skip to content

Commit 6449654

Browse files
committed
Fix more tests
1 parent 084a7c9 commit 6449654

File tree

3 files changed

+90
-165
lines changed

3 files changed

+90
-165
lines changed

lib/ruby_saml/xml.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,27 @@ module XML
5757
# @raise [ValidationError] If there was a problem loading the SAML Message XML
5858
def safe_load_nokogiri(document, check_malformed_doc: true)
5959
doc_str = document.to_s
60-
raise StandardError.new('Dangerous XML detected. No Doctype nodes allowed') if doc_str.include?('<!DOCTYPE')
61-
62-
begin
63-
xml = Nokogiri::XML(doc_str) do |config|
64-
config.options = NOKOGIRI_OPTIONS
60+
error = nil
61+
error = StandardError.new('Dangerous XML detected. No Doctype nodes allowed') if doc_str.include?('<!DOCTYPE')
62+
63+
xml = nil
64+
unless error
65+
begin
66+
xml = Nokogiri::XML(doc_str) do |config|
67+
config.options = NOKOGIRI_OPTIONS
68+
end
69+
rescue StandardError => e
70+
error ||= e
71+
# raise StandardError.new(e.message)
6572
end
66-
rescue StandardError => e
67-
raise StandardError.new(e.message)
6873
end
6974

70-
raise StandardError.new('Dangerous XML detected. No Doctype nodes allowed') if xml.internal_subset
71-
72-
raise StandardError.new("There were XML errors when parsing: #{xml.errors}") if check_malformed_doc && !xml.errors.empty?
75+
# TODO: This is messy, its shims how the old work REXML parser
76+
if xml
77+
error ||= StandardError.new('Dangerous XML detected. No Doctype nodes allowed') if xml.internal_subset
78+
error ||= StandardError.new("There were XML errors when parsing: #{xml.errors}") if check_malformed_doc && !xml.errors.empty?
79+
end
80+
return Nokogiri::XML::Document.new if error || !xml
7381

7482
xml
7583
end

lib/ruby_saml/xml/signed_document_info.rb

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def validate_signature(cert)
6161

6262
# Compare digest
6363
calculated_digest = digest_algorithm.digest(canonicalized_subject)
64+
# puts "calculated_digest: #{calculated_digest.bytes}"
65+
# puts "digest_value: #{digest_value.bytes}"
66+
# puts "subject" + canonicalized_subject.inspect
67+
# puts "\n\n\n\n\n\n"
6468
unless calculated_digest == digest_value
6569
raise RubySaml::ValidationError.new('Digest mismatch')
6670
end
@@ -107,7 +111,7 @@ def signature_value
107111
# Get the canonicalized SignedInfo element
108112
# @return [String] The canonicalized SignedInfo element
109113
def canonicalized_signed_info
110-
signed_info_node.canonicalize(canon_algorithm_from_signed_info)
114+
@canonicalized_signed_info ||= signed_info_node.canonicalize(canon_algorithm_from_signed_info)
111115
end
112116

113117
# Get the Reference node
@@ -120,7 +124,7 @@ def reference_node
120124
# Get the ID of the signed element
121125
# @return [String] The ID of the signed element
122126
def subject_id
123-
id = uri_from_reference_node || signature_node.parent['ID']
127+
id = uri_from_reference_node || signature_node.parent&.[]('ID')
124128
return id unless !id || id.empty?
125129
raise RubySaml::ValidationError.new('No signed subject ID found')
126130
end
@@ -135,9 +139,17 @@ def subject_node
135139
# Get the canonicalized subject node (the node being signed)
136140
# @return [String] The canonicalized subject
137141
def canonicalized_subject
138-
dupe = Nokogiri::XML(subject_node.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)).root
139-
dupe.xpath('//ds:Signature', { 'ds' => RubySaml::XML::DSIG }).each(&:remove)
140-
dupe.canonicalize(canon_algorithm, inclusive_namespaces)
142+
remove_signature_node!
143+
subject_node.canonicalize(canon_algorithm, inclusive_namespaces)
144+
end
145+
146+
# TODO: Destructive side-effect!! signature_node.remove
147+
# should possibly deep copy the noko object initially
148+
def remove_signature_node!
149+
inclusive_namespaces # memoize this
150+
canonicalized_signed_info # memoize this
151+
152+
signature_node.remove
141153
end
142154

143155
# Get the digest algorithm
@@ -194,19 +206,23 @@ def certificate_fingerprint(algorithm = 'SHA256')
194206
# Extract inclusive namespaces from the document
195207
# @return [Array<String>, nil] The inclusive namespaces
196208
def inclusive_namespaces
197-
noko.at_xpath(
198-
'//ec:InclusiveNamespaces',
199-
{ 'ec' => RubySaml::XML::C14N }
200-
)&.[]('PrefixList')&.split
209+
@inclusive_namespaces ||= begin
210+
noko.at_xpath(
211+
'//ec:InclusiveNamespaces',
212+
{ 'ec' => RubySaml::XML::C14N }
213+
)&.[]('PrefixList')&.split
214+
end
201215
end
202216

203217
private
204218

205219
# Get the ds:Signature element from the document
206220
# @return [Nokogiri::XML::Element] The Signature element
207221
def signature_node
208-
noko.at_xpath('//ds:Signature', { 'ds' => RubySaml::XML::DSIG }) ||
209-
(raise RubySaml::ValidationError.new('No Signature node found'))
222+
@signature_node ||= begin
223+
noko.at_xpath('//ds:Signature', { 'ds' => RubySaml::XML::DSIG }) ||
224+
(raise RubySaml::ValidationError.new('No Signature node found'))
225+
end
210226
end
211227

212228
# Get the ds:SignedInfo element from the document

test/xml_test.rb

Lines changed: 45 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,14 @@ class XmlTest < Minitest::Test
211211
describe '#extract_inclusive_namespaces' do
212212
it 'support explicit namespace resolution for exclusive canonicalization' do
213213
document = fixture(:open_saml_response, false)
214-
inclusive_namespaces = RubySaml::XML::SignedDocumentValidator.send(:extract_inclusive_namespaces, document)
214+
inclusive_namespaces = RubySaml::XML::SignedDocumentInfo.new(document).send(:inclusive_namespaces)
215215

216216
assert_equal %w[ xs ], inclusive_namespaces
217217
end
218218

219219
it 'support implicit namespace resolution for exclusive canonicalization' do
220220
document = fixture(:no_signature_ns, false)
221-
inclusive_namespaces = RubySaml::XML::SignedDocumentValidator.send(:extract_inclusive_namespaces, document)
221+
inclusive_namespaces = RubySaml::XML::SignedDocumentInfo.new(document).send(:inclusive_namespaces)
222222

223223
assert_equal %w[ #default saml ds xs xsi ], inclusive_namespaces
224224
end
@@ -238,7 +238,7 @@ class XmlTest < Minitest::Test
238238
it 'return nil when inclusive namespace element is missing' do
239239
document = fixture(:no_signature_ns, false)
240240
document.slice! %r{<InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default saml ds xs xsi"/>}
241-
inclusive_namespaces = RubySaml::XML::SignedDocumentValidator.send(:extract_inclusive_namespaces, document)
241+
inclusive_namespaces = RubySaml::XML::SignedDocumentInfo.new(document).send(:inclusive_namespaces)
242242

243243
assert inclusive_namespaces.nil?
244244
end
@@ -250,157 +250,58 @@ class XmlTest < Minitest::Test
250250
settings.idp_sso_service_url = "https://idp.example.com/sso"
251251
settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
252252
settings.idp_slo_service_url = "https://idp.example.com/slo",
253-
settings.sp_entity_id = "https://sp.example.com/saml2"
253+
settings.sp_entity_id = "https://sp.example.com/saml2"
254254
settings.assertion_consumer_service_url = "https://sp.example.com/acs"
255255
settings.single_logout_service_url = "https://sp.example.com/sls"
256256
settings
257257
end
258258

259-
it "sign an AuthNRequest" do
260-
auth_request = RubySaml::Authrequest.new
261-
auth_request.assign_uuid(settings)
262-
request_doc = auth_request.create_xml_document(settings)
263-
264-
# Use the DocumentSigner to sign the document
265-
signed_doc = RubySaml::XML::DocumentSigner.sign_document(
266-
request_doc,
267-
ruby_saml_key,
268-
ruby_saml_cert,
269-
RubySaml::XML::RSA_SHA256,
270-
RubySaml::XML::SHA256
271-
)
272-
273-
# Verify our signature using the static validator
274-
errors = []
275-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
276-
signed_doc.to_s,
277-
ruby_saml_cert_fingerprint,
278-
soft: false
279-
)
280-
281-
# Test with certificate as text
282-
auth_request2 = RubySaml::Authrequest.new
283-
auth_request2.assign_uuid(settings)
284-
request_doc2 = auth_request2.create_xml_document(settings)
285-
286-
signed_doc2 = RubySaml::XML::DocumentSigner.sign_document(
287-
request_doc2,
288-
ruby_saml_key,
289-
ruby_saml_cert_text,
290-
RubySaml::XML::RSA_SHA256,
291-
RubySaml::XML::SHA256
292-
)
293-
294-
errors2 = []
295-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
296-
signed_doc2.to_s,
297-
ruby_saml_cert_fingerprint,
298-
soft: false
299-
)
259+
it "signs an AuthNRequest with a certificate object" do
260+
request_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
261+
request_doc = RubySaml::XML::DocumentSigner.sign_document(request_doc, ruby_saml_key, ruby_saml_cert)
262+
263+
# verify signature
264+
assert RubySaml::XML::SignedDocumentValidator.validate_document(request_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
265+
end
266+
267+
it "signs an AuthNRequest with a certificate string" do
268+
request_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
269+
request_doc = RubySaml::XML::DocumentSigner.sign_document(request_doc, ruby_saml_key, ruby_saml_cert_text)
270+
271+
# verify signature
272+
assert RubySaml::XML::SignedDocumentValidator.validate_document(request_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
273+
end
274+
275+
it "signs a LogoutRequest with a certificate object" do
276+
logout_request_doc = RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings)
277+
logout_request_doc = RubySaml::XML::DocumentSigner.sign_document(logout_request_doc, ruby_saml_key, ruby_saml_cert)
278+
279+
# verify signature
280+
assert RubySaml::XML::SignedDocumentValidator.validate_document(logout_request_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
300281
end
301282

302-
it "sign an AuthNRequest with certificate as text" do
303-
auth_request = RubySaml::Authrequest.new
304-
auth_request.assign_uuid(settings)
305-
request_doc = auth_request.create_xml_document(settings)
306-
307-
signed_doc = RubySaml::XML::DocumentSigner.sign_document(
308-
request_doc,
309-
ruby_saml_key,
310-
ruby_saml_cert_text,
311-
RubySaml::XML::RSA_SHA256,
312-
RubySaml::XML::SHA256
313-
)
314-
315-
# Verify our signature
316-
errors = []
317-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
318-
signed_doc.to_s,
319-
ruby_saml_cert_fingerprint,
320-
soft: false
321-
)
283+
it "signs a LogoutRequest with a certificate string" do
284+
logout_request_doc = RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings)
285+
logout_request_doc = RubySaml::XML::DocumentSigner.sign_document(logout_request_doc, ruby_saml_key, ruby_saml_cert_text)
286+
287+
# verify signature
288+
assert RubySaml::XML::SignedDocumentValidator.validate_document(logout_request_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
322289
end
323290

324-
it "sign a LogoutRequest" do
325-
logout_request = RubySaml::Logoutrequest.new
326-
logout_request.assign_uuid(settings)
327-
request_doc = logout_request.create_xml_document(settings)
328-
329-
signed_doc = RubySaml::XML::DocumentSigner.sign_document(
330-
request_doc,
331-
ruby_saml_key,
332-
ruby_saml_cert,
333-
RubySaml::XML::RSA_SHA256,
334-
RubySaml::XML::SHA256
335-
)
336-
337-
# Verify our signature
338-
errors = []
339-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
340-
signed_doc.to_s,
341-
ruby_saml_cert_fingerprint,
342-
soft: false
343-
)
344-
345-
logout_request2 = RubySaml::Logoutrequest.new
346-
logout_request2.assign_uuid(settings)
347-
request_doc2 = logout_request2.create_xml_document(settings)
348-
349-
signed_doc2 = RubySaml::XML::DocumentSigner.sign_document(
350-
request_doc2,
351-
ruby_saml_key,
352-
ruby_saml_cert_text,
353-
RubySaml::XML::RSA_SHA256,
354-
RubySaml::XML::SHA256
355-
)
356-
357-
# Verify our signature
358-
errors2 = []
359-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
360-
signed_doc2.to_s,
361-
ruby_saml_cert_fingerprint,
362-
soft: false
363-
)
291+
it "signs a LogoutResponse with a certificate object" do
292+
logout_response_doc = RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message")
293+
logout_response_doc = RubySaml::XML::DocumentSigner.sign_document(logout_response_doc, ruby_saml_key, ruby_saml_cert)
294+
295+
# verify signature
296+
assert RubySaml::XML::SignedDocumentValidator.validate_document(logout_response_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
364297
end
365298

366-
it "sign a LogoutResponse" do
367-
logout_response = RubySaml::SloLogoutresponse.new
368-
logout_response.assign_uuid(settings)
369-
response_doc = logout_response.create_xml_document(settings, 'request_id_example', "Custom Logout Message")
370-
371-
signed_doc = RubySaml::XML::DocumentSigner.sign_document(
372-
response_doc,
373-
ruby_saml_key,
374-
ruby_saml_cert,
375-
RubySaml::XML::RSA_SHA256,
376-
RubySaml::XML::SHA256
377-
)
378-
379-
# Verify our signature
380-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
381-
signed_doc.to_s,
382-
ruby_saml_cert_fingerprint,
383-
soft: false
384-
)
385-
386-
logout_response2 = RubySaml::SloLogoutresponse.new
387-
logout_response2.assign_uuid(settings)
388-
response_doc2 = logout_response2.create_xml_document(settings, 'request_id_example', "Custom Logout Message")
389-
390-
signed_doc2 = RubySaml::XML::DocumentSigner.sign_document(
391-
response_doc2,
392-
ruby_saml_key,
393-
ruby_saml_cert_text,
394-
RubySaml::XML::RSA_SHA256,
395-
RubySaml::XML::SHA256
396-
)
397-
398-
# Verify our signature
399-
assert RubySaml::XML::SignedDocumentValidator.validate_document(
400-
signed_doc2.to_s,
401-
ruby_saml_cert_fingerprint,
402-
soft: false
403-
)
299+
it "signs a LogoutResponse with a certificate string" do
300+
logout_response_doc = RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message")
301+
logout_response_doc = RubySaml::XML::DocumentSigner.sign_document(logout_response_doc, ruby_saml_key, ruby_saml_cert_text)
302+
303+
# verify signature
304+
assert RubySaml::XML::SignedDocumentValidator.validate_document(logout_response_doc.to_s, ruby_saml_cert_fingerprint, soft: false)
404305
end
405306
end
406307

@@ -554,7 +455,7 @@ class XmlTest < Minitest::Test
554455
refute RubySaml::XML::SignedDocumentValidator.validate_document_with_cert(document, idp_cert).is_a?(TrueClass), 'Document should be valid'
555456
errors = []
556457
RubySaml::XML::SignedDocumentValidator.validate_document_with_cert(document, idp_cert, errors)
557-
assert_equal(["Document Certificate Error: PEM_read_bio_X509: no start line"], errors)
458+
assert_equal(["Document Certificate Error"], errors)
558459
end
559460
end
560461

0 commit comments

Comments
 (0)