ruby-saml 1.8.0 → 1.13.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/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +66 -1
- data/README.md +365 -209
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +25 -9
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
- data/lib/onelogin/ruby-saml/logging.rb +4 -1
- data/lib/onelogin/ruby-saml/logoutrequest.rb +25 -10
- data/lib/onelogin/ruby-saml/logoutresponse.rb +33 -17
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +89 -45
- data/lib/onelogin/ruby-saml/saml_message.rb +17 -8
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +124 -43
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +38 -11
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +42 -19
- data/lib/onelogin/ruby-saml/utils.rb +94 -12
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +44 -19
- data/ruby-saml.gemspec +16 -8
- metadata +44 -282
- data/.travis.yml +0 -26
- data/test/certificates/certificate1 +0 -12
- data/test/certificates/certificate_without_head_foot +0 -1
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/certificates/ruby-saml.crt +0 -14
- data/test/certificates/ruby-saml.key +0 -15
- data/test/idp_metadata_parser_test.rb +0 -579
- data/test/logging_test.rb +0 -62
- data/test/logout_requests/invalid_slo_request.xml +0 -6
- data/test/logout_requests/slo_request.xml +0 -4
- data/test/logout_requests/slo_request.xml.base64 +0 -1
- data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
- data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
- data/test/logout_requests/slo_request_with_session_index.xml +0 -5
- data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
- data/test/logoutrequest_test.rb +0 -226
- data/test/logoutresponse_test.rb +0 -402
- data/test/metadata/idp_descriptor.xml +0 -26
- data/test/metadata/idp_descriptor_2.xml +0 -56
- data/test/metadata/idp_descriptor_3.xml +0 -14
- data/test/metadata/idp_descriptor_4.xml +0 -72
- data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
- data/test/metadata/idp_metadata_multi_certs.xml +0 -75
- data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
- data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
- data/test/metadata/idp_multiple_descriptors.xml +0 -53
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -323
- data/test/response_test.rb +0 -1586
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -301
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -199
- data/test/test_helper.rb +0 -327
- data/test/utils_test.rb +0 -254
- data/test/xml_security_test.rb +0 -421
|
@@ -3,6 +3,7 @@ if RUBY_VERSION < '1.9'
|
|
|
3
3
|
else
|
|
4
4
|
require 'securerandom'
|
|
5
5
|
end
|
|
6
|
+
require "openssl"
|
|
6
7
|
|
|
7
8
|
module OneLogin
|
|
8
9
|
module RubySaml
|
|
@@ -12,8 +13,68 @@ module OneLogin
|
|
|
12
13
|
class Utils
|
|
13
14
|
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
|
17
|
+
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
|
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
|
19
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
|
|
20
|
+
DURATION_FORMAT = %r(^
|
|
21
|
+
(-?)P # 1: Duration sign
|
|
22
|
+
(?:
|
|
23
|
+
(?:(\d+)Y)? # 2: Years
|
|
24
|
+
(?:(\d+)M)? # 3: Months
|
|
25
|
+
(?:(\d+)D)? # 4: Days
|
|
26
|
+
(?:T
|
|
27
|
+
(?:(\d+)H)? # 5: Hours
|
|
28
|
+
(?:(\d+)M)? # 6: Minutes
|
|
29
|
+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
|
|
30
|
+
)?
|
|
31
|
+
|
|
|
32
|
+
(\d+)W # 8: Weeks
|
|
33
|
+
)
|
|
34
|
+
$)x.freeze
|
|
35
|
+
|
|
36
|
+
# Checks if the x509 cert provided is expired
|
|
37
|
+
#
|
|
38
|
+
# @param cert [Certificate] The x509 certificate
|
|
39
|
+
#
|
|
40
|
+
def self.is_cert_expired(cert)
|
|
41
|
+
if cert.is_a?(String)
|
|
42
|
+
cert = OpenSSL::X509::Certificate.new(cert)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
return cert.not_after < Time.now
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Interprets a ISO8601 duration value relative to a given timestamp.
|
|
49
|
+
#
|
|
50
|
+
# @param duration [String] The duration, as a string.
|
|
51
|
+
# @param timestamp [Integer] The unix timestamp we should apply the
|
|
52
|
+
# duration to. Optional, default to the
|
|
53
|
+
# current time.
|
|
54
|
+
#
|
|
55
|
+
# @return [Integer] The new timestamp, after the duration is applied.
|
|
56
|
+
#
|
|
57
|
+
def self.parse_duration(duration, timestamp=Time.now.utc)
|
|
58
|
+
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
|
|
59
|
+
|
|
60
|
+
matches = duration.match(DURATION_FORMAT)
|
|
61
|
+
|
|
62
|
+
if matches.nil?
|
|
63
|
+
raise Exception.new("Invalid ISO 8601 duration")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sign = matches[1] == '-' ? -1 : 1
|
|
67
|
+
|
|
68
|
+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
|
69
|
+
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
|
|
70
|
+
|
|
71
|
+
initial_datetime = Time.at(timestamp).utc.to_datetime
|
|
72
|
+
final_datetime = initial_datetime.next_year(durYears)
|
|
73
|
+
final_datetime = final_datetime.next_month(durMonths)
|
|
74
|
+
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
|
|
75
|
+
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
|
76
|
+
return final_timestamp
|
|
77
|
+
end
|
|
17
78
|
|
|
18
79
|
# Return a properly formatted x509 certificate
|
|
19
80
|
#
|
|
@@ -22,7 +83,11 @@ module OneLogin
|
|
|
22
83
|
#
|
|
23
84
|
def self.format_cert(cert)
|
|
24
85
|
# don't try to format an encoded certificate or if is empty or nil
|
|
25
|
-
|
|
86
|
+
if cert.respond_to?(:ascii_only?)
|
|
87
|
+
return cert if cert.nil? || cert.empty? || !cert.ascii_only?
|
|
88
|
+
else
|
|
89
|
+
return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
|
|
90
|
+
end
|
|
26
91
|
|
|
27
92
|
if cert.scan(/BEGIN CERTIFICATE/).length > 1
|
|
28
93
|
formatted_cert = []
|
|
@@ -32,7 +97,9 @@ module OneLogin
|
|
|
32
97
|
formatted_cert.join("\n")
|
|
33
98
|
else
|
|
34
99
|
cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
|
|
35
|
-
cert = cert.gsub(
|
|
100
|
+
cert = cert.gsub(/\r/, "")
|
|
101
|
+
cert = cert.gsub(/\n/, "")
|
|
102
|
+
cert = cert.gsub(/\s/, "")
|
|
36
103
|
cert = cert.scan(/.{1,64}/)
|
|
37
104
|
cert = cert.join("\n")
|
|
38
105
|
"-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
|
|
@@ -51,7 +118,9 @@ module OneLogin
|
|
|
51
118
|
# is this an rsa key?
|
|
52
119
|
rsa_key = key.match("RSA PRIVATE KEY")
|
|
53
120
|
key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
|
|
54
|
-
key = key.gsub(
|
|
121
|
+
key = key.gsub(/\n/, "")
|
|
122
|
+
key = key.gsub(/\r/, "")
|
|
123
|
+
key = key.gsub(/\s/, "")
|
|
55
124
|
key = key.scan(/.{1,64}/)
|
|
56
125
|
key = key.join("\n")
|
|
57
126
|
key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
|
|
@@ -98,7 +167,7 @@ module OneLogin
|
|
|
98
167
|
# @param rawparams [Hash] Raw GET Parameters
|
|
99
168
|
# @param params [Hash] GET Parameters
|
|
100
169
|
# @return [Hash] New raw parameters
|
|
101
|
-
#
|
|
170
|
+
#
|
|
102
171
|
def self.prepare_raw_get_params(rawparams, params)
|
|
103
172
|
rawparams ||= {}
|
|
104
173
|
|
|
@@ -107,7 +176,7 @@ module OneLogin
|
|
|
107
176
|
end
|
|
108
177
|
if rawparams['SAMLResponse'].nil? && !params['SAMLResponse'].nil?
|
|
109
178
|
rawparams['SAMLResponse'] = CGI.escape(params['SAMLResponse'])
|
|
110
|
-
end
|
|
179
|
+
end
|
|
111
180
|
if rawparams['RelayState'].nil? && !params['RelayState'].nil?
|
|
112
181
|
rawparams['RelayState'] = CGI.escape(params['RelayState'])
|
|
113
182
|
end
|
|
@@ -136,16 +205,16 @@ module OneLogin
|
|
|
136
205
|
# @param status_code [String] StatusCode value
|
|
137
206
|
# @param status_message [Strig] StatusMessage value
|
|
138
207
|
# @return [String] The status error message
|
|
139
|
-
def self.status_error_msg(error_msg,
|
|
140
|
-
unless
|
|
141
|
-
if
|
|
142
|
-
status_codes =
|
|
208
|
+
def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil)
|
|
209
|
+
unless raw_status_code.nil?
|
|
210
|
+
if raw_status_code.include? "|"
|
|
211
|
+
status_codes = raw_status_code.split(' | ')
|
|
143
212
|
values = status_codes.collect do |status_code|
|
|
144
213
|
status_code.split(':').last
|
|
145
214
|
end
|
|
146
215
|
printable_code = values.join(" => ")
|
|
147
216
|
else
|
|
148
|
-
printable_code =
|
|
217
|
+
printable_code = raw_status_code.split(':').last
|
|
149
218
|
end
|
|
150
219
|
error_msg << ', was ' + printable_code
|
|
151
220
|
end
|
|
@@ -232,6 +301,9 @@ module OneLogin
|
|
|
232
301
|
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
|
233
302
|
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
|
234
303
|
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
|
304
|
+
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
|
|
305
|
+
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
|
|
306
|
+
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
|
|
235
307
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
|
236
308
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
|
237
309
|
end
|
|
@@ -242,6 +314,16 @@ module OneLogin
|
|
|
242
314
|
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
|
243
315
|
assertion_plaintext = cipher.update(data)
|
|
244
316
|
assertion_plaintext << cipher.final
|
|
317
|
+
elsif auth_cipher
|
|
318
|
+
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
|
|
319
|
+
data = cipher_text[iv_len..text_len-1-tag_len]
|
|
320
|
+
auth_cipher.padding = 0
|
|
321
|
+
auth_cipher.key = symmetric_key
|
|
322
|
+
auth_cipher.iv = cipher_text[0..iv_len-1]
|
|
323
|
+
auth_cipher.auth_data = ''
|
|
324
|
+
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
|
|
325
|
+
assertion_plaintext = auth_cipher.update(data)
|
|
326
|
+
assertion_plaintext << auth_cipher.final
|
|
245
327
|
elsif rsa
|
|
246
328
|
rsa.private_decrypt(cipher_text)
|
|
247
329
|
elsif oaep
|
data/lib/xml_security.rb
CHANGED
|
@@ -91,7 +91,7 @@ module XMLSecurity
|
|
|
91
91
|
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
|
92
92
|
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
attr_writer :uuid
|
|
95
95
|
|
|
96
96
|
def uuid
|
|
97
97
|
@uuid ||= begin
|
|
@@ -159,15 +159,13 @@ module XMLSecurity
|
|
|
159
159
|
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
|
|
160
160
|
|
|
161
161
|
# add the signature
|
|
162
|
-
issuer_element =
|
|
162
|
+
issuer_element = elements["//saml:Issuer"]
|
|
163
163
|
if issuer_element
|
|
164
|
-
|
|
164
|
+
root.insert_after(issuer_element, signature_element)
|
|
165
|
+
elsif first_child = root.children[0]
|
|
166
|
+
root.insert_before(first_child, signature_element)
|
|
165
167
|
else
|
|
166
|
-
|
|
167
|
-
self.root.insert_before sp_sso_descriptor, signature_element
|
|
168
|
-
else
|
|
169
|
-
self.root.add_element(signature_element)
|
|
170
|
-
end
|
|
168
|
+
root.add_element(signature_element)
|
|
171
169
|
end
|
|
172
170
|
end
|
|
173
171
|
|
|
@@ -187,7 +185,7 @@ module XMLSecurity
|
|
|
187
185
|
class SignedDocument < BaseDocument
|
|
188
186
|
include OneLogin::RubySaml::ErrorHandling
|
|
189
187
|
|
|
190
|
-
|
|
188
|
+
attr_writer :signed_element_id
|
|
191
189
|
|
|
192
190
|
def initialize(response, errors = [])
|
|
193
191
|
super(response)
|
|
@@ -211,8 +209,8 @@ module XMLSecurity
|
|
|
211
209
|
cert_text = Base64.decode64(base64_cert)
|
|
212
210
|
begin
|
|
213
211
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
|
214
|
-
rescue OpenSSL::X509::CertificateError =>
|
|
215
|
-
return append_error("Certificate Error", soft)
|
|
212
|
+
rescue OpenSSL::X509::CertificateError => _e
|
|
213
|
+
return append_error("Document Certificate Error", soft)
|
|
216
214
|
end
|
|
217
215
|
|
|
218
216
|
if options[:fingerprint_alg]
|
|
@@ -224,7 +222,6 @@ module XMLSecurity
|
|
|
224
222
|
|
|
225
223
|
# check cert matches registered idp cert
|
|
226
224
|
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
|
227
|
-
@errors << "Fingerprint mismatch"
|
|
228
225
|
return append_error("Fingerprint mismatch", soft)
|
|
229
226
|
end
|
|
230
227
|
else
|
|
@@ -241,7 +238,7 @@ module XMLSecurity
|
|
|
241
238
|
validate_signature(base64_cert, soft)
|
|
242
239
|
end
|
|
243
240
|
|
|
244
|
-
def validate_document_with_cert(idp_cert)
|
|
241
|
+
def validate_document_with_cert(idp_cert, soft = true)
|
|
245
242
|
# get cert from response
|
|
246
243
|
cert_element = REXML::XPath.first(
|
|
247
244
|
self,
|
|
@@ -254,13 +251,13 @@ module XMLSecurity
|
|
|
254
251
|
cert_text = Base64.decode64(base64_cert)
|
|
255
252
|
begin
|
|
256
253
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
|
257
|
-
rescue OpenSSL::X509::CertificateError =>
|
|
258
|
-
return append_error("Certificate Error", soft)
|
|
254
|
+
rescue OpenSSL::X509::CertificateError => _e
|
|
255
|
+
return append_error("Document Certificate Error", soft)
|
|
259
256
|
end
|
|
260
257
|
|
|
261
258
|
# check saml response cert matches provided idp cert
|
|
262
259
|
if idp_cert.to_pem != cert.to_pem
|
|
263
|
-
return
|
|
260
|
+
return append_error("Certificate of the Signature element does not match provided certificate", soft)
|
|
264
261
|
end
|
|
265
262
|
else
|
|
266
263
|
base64_cert = Base64.encode64(idp_cert.to_pem)
|
|
@@ -318,15 +315,17 @@ module XMLSecurity
|
|
|
318
315
|
|
|
319
316
|
# check digests
|
|
320
317
|
ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
|
|
321
|
-
uri = ref.attributes.get_attribute("URI").value
|
|
322
318
|
|
|
323
319
|
hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
|
|
324
|
-
|
|
320
|
+
|
|
325
321
|
canon_algorithm = canon_algorithm REXML::XPath.first(
|
|
326
322
|
ref,
|
|
327
323
|
'//ds:CanonicalizationMethod',
|
|
328
324
|
{ "ds" => DSIG }
|
|
329
325
|
)
|
|
326
|
+
|
|
327
|
+
canon_algorithm = process_transforms(ref, canon_algorithm)
|
|
328
|
+
|
|
330
329
|
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
|
|
331
330
|
|
|
332
331
|
digest_algorithm = algorithm(REXML::XPath.first(
|
|
@@ -343,7 +342,6 @@ module XMLSecurity
|
|
|
343
342
|
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
|
|
344
343
|
|
|
345
344
|
unless digests_match?(hash, digest_value)
|
|
346
|
-
@errors << "Digest mismatch"
|
|
347
345
|
return append_error("Digest mismatch", soft)
|
|
348
346
|
end
|
|
349
347
|
|
|
@@ -361,6 +359,33 @@ module XMLSecurity
|
|
|
361
359
|
|
|
362
360
|
private
|
|
363
361
|
|
|
362
|
+
def process_transforms(ref, canon_algorithm)
|
|
363
|
+
transforms = REXML::XPath.match(
|
|
364
|
+
ref,
|
|
365
|
+
"//ds:Transforms/ds:Transform",
|
|
366
|
+
{ "ds" => DSIG }
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
transforms.each do |transform_element|
|
|
370
|
+
if transform_element.attributes && transform_element.attributes["Algorithm"]
|
|
371
|
+
algorithm = transform_element.attributes["Algorithm"]
|
|
372
|
+
case algorithm
|
|
373
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
|
374
|
+
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
|
375
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
|
|
376
|
+
when "http://www.w3.org/2006/12/xml-c14n11",
|
|
377
|
+
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
|
378
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
|
|
379
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n#",
|
|
380
|
+
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
|
|
381
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
canon_algorithm
|
|
387
|
+
end
|
|
388
|
+
|
|
364
389
|
def digests_match?(hash, digest_value)
|
|
365
390
|
hash == digest_value
|
|
366
391
|
end
|
data/ruby-saml.gemspec
CHANGED
|
@@ -15,36 +15,44 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
"LICENSE",
|
|
16
16
|
"README.md"
|
|
17
17
|
]
|
|
18
|
-
s.files = `git ls-files`.split("\
|
|
19
|
-
s.homepage = %q{
|
|
20
|
-
s.rubyforge_project = %q{http://www.rubygems.org/gems/ruby-saml}
|
|
18
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
+
s.homepage = %q{https://github.com/onelogin/ruby-saml}
|
|
21
20
|
s.rdoc_options = ["--charset=UTF-8"]
|
|
22
21
|
s.require_paths = ["lib"]
|
|
23
22
|
s.rubygems_version = %q{1.3.7}
|
|
24
23
|
s.required_ruby_version = '>= 1.8.7'
|
|
25
24
|
s.summary = %q{SAML Ruby Tookit}
|
|
26
|
-
s.test_files = `git ls-files test/*`.split("\n")
|
|
27
25
|
|
|
28
26
|
# Because runtime dependencies are determined at build time, we cannot make
|
|
29
27
|
# Nokogiri's version dependent on the Ruby version, even though we would
|
|
30
28
|
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
|
|
31
29
|
if defined?(JRUBY_VERSION)
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
if JRUBY_VERSION < '9.2.0.0'
|
|
31
|
+
s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5')
|
|
32
|
+
s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
|
|
33
|
+
s.add_runtime_dependency('json', '< 2.3.0')
|
|
34
|
+
else
|
|
35
|
+
s.add_runtime_dependency('nokogiri', '>= 1.8.2')
|
|
36
|
+
end
|
|
34
37
|
elsif RUBY_VERSION < '1.9'
|
|
35
38
|
s.add_runtime_dependency('uuid')
|
|
36
39
|
s.add_runtime_dependency('nokogiri', '<= 1.5.11')
|
|
37
40
|
elsif RUBY_VERSION < '2.1'
|
|
38
41
|
s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1')
|
|
42
|
+
s.add_runtime_dependency('json', '< 2.3.0')
|
|
43
|
+
elsif RUBY_VERSION < '2.3'
|
|
44
|
+
s.add_runtime_dependency('nokogiri', '>= 1.9.1', '<= 1.10.0')
|
|
39
45
|
else
|
|
40
|
-
s.add_runtime_dependency('nokogiri', '>= 1.5
|
|
46
|
+
s.add_runtime_dependency('nokogiri', '>= 1.10.5')
|
|
47
|
+
s.add_runtime_dependency('rexml')
|
|
41
48
|
end
|
|
42
49
|
|
|
50
|
+
s.add_development_dependency('coveralls')
|
|
43
51
|
s.add_development_dependency('minitest', '~> 5.5')
|
|
44
52
|
s.add_development_dependency('mocha', '~> 0.14')
|
|
45
53
|
s.add_development_dependency('rake', '~> 10')
|
|
46
54
|
s.add_development_dependency('shoulda', '~> 2.11')
|
|
47
|
-
s.add_development_dependency('simplecov'
|
|
55
|
+
s.add_development_dependency('simplecov')
|
|
48
56
|
s.add_development_dependency('systemu', '~> 2')
|
|
49
57
|
s.add_development_dependency('timecop', '<= 0.6.0')
|
|
50
58
|
|