ruby-saml 0.9.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.md +71 -15
- data/changelog.md +15 -6
- data/lib/onelogin/ruby-saml.rb +1 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +25 -2
- data/lib/onelogin/ruby-saml/attributes.rb +42 -23
- data/lib/onelogin/ruby-saml/authrequest.rb +33 -8
- data/lib/onelogin/ruby-saml/http_error.rb +7 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +65 -10
- data/lib/onelogin/ruby-saml/logging.rb +14 -10
- data/lib/onelogin/ruby-saml/logoutrequest.rb +39 -14
- data/lib/onelogin/ruby-saml/logoutresponse.rb +166 -39
- data/lib/onelogin/ruby-saml/metadata.rb +40 -23
- data/lib/onelogin/ruby-saml/response.rb +562 -88
- data/lib/onelogin/ruby-saml/saml_message.rb +80 -14
- data/lib/onelogin/ruby-saml/settings.rb +62 -23
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +210 -20
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -13
- data/lib/onelogin/ruby-saml/utils.rb +163 -40
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/schemas/saml-schema-metadata-2.0.xsd +0 -2
- data/lib/xml_security.rb +87 -29
- data/ruby-saml.gemspec +1 -0
- data/test/certificates/{r1_certificate2_base64 → certificate_without_head_foot} +0 -0
- data/test/certificates/formatted_certificate +14 -0
- data/test/certificates/formatted_private_key +12 -0
- data/test/certificates/formatted_rsa_private_key +12 -0
- data/test/certificates/invalid_certificate1 +1 -0
- data/test/certificates/invalid_certificate2 +1 -0
- data/test/certificates/invalid_certificate3 +12 -0
- data/test/certificates/invalid_private_key1 +1 -0
- data/test/certificates/invalid_private_key2 +1 -0
- data/test/certificates/invalid_private_key3 +10 -0
- data/test/certificates/invalid_rsa_private_key1 +1 -0
- data/test/certificates/invalid_rsa_private_key2 +1 -0
- data/test/certificates/invalid_rsa_private_key3 +10 -0
- data/test/idp_metadata_parser_test.rb +41 -4
- data/test/logging_test.rb +62 -0
- data/test/logout_requests/invalid_slo_request.xml +6 -0
- data/test/{responses → logout_requests}/slo_request.xml +0 -0
- data/test/logout_requests/slo_request.xml.base64 +1 -0
- data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
- data/test/logout_requests/slo_request_with_session_index.xml +5 -0
- data/test/{responses → logout_responses}/logoutresponse_fixtures.rb +6 -6
- data/test/logoutrequest_test.rb +79 -52
- data/test/logoutresponse_test.rb +206 -59
- data/test/metadata_test.rb +77 -7
- data/test/request_test.rb +80 -65
- data/test/response_test.rb +862 -189
- data/test/responses/attackxee.xml +13 -0
- data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
- data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
- data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
- data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
- data/test/responses/invalids/no_id.xml.base64 +1 -0
- data/test/responses/invalids/no_saml2.xml.base64 +1 -0
- data/test/responses/invalids/no_signature.xml.base64 +1 -0
- data/test/responses/invalids/no_status.xml.base64 +1 -0
- data/test/responses/invalids/no_status_code.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
- data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
- data/test/responses/{response4.xml.base64 → response_assertion_wrapped.xml.base64} +0 -0
- data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
- data/test/responses/response_unsigned_xml_base64 +1 -0
- data/test/responses/{response5.xml.base64 → response_with_saml2_namespace.xml.base64} +0 -0
- data/test/responses/{response3.xml.base64 → response_with_signed_assertion.xml.base64} +0 -0
- data/test/responses/{r1_response6.xml.base64 → response_with_signed_assertion_2.xml.base64} +0 -0
- data/test/responses/{response1.xml.base64 → response_with_undefined_recipient.xml.base64} +0 -0
- data/test/responses/{response2.xml.base64 → response_without_attributes.xml.base64} +0 -0
- data/test/responses/{wrapped_response_2.xml.base64 → response_wrapped.xml.base64} +0 -0
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/valid_response.xml.base64 +1 -0
- data/test/saml_message_test.rb +56 -0
- data/test/settings_test.rb +138 -1
- data/test/slo_logoutrequest_test.rb +239 -28
- data/test/slo_logoutresponse_test.rb +93 -71
- data/test/test_helper.rb +138 -31
- data/test/utils_test.rb +129 -25
- data/test/xml_security_test.rb +140 -71
- metadata +142 -25
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
data/test/utils_test.rb
CHANGED
|
@@ -1,41 +1,145 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
class UtilsTest < Minitest::Test
|
|
4
|
-
describe "
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
assert_equal 'element text', OneLogin::RubySaml::Utils.element_text(element)
|
|
9
|
-
end
|
|
4
|
+
describe ".format_cert" do
|
|
5
|
+
let(:formatted_certificate) do
|
|
6
|
+
read_certificate("formatted_certificate")
|
|
7
|
+
end
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
it "returns empty string when the cert is an empty string" do
|
|
10
|
+
cert = ""
|
|
11
|
+
assert_equal "", OneLogin::RubySaml::Utils.format_cert(cert)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "returns nil when the cert is nil" do
|
|
15
|
+
cert = nil
|
|
16
|
+
assert_equal nil, OneLogin::RubySaml::Utils.format_cert(cert)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns the certificate when it is valid" do
|
|
20
|
+
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(formatted_certificate)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "reformats the certificate when there are spaces and no line breaks" do
|
|
24
|
+
invalid_certificate1 = read_certificate("invalid_certificate1")
|
|
25
|
+
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "reformats the certificate when there are spaces and no headers" do
|
|
29
|
+
invalid_certificate2 = read_certificate("invalid_certificate2")
|
|
30
|
+
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate2)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "reformats the certificate when there line breaks and no headers" do
|
|
34
|
+
invalid_certificate3 = read_certificate("invalid_certificate3")
|
|
35
|
+
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate3)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe ".format_private_key" do
|
|
40
|
+
let(:formatted_private_key) do
|
|
41
|
+
read_certificate("formatted_private_key")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "returns empty string when the private key is an empty string" do
|
|
45
|
+
private_key = ""
|
|
46
|
+
assert_equal "", OneLogin::RubySaml::Utils.format_private_key(private_key)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns nil when the private key is nil" do
|
|
50
|
+
private_key = nil
|
|
51
|
+
assert_equal nil, OneLogin::RubySaml::Utils.format_private_key(private_key)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "returns the private key when it is valid" do
|
|
55
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_private_key)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "reformats the private key when there are spaces and no line breaks" do
|
|
59
|
+
invalid_private_key1 = read_certificate("invalid_private_key1")
|
|
60
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key1)
|
|
61
|
+
end
|
|
15
62
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
63
|
+
it "reformats the private key when there are spaces and no headers" do
|
|
64
|
+
invalid_private_key2 = read_certificate("invalid_private_key2")
|
|
65
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key2)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "reformats the private key when there line breaks and no headers" do
|
|
69
|
+
invalid_private_key3 = read_certificate("invalid_private_key3")
|
|
70
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key3)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "an RSA public key" do
|
|
74
|
+
let(:formatted_rsa_private_key) do
|
|
75
|
+
read_certificate("formatted_rsa_private_key")
|
|
19
76
|
end
|
|
20
77
|
|
|
21
|
-
it
|
|
22
|
-
|
|
23
|
-
assert_equal 'element & text', OneLogin::RubySaml::Utils.element_text(element)
|
|
78
|
+
it "returns the private key when it is valid" do
|
|
79
|
+
assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_rsa_private_key)
|
|
24
80
|
end
|
|
25
81
|
|
|
26
|
-
it
|
|
27
|
-
|
|
28
|
-
assert_equal
|
|
82
|
+
it "reformats the private key when there are spaces and no line breaks" do
|
|
83
|
+
invalid_rsa_private_key1 = read_certificate("invalid_rsa_private_key1")
|
|
84
|
+
assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key1)
|
|
29
85
|
end
|
|
30
86
|
|
|
31
|
-
it
|
|
32
|
-
|
|
87
|
+
it "reformats the private key when there are spaces and no headers" do
|
|
88
|
+
invalid_rsa_private_key2 = read_certificate("invalid_rsa_private_key2")
|
|
89
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key2)
|
|
33
90
|
end
|
|
34
91
|
|
|
35
|
-
it
|
|
36
|
-
|
|
37
|
-
assert_equal
|
|
92
|
+
it "reformats the private key when there line breaks and no headers" do
|
|
93
|
+
invalid_rsa_private_key3 = read_certificate("invalid_rsa_private_key3")
|
|
94
|
+
assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key3)
|
|
38
95
|
end
|
|
39
96
|
end
|
|
40
97
|
end
|
|
98
|
+
|
|
99
|
+
describe "build_query" do
|
|
100
|
+
it "returns the query string" do
|
|
101
|
+
params = {}
|
|
102
|
+
params[:type] = "SAMLRequest"
|
|
103
|
+
params[:data] = "PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8+"
|
|
104
|
+
params[:relay_state] = "http://example.com"
|
|
105
|
+
params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
|
106
|
+
query_string = OneLogin::RubySaml::Utils.build_query(params)
|
|
107
|
+
assert_equal "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1", query_string
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "#verify_signature" do
|
|
112
|
+
before do
|
|
113
|
+
@params = {}
|
|
114
|
+
@params[:cert] = ruby_saml_cert
|
|
115
|
+
@params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
|
116
|
+
@params[:query_string] = "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "returns true when the signature is valid" do
|
|
120
|
+
@params[:signature] = "uWJm/T4gKLYEsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4="
|
|
121
|
+
assert OneLogin::RubySaml::Utils.verify_signature(@params)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "returns false when the signature is invalid" do
|
|
125
|
+
@params[:signature] = "uWJm/InVaLiDsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4="
|
|
126
|
+
assert !OneLogin::RubySaml::Utils.verify_signature(@params)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#status_error_msg" do
|
|
131
|
+
it "returns a error msg with a status message" do
|
|
132
|
+
error_msg = "The status code of the Logout Response was not Success"
|
|
133
|
+
status_code = "urn:oasis:names:tc:SAML:2.0:status:Requester"
|
|
134
|
+
status_message = "The request could not be performed due to an error on the part of the requester."
|
|
135
|
+
status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
|
|
136
|
+
assert_equal = "The status code of the Logout Response was not Success, was Requester -> The request could not be performed due to an error on the part of the requester.", status_error_msg
|
|
137
|
+
|
|
138
|
+
status_error_msg2 = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code)
|
|
139
|
+
assert_equal = "The status code of the Logout Response was not Success, was Requester", status_error_msg2
|
|
140
|
+
|
|
141
|
+
status_error_msg3 = OneLogin::RubySaml::Utils.status_error_msg(error_msg)
|
|
142
|
+
assert_equal = "The status code of the Logout Response was not Success", status_error_msg3
|
|
143
|
+
end
|
|
144
|
+
end
|
|
41
145
|
end
|
data/test/xml_security_test.rb
CHANGED
|
@@ -6,78 +6,125 @@ class XmlSecurityTest < Minitest::Test
|
|
|
6
6
|
include XMLSecurity
|
|
7
7
|
|
|
8
8
|
describe "XmlSecurity" do
|
|
9
|
+
|
|
10
|
+
let(:decoded_response) { Base64.decode64(response_document_without_recipient) }
|
|
11
|
+
let(:document) { XMLSecurity::SignedDocument.new(decoded_response) }
|
|
12
|
+
let(:settings) { OneLogin::RubySaml::Settings.new() }
|
|
13
|
+
|
|
9
14
|
before do
|
|
10
|
-
@
|
|
11
|
-
@base64cert = @document.elements["//ds:X509Certificate"].text
|
|
15
|
+
@base64cert = document.elements["//ds:X509Certificate"].text
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
it "should run validate without throwing NS related exceptions" do
|
|
15
|
-
assert
|
|
19
|
+
assert !document.validate_signature(@base64cert, true)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
it "should run validate with throwing NS related exceptions" do
|
|
19
23
|
assert_raises(OneLogin::RubySaml::ValidationError) do
|
|
20
|
-
|
|
24
|
+
document.validate_signature(@base64cert, false)
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
it "not raise an error when softly validating the document multiple times" do
|
|
25
|
-
2.times { assert_equal
|
|
29
|
+
2.times { assert_equal document.validate_signature(@base64cert, true), false }
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
it "not raise an error when softly validating the document and the X509Certificate is missing" do
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
assert !document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test
|
|
33
|
+
decoded_response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
|
|
34
|
+
mod_document = XMLSecurity::SignedDocument.new(decoded_response)
|
|
35
|
+
assert !mod_document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
it "should raise Fingerprint mismatch" do
|
|
36
39
|
exception = assert_raises(OneLogin::RubySaml::ValidationError) do
|
|
37
|
-
|
|
40
|
+
document.validate_document("no:fi:ng:er:pr:in:t", false)
|
|
38
41
|
end
|
|
39
42
|
assert_equal("Fingerprint mismatch", exception.message)
|
|
40
|
-
|
|
43
|
+
assert_includes document.errors, "Fingerprint mismatch"
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
it "should raise Digest mismatch" do
|
|
44
47
|
exception = assert_raises(OneLogin::RubySaml::ValidationError) do
|
|
45
|
-
|
|
48
|
+
document.validate_signature(@base64cert, false)
|
|
46
49
|
end
|
|
47
50
|
assert_equal("Digest mismatch", exception.message)
|
|
48
|
-
|
|
51
|
+
assert_includes document.errors, "Digest mismatch"
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
it "should raise Key validation error" do
|
|
52
|
-
|
|
53
|
-
response.sub!("<ds:DigestValue>pJQ7MS/ek4KRRWGmv/H43ReHYMs=</ds:DigestValue>",
|
|
55
|
+
decoded_response.sub!("<ds:DigestValue>pJQ7MS/ek4KRRWGmv/H43ReHYMs=</ds:DigestValue>",
|
|
54
56
|
"<ds:DigestValue>b9xsAXLsynugg3Wc1CI3kpWku+0=</ds:DigestValue>")
|
|
55
|
-
|
|
56
|
-
base64cert =
|
|
57
|
+
mod_document = XMLSecurity::SignedDocument.new(decoded_response)
|
|
58
|
+
base64cert = mod_document.elements["//ds:X509Certificate"].text
|
|
57
59
|
exception = assert_raises(OneLogin::RubySaml::ValidationError) do
|
|
58
|
-
|
|
60
|
+
mod_document.validate_signature(base64cert, false)
|
|
59
61
|
end
|
|
60
62
|
assert_equal("Key validation error", exception.message)
|
|
61
|
-
|
|
63
|
+
assert_includes mod_document.errors, "Key validation error"
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
it "correctly obtain the digest method with alternate namespace declaration" do
|
|
65
|
-
|
|
66
|
-
base64cert =
|
|
67
|
-
assert
|
|
67
|
+
adfs_document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false))
|
|
68
|
+
base64cert = adfs_document.elements["//X509Certificate"].text
|
|
69
|
+
assert adfs_document.validate_signature(base64cert, false)
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
it "raise validation error when the X509Certificate is missing" do
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
document = XMLSecurity::SignedDocument.new(response)
|
|
73
|
+
decoded_response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
|
|
74
|
+
mod_document = XMLSecurity::SignedDocument.new(decoded_response)
|
|
74
75
|
exception = assert_raises(OneLogin::RubySaml::ValidationError) do
|
|
75
|
-
|
|
76
|
+
mod_document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test
|
|
76
77
|
end
|
|
77
78
|
assert_equal("Certificate element missing in response (ds:X509Certificate)", exception.message)
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
describe "#canon_algorithm" do
|
|
83
|
+
it "C14N_EXCLUSIVE_1_0" do
|
|
84
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
85
|
+
assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#")
|
|
86
|
+
assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#WithComments")
|
|
87
|
+
assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("other")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "C14N_1_0" do
|
|
91
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
|
|
92
|
+
assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "XML_C14N_1_1" do
|
|
96
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
|
|
97
|
+
assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "#algorithm" do
|
|
102
|
+
it "SHA1" do
|
|
103
|
+
alg = OpenSSL::Digest::SHA1
|
|
104
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1")
|
|
105
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#sha1")
|
|
106
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("other")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "SHA256" do
|
|
110
|
+
alg = OpenSSL::Digest::SHA256
|
|
111
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
|
|
112
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha256")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "SHA384" do
|
|
116
|
+
alg = OpenSSL::Digest::SHA384
|
|
117
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384")
|
|
118
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha384")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "SHA512" do
|
|
122
|
+
alg = OpenSSL::Digest::SHA512
|
|
123
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512")
|
|
124
|
+
assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha512")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
81
128
|
describe "Fingerprint Algorithms" do
|
|
82
129
|
let(:response_fingerprint_test) { OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha1, false)) }
|
|
83
130
|
|
|
@@ -117,23 +164,23 @@ class XmlSecurityTest < Minitest::Test
|
|
|
117
164
|
|
|
118
165
|
describe "Signature Algorithms" do
|
|
119
166
|
it "validate using SHA1" do
|
|
120
|
-
|
|
121
|
-
assert
|
|
167
|
+
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
|
|
168
|
+
assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
|
122
169
|
end
|
|
123
170
|
|
|
124
171
|
it "validate using SHA256" do
|
|
125
|
-
|
|
126
|
-
assert
|
|
172
|
+
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
|
|
173
|
+
assert document.validate_document("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")
|
|
127
174
|
end
|
|
128
175
|
|
|
129
176
|
it "validate using SHA384" do
|
|
130
|
-
|
|
131
|
-
assert
|
|
177
|
+
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
|
|
178
|
+
assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
|
132
179
|
end
|
|
133
180
|
|
|
134
181
|
it "validate using SHA512" do
|
|
135
|
-
|
|
136
|
-
assert
|
|
182
|
+
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
|
|
183
|
+
assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
|
137
184
|
end
|
|
138
185
|
end
|
|
139
186
|
|
|
@@ -161,12 +208,11 @@ class XmlSecurityTest < Minitest::Test
|
|
|
161
208
|
response = OneLogin::RubySaml::Response.new(fixture("tdnf_response.xml"))
|
|
162
209
|
response.stubs(:conditions).returns(nil)
|
|
163
210
|
assert !response.is_valid?
|
|
164
|
-
settings = OneLogin::RubySaml::Settings.new
|
|
165
211
|
assert !response.is_valid?
|
|
166
212
|
response.settings = settings
|
|
167
213
|
assert !response.is_valid?
|
|
168
214
|
settings.idp_cert_fingerprint = "e6 38 9a 20 b7 4f 13 db 6a bc b1 42 6a e7 52 1d d6 56 d4 1b".upcase.gsub(" ", ":")
|
|
169
|
-
assert response.
|
|
215
|
+
assert response.is_valid?
|
|
170
216
|
end
|
|
171
217
|
|
|
172
218
|
it "return an empty list when inclusive namespace element is missing" do
|
|
@@ -181,78 +227,101 @@ class XmlSecurityTest < Minitest::Test
|
|
|
181
227
|
end
|
|
182
228
|
|
|
183
229
|
describe "XMLSecurity::DSIG" do
|
|
184
|
-
|
|
185
|
-
settings =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
230
|
+
before do
|
|
231
|
+
settings.idp_sso_target_url = "https://idp.example.com/sso"
|
|
232
|
+
settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
233
|
+
settings.idp_slo_target_url = "https://idp.example.com/slo",
|
|
234
|
+
settings.issuer = "https://sp.example.com/saml2"
|
|
235
|
+
settings.assertion_consumer_service_url = "https://sp.example.com/acs"
|
|
236
|
+
settings.single_logout_service_url = "https://sp.example.com/sls"
|
|
237
|
+
end
|
|
238
|
+
|
|
191
239
|
|
|
240
|
+
it "sign an AuthNRequest" do
|
|
192
241
|
request = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
193
242
|
request.sign_document(ruby_saml_key, ruby_saml_cert)
|
|
194
|
-
|
|
195
243
|
# verify our signature
|
|
196
244
|
signed_doc = XMLSecurity::SignedDocument.new(request.to_s)
|
|
197
245
|
assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
|
|
246
|
+
|
|
247
|
+
request2 = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
248
|
+
request2.sign_document(ruby_saml_key, ruby_saml_cert_text)
|
|
249
|
+
# verify our signature
|
|
250
|
+
signed_doc2 = XMLSecurity::SignedDocument.new(request2.to_s)
|
|
251
|
+
assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
198
252
|
end
|
|
199
253
|
|
|
200
|
-
it "sign
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
:protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
204
|
-
:issuer => "https://sp.example.com/saml2",
|
|
205
|
-
:single_logout_service_url => "https://sp.example.com/sls"
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
request = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings)
|
|
209
|
-
request.sign_document(ruby_saml_key, ruby_saml_cert)
|
|
254
|
+
it "sign an AuthNRequest with certificate as text" do
|
|
255
|
+
request = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
|
|
256
|
+
request.sign_document(ruby_saml_key, ruby_saml_cert_text)
|
|
210
257
|
|
|
211
258
|
# verify our signature
|
|
212
259
|
signed_doc = XMLSecurity::SignedDocument.new(request.to_s)
|
|
213
260
|
assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
|
|
214
261
|
end
|
|
215
262
|
|
|
216
|
-
it "sign a
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
})
|
|
263
|
+
it "sign a LogoutRequest" do
|
|
264
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings)
|
|
265
|
+
logout_request.sign_document(ruby_saml_key, ruby_saml_cert)
|
|
266
|
+
# verify our signature
|
|
267
|
+
signed_doc = XMLSecurity::SignedDocument.new(logout_request.to_s)
|
|
268
|
+
assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
|
|
223
269
|
|
|
224
|
-
|
|
225
|
-
|
|
270
|
+
logout_request2 = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings)
|
|
271
|
+
logout_request2.sign_document(ruby_saml_key, ruby_saml_cert_text)
|
|
272
|
+
# verify our signature
|
|
273
|
+
signed_doc2 = XMLSecurity::SignedDocument.new(logout_request2.to_s)
|
|
274
|
+
signed_doc2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
275
|
+
assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
276
|
+
end
|
|
226
277
|
|
|
278
|
+
it "sign a LogoutResponse" do
|
|
279
|
+
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message")
|
|
280
|
+
logout_response.sign_document(ruby_saml_key, ruby_saml_cert)
|
|
227
281
|
# verify our signature
|
|
228
|
-
signed_doc = XMLSecurity::SignedDocument.new(
|
|
282
|
+
signed_doc = XMLSecurity::SignedDocument.new(logout_response.to_s)
|
|
229
283
|
assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
|
|
284
|
+
|
|
285
|
+
logout_response2 = OneLogin::RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message")
|
|
286
|
+
logout_response2.sign_document(ruby_saml_key, ruby_saml_cert_text)
|
|
287
|
+
# verify our signature
|
|
288
|
+
signed_doc2 = XMLSecurity::SignedDocument.new(logout_response2.to_s)
|
|
289
|
+
signed_doc2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
290
|
+
assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false)
|
|
230
291
|
end
|
|
231
292
|
end
|
|
232
293
|
|
|
233
294
|
describe "StarfieldTMS" do
|
|
295
|
+
let (:response) { OneLogin::RubySaml::Response.new(fixture(:starfield_response)) }
|
|
296
|
+
|
|
234
297
|
before do
|
|
235
|
-
|
|
236
|
-
@response.settings = OneLogin::RubySaml::Settings.new(
|
|
237
|
-
:idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D"
|
|
238
|
-
)
|
|
298
|
+
response.settings = OneLogin::RubySaml::Settings.new( :idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D")
|
|
239
299
|
end
|
|
240
300
|
|
|
241
301
|
it "be able to validate a good response" do
|
|
242
302
|
Timecop.freeze Time.parse('2012-11-28 17:55:00 UTC') do
|
|
243
|
-
|
|
303
|
+
response.stubs(:validate_subject_confirmation).returns(true)
|
|
304
|
+
assert response.is_valid?
|
|
244
305
|
end
|
|
245
306
|
end
|
|
246
307
|
|
|
247
308
|
it "fail before response is valid" do
|
|
248
309
|
Timecop.freeze Time.parse('2012-11-20 17:55:00 UTC') do
|
|
249
|
-
assert !
|
|
310
|
+
assert !response.is_valid?
|
|
311
|
+
|
|
312
|
+
contains_expected_error = response.errors.include? "Current time is earlier than NotBefore condition 2012-11-20 17:55:00 UTC < 2012-11-28 17:53:45 UTC)"
|
|
313
|
+
contains_expected_error ||= response.errors.include? "Current time is earlier than NotBefore condition Tue Nov 20 17:55:00 UTC 2012 < Wed Nov 28 17:53:45 UTC 2012)"
|
|
314
|
+
assert contains_expected_error
|
|
250
315
|
end
|
|
251
316
|
end
|
|
252
317
|
|
|
253
318
|
it "fail after response expires" do
|
|
254
319
|
Timecop.freeze Time.parse('2012-11-30 17:55:00 UTC') do
|
|
255
|
-
assert !
|
|
320
|
+
assert !response.is_valid?
|
|
321
|
+
|
|
322
|
+
contains_expected_error = response.errors.include? "Current time is on or after NotOnOrAfter condition (2012-11-30 17:55:00 UTC >= 2012-11-28 18:33:45 UTC)"
|
|
323
|
+
contains_expected_error ||= response.errors.include? "Current time is on or after NotOnOrAfter condition (Fri Nov 30 17:55:00 UTC 2012 >= Wed Nov 28 18:33:45 UTC 2012)"
|
|
324
|
+
assert contains_expected_error
|
|
256
325
|
end
|
|
257
326
|
end
|
|
258
327
|
end
|