ruby-saml 1.10.1 → 1.12.2
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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +7 -7
- data/.travis.yml +21 -20
- data/README.md +95 -26
- data/changelog.md +40 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +11 -6
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +70 -24
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +11 -5
- data/lib/onelogin/ruby-saml/logoutresponse.rb +21 -2
- data/lib/onelogin/ruby-saml/metadata.rb +11 -3
- data/lib/onelogin/ruby-saml/response.rb +64 -23
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -0
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +72 -7
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +20 -1
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
- data/lib/onelogin/ruby-saml/utils.rb +69 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +34 -6
- data/ruby-saml.gemspec +9 -5
- metadata +161 -383
- data/test/certificates/certificate.der +0 -0
- 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 -587
- 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 -86
- data/test/logoutrequest_test.rb +0 -260
- data/test/logoutresponse_test.rb +0 -409
- 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 -59
- data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -340
- data/test/response_test.rb +0 -1620
- 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_with_formatted_x509certificate.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 -329
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -331
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
@@ -22,6 +22,8 @@ module OneLogin
|
|
22
22
|
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
|
23
23
|
@@mutex = Mutex.new
|
24
24
|
|
25
|
+
MAX_BYTE_SIZE = 250000
|
26
|
+
|
25
27
|
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
|
26
28
|
#
|
27
29
|
def self.schema
|
@@ -89,6 +91,10 @@ module OneLogin
|
|
89
91
|
def decode_raw_saml(saml)
|
90
92
|
return saml unless base64_encoded?(saml)
|
91
93
|
|
94
|
+
if saml.bytesize > MAX_BYTE_SIZE
|
95
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
|
96
|
+
end
|
97
|
+
|
92
98
|
decoded = decode(saml)
|
93
99
|
begin
|
94
100
|
inflate(decoded)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "xml_security"
|
2
2
|
require "onelogin/ruby-saml/attribute_service"
|
3
3
|
require "onelogin/ruby-saml/utils"
|
4
|
+
require "onelogin/ruby-saml/validation_error"
|
4
5
|
|
5
6
|
# Only supports SAML 2.0
|
6
7
|
module OneLogin
|
@@ -30,16 +31,18 @@ module OneLogin
|
|
30
31
|
|
31
32
|
# IdP Data
|
32
33
|
attr_accessor :idp_entity_id
|
33
|
-
|
34
|
-
attr_accessor :
|
34
|
+
|
35
|
+
attr_accessor :idp_sso_service_url
|
36
|
+
attr_accessor :idp_slo_service_url
|
37
|
+
attr_accessor :idp_slo_response_service_url
|
35
38
|
attr_accessor :idp_cert
|
36
39
|
attr_accessor :idp_cert_fingerprint
|
37
40
|
attr_accessor :idp_cert_fingerprint_algorithm
|
38
41
|
attr_accessor :idp_cert_multi
|
39
42
|
attr_accessor :idp_attribute_names
|
40
43
|
attr_accessor :idp_name_qualifier
|
44
|
+
attr_accessor :valid_until
|
41
45
|
# SP Data
|
42
|
-
attr_accessor :issuer
|
43
46
|
attr_accessor :assertion_consumer_service_url
|
44
47
|
attr_accessor :assertion_consumer_service_binding
|
45
48
|
attr_accessor :sp_name_qualifier
|
@@ -67,6 +70,58 @@ module OneLogin
|
|
67
70
|
# Compability
|
68
71
|
attr_accessor :assertion_consumer_logout_service_url
|
69
72
|
attr_accessor :assertion_consumer_logout_service_binding
|
73
|
+
attr_accessor :issuer
|
74
|
+
attr_accessor :idp_sso_target_url
|
75
|
+
attr_accessor :idp_slo_target_url
|
76
|
+
|
77
|
+
# @return [String] IdP Single Sign On Service URL
|
78
|
+
#
|
79
|
+
def idp_sso_service_url
|
80
|
+
val = nil
|
81
|
+
if @idp_sso_service_url.nil?
|
82
|
+
if @idp_sso_target_url
|
83
|
+
val = @idp_sso_target_url
|
84
|
+
end
|
85
|
+
else
|
86
|
+
val = @idp_sso_service_url
|
87
|
+
end
|
88
|
+
val
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] IdP Single Logout Service URL
|
92
|
+
#
|
93
|
+
def idp_slo_service_url
|
94
|
+
val = nil
|
95
|
+
if @idp_slo_service_url.nil?
|
96
|
+
if @idp_slo_target_url
|
97
|
+
val = @idp_slo_target_url
|
98
|
+
end
|
99
|
+
else
|
100
|
+
val = @idp_slo_service_url
|
101
|
+
end
|
102
|
+
val
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [String] SP Entity ID
|
106
|
+
#
|
107
|
+
def sp_entity_id
|
108
|
+
val = nil
|
109
|
+
if @sp_entity_id.nil?
|
110
|
+
if @issuer
|
111
|
+
val = @issuer
|
112
|
+
end
|
113
|
+
else
|
114
|
+
val = @sp_entity_id
|
115
|
+
end
|
116
|
+
val
|
117
|
+
end
|
118
|
+
|
119
|
+
# Setter for SP Entity ID.
|
120
|
+
# @param val [String].
|
121
|
+
#
|
122
|
+
def sp_entity_id=(val)
|
123
|
+
@sp_entity_id = val
|
124
|
+
end
|
70
125
|
|
71
126
|
# @return [String] Single Logout Service URL.
|
72
127
|
#
|
@@ -166,7 +221,15 @@ module OneLogin
|
|
166
221
|
return nil if certificate.nil? || certificate.empty?
|
167
222
|
|
168
223
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
169
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
224
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
225
|
+
|
226
|
+
if security[:check_sp_cert_expiration]
|
227
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
228
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
cert
|
170
233
|
end
|
171
234
|
|
172
235
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
@@ -196,6 +259,7 @@ module OneLogin
|
|
196
259
|
:compress_request => true,
|
197
260
|
:compress_response => true,
|
198
261
|
:soft => true,
|
262
|
+
:double_quote_xml_attribute_values => false,
|
199
263
|
:security => {
|
200
264
|
:authn_requests_signed => false,
|
201
265
|
:logout_requests_signed => false,
|
@@ -206,9 +270,10 @@ module OneLogin
|
|
206
270
|
:metadata_signed => false,
|
207
271
|
:embed_sign => false,
|
208
272
|
:digest_method => XMLSecurity::Document::SHA1,
|
209
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
210
|
-
|
211
|
-
|
273
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
274
|
+
:check_idp_cert_expiration => false,
|
275
|
+
:check_sp_cert_expiration => false
|
276
|
+
}.freeze
|
212
277
|
}.freeze
|
213
278
|
end
|
214
279
|
end
|
@@ -47,6 +47,10 @@ module OneLogin
|
|
47
47
|
@document = REXML::Document.new(@request)
|
48
48
|
end
|
49
49
|
|
50
|
+
def request_id
|
51
|
+
id(document)
|
52
|
+
end
|
53
|
+
|
50
54
|
# Validates the Logout Request with the default values (soft = true)
|
51
55
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
|
52
56
|
# @return [Boolean] TRUE if the Logout Request is valid
|
@@ -280,13 +284,19 @@ module OneLogin
|
|
280
284
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
281
285
|
)
|
282
286
|
|
287
|
+
expired = false
|
283
288
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
284
289
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
285
|
-
:cert =>
|
290
|
+
:cert => idp_cert,
|
286
291
|
:sig_alg => options[:get_params]['SigAlg'],
|
287
292
|
:signature => options[:get_params]['Signature'],
|
288
293
|
:query_string => query_string
|
289
294
|
)
|
295
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
296
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
297
|
+
expired = true
|
298
|
+
end
|
299
|
+
end
|
290
300
|
else
|
291
301
|
valid = false
|
292
302
|
idp_certs[:signing].each do |signing_idp_cert|
|
@@ -297,11 +307,20 @@ module OneLogin
|
|
297
307
|
:query_string => query_string
|
298
308
|
)
|
299
309
|
if valid
|
310
|
+
if settings.security[:check_idp_cert_expiration]
|
311
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
312
|
+
expired = true
|
313
|
+
end
|
314
|
+
end
|
300
315
|
break
|
301
316
|
end
|
302
317
|
end
|
303
318
|
end
|
304
319
|
|
320
|
+
if expired
|
321
|
+
error_msg = "IdP x509 certificate expired"
|
322
|
+
return append_error(error_msg)
|
323
|
+
end
|
305
324
|
unless valid
|
306
325
|
return append_error("Invalid Signature on Logout Request")
|
307
326
|
end
|
@@ -2,6 +2,7 @@ require "onelogin/ruby-saml/logging"
|
|
2
2
|
|
3
3
|
require "onelogin/ruby-saml/saml_message"
|
4
4
|
require "onelogin/ruby-saml/utils"
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
5
6
|
|
6
7
|
# Only supports SAML 2.0
|
7
8
|
module OneLogin
|
@@ -21,23 +22,30 @@ module OneLogin
|
|
21
22
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
22
23
|
end
|
23
24
|
|
25
|
+
def response_id
|
26
|
+
@uuid
|
27
|
+
end
|
28
|
+
|
24
29
|
# Creates the Logout Response string.
|
25
30
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
26
31
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
27
32
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
28
33
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
34
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
29
35
|
# @return [String] Logout Request string that includes the SAMLRequest
|
30
36
|
#
|
31
|
-
def create(settings, request_id = nil, logout_message = nil, params = {})
|
32
|
-
params = create_params(settings, request_id, logout_message, params)
|
33
|
-
params_prefix = (settings.
|
37
|
+
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
38
|
+
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
39
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
34
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
35
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
36
43
|
params.each_pair do |key, value|
|
37
44
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
38
45
|
end
|
39
46
|
|
40
|
-
|
47
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
48
|
+
@logout_url = url + response_params
|
41
49
|
end
|
42
50
|
|
43
51
|
# Creates the Get parameters for the logout response.
|
@@ -45,9 +53,10 @@ module OneLogin
|
|
45
53
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
46
54
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
47
55
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
56
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
48
57
|
# @return [Hash] Parameters
|
49
58
|
#
|
50
|
-
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
59
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
51
60
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
52
61
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
53
62
|
# conflicts so this line will solve them.
|
@@ -58,7 +67,7 @@ module OneLogin
|
|
58
67
|
params.delete('RelayState')
|
59
68
|
end
|
60
69
|
|
61
|
-
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
70
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
62
71
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
63
72
|
|
64
73
|
response = ""
|
@@ -94,39 +103,44 @@ module OneLogin
|
|
94
103
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
95
104
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
96
105
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
106
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
97
107
|
# @return [String] The SAMLResponse String.
|
98
108
|
#
|
99
|
-
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
100
|
-
document = create_xml_document(settings, request_id, logout_message)
|
109
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
|
110
|
+
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
|
101
111
|
sign_document(document, settings)
|
102
112
|
end
|
103
113
|
|
104
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
105
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
106
116
|
|
107
117
|
response_doc = XMLSecurity::Document.new
|
108
118
|
response_doc.uuid = uuid
|
109
119
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
121
|
+
|
122
|
+
|
110
123
|
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
111
124
|
root.attributes['ID'] = uuid
|
112
125
|
root.attributes['IssueInstant'] = time
|
113
126
|
root.attributes['Version'] = '2.0'
|
114
127
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
115
|
-
root.attributes['Destination'] =
|
128
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
116
129
|
|
117
|
-
if settings.
|
130
|
+
if settings.sp_entity_id != nil
|
118
131
|
issuer = root.add_element "saml:Issuer"
|
119
|
-
issuer.text = settings.
|
132
|
+
issuer.text = settings.sp_entity_id
|
120
133
|
end
|
121
134
|
|
122
|
-
# add
|
135
|
+
# add status
|
123
136
|
status = root.add_element 'samlp:Status'
|
124
137
|
|
125
|
-
#
|
126
|
-
status_code
|
127
|
-
|
138
|
+
# status code
|
139
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
140
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
141
|
+
status_code_elem.attributes['Value'] = status_code
|
128
142
|
|
129
|
-
#
|
143
|
+
# status message
|
130
144
|
logout_message ||= 'Successfully Signed Out'
|
131
145
|
status_message = status.add_element 'samlp:StatusMessage'
|
132
146
|
status_message.text = logout_message
|
@@ -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
|
@@ -14,6 +15,61 @@ module OneLogin
|
|
14
15
|
|
15
16
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
16
17
|
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
18
|
+
DURATION_FORMAT = %r(^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$)
|
19
|
+
|
20
|
+
# Checks if the x509 cert provided is expired
|
21
|
+
#
|
22
|
+
# @param cert [Certificate] The x509 certificate
|
23
|
+
#
|
24
|
+
def self.is_cert_expired(cert)
|
25
|
+
if cert.is_a?(String)
|
26
|
+
cert = OpenSSL::X509::Certificate.new(cert)
|
27
|
+
end
|
28
|
+
|
29
|
+
return cert.not_after < Time.now
|
30
|
+
end
|
31
|
+
|
32
|
+
# Interprets a ISO8601 duration value relative to a given timestamp.
|
33
|
+
#
|
34
|
+
# @param duration [String] The duration, as a string.
|
35
|
+
# @param timestamp [Integer] The unix timestamp we should apply the
|
36
|
+
# duration to. Optional, default to the
|
37
|
+
# current time.
|
38
|
+
#
|
39
|
+
# @return [Integer] The new timestamp, after the duration is applied.
|
40
|
+
#
|
41
|
+
def self.parse_duration(duration, timestamp=Time.now.utc)
|
42
|
+
matches = duration.match(DURATION_FORMAT)
|
43
|
+
|
44
|
+
if matches.nil?
|
45
|
+
raise Exception.new("Invalid ISO 8601 duration")
|
46
|
+
end
|
47
|
+
|
48
|
+
durYears = matches[2].to_i
|
49
|
+
durMonths = matches[3].to_i
|
50
|
+
durDays = matches[4].to_i
|
51
|
+
durHours = matches[5].to_i
|
52
|
+
durMinutes = matches[6].to_i
|
53
|
+
durSeconds = matches[7].to_f
|
54
|
+
durWeeks = matches[8].to_i
|
55
|
+
|
56
|
+
if matches[1] == "-"
|
57
|
+
durYears = -durYears
|
58
|
+
durMonths = -durMonths
|
59
|
+
durDays = -durDays
|
60
|
+
durHours = -durHours
|
61
|
+
durMinutes = -durMinutes
|
62
|
+
durSeconds = -durSeconds
|
63
|
+
durWeeks = -durWeeks
|
64
|
+
end
|
65
|
+
|
66
|
+
initial_datetime = Time.at(timestamp).utc.to_datetime
|
67
|
+
final_datetime = initial_datetime.next_year(durYears)
|
68
|
+
final_datetime = final_datetime.next_month(durMonths)
|
69
|
+
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
|
70
|
+
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
71
|
+
return final_timestamp
|
72
|
+
end
|
17
73
|
|
18
74
|
# Return a properly formatted x509 certificate
|
19
75
|
#
|
@@ -240,6 +296,9 @@ module OneLogin
|
|
240
296
|
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
241
297
|
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
242
298
|
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
299
|
+
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
|
300
|
+
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
|
301
|
+
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
|
243
302
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
244
303
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
245
304
|
end
|
@@ -250,6 +309,16 @@ module OneLogin
|
|
250
309
|
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
251
310
|
assertion_plaintext = cipher.update(data)
|
252
311
|
assertion_plaintext << cipher.final
|
312
|
+
elsif auth_cipher
|
313
|
+
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
|
314
|
+
data = cipher_text[iv_len..text_len-1-tag_len]
|
315
|
+
auth_cipher.padding = 0
|
316
|
+
auth_cipher.key = symmetric_key
|
317
|
+
auth_cipher.iv = cipher_text[0..iv_len-1]
|
318
|
+
auth_cipher.auth_data = ''
|
319
|
+
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
|
320
|
+
assertion_plaintext = auth_cipher.update(data)
|
321
|
+
assertion_plaintext << auth_cipher.final
|
253
322
|
elsif rsa
|
254
323
|
rsa.private_decrypt(cipher_text)
|
255
324
|
elsif oaep
|
data/lib/xml_security.rb
CHANGED
@@ -212,7 +212,7 @@ module XMLSecurity
|
|
212
212
|
begin
|
213
213
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
214
214
|
rescue OpenSSL::X509::CertificateError => _e
|
215
|
-
return append_error("Certificate Error", soft)
|
215
|
+
return append_error("Document Certificate Error", soft)
|
216
216
|
end
|
217
217
|
|
218
218
|
if options[:fingerprint_alg]
|
@@ -224,7 +224,6 @@ module XMLSecurity
|
|
224
224
|
|
225
225
|
# check cert matches registered idp cert
|
226
226
|
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
227
|
-
@errors << "Fingerprint mismatch"
|
228
227
|
return append_error("Fingerprint mismatch", soft)
|
229
228
|
end
|
230
229
|
else
|
@@ -241,7 +240,7 @@ module XMLSecurity
|
|
241
240
|
validate_signature(base64_cert, soft)
|
242
241
|
end
|
243
242
|
|
244
|
-
def validate_document_with_cert(idp_cert)
|
243
|
+
def validate_document_with_cert(idp_cert, soft = true)
|
245
244
|
# get cert from response
|
246
245
|
cert_element = REXML::XPath.first(
|
247
246
|
self,
|
@@ -255,12 +254,12 @@ module XMLSecurity
|
|
255
254
|
begin
|
256
255
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
257
256
|
rescue OpenSSL::X509::CertificateError => _e
|
258
|
-
return append_error("Certificate Error", soft)
|
257
|
+
return append_error("Document Certificate Error", soft)
|
259
258
|
end
|
260
259
|
|
261
260
|
# check saml response cert matches provided idp cert
|
262
261
|
if idp_cert.to_pem != cert.to_pem
|
263
|
-
return
|
262
|
+
return append_error("Certificate of the Signature element does not match provided certificate", soft)
|
264
263
|
end
|
265
264
|
else
|
266
265
|
base64_cert = Base64.encode64(idp_cert.to_pem)
|
@@ -326,6 +325,9 @@ module XMLSecurity
|
|
326
325
|
'//ds:CanonicalizationMethod',
|
327
326
|
{ "ds" => DSIG }
|
328
327
|
)
|
328
|
+
|
329
|
+
canon_algorithm = process_transforms(ref, canon_algorithm)
|
330
|
+
|
329
331
|
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
|
330
332
|
|
331
333
|
digest_algorithm = algorithm(REXML::XPath.first(
|
@@ -342,7 +344,6 @@ module XMLSecurity
|
|
342
344
|
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
|
343
345
|
|
344
346
|
unless digests_match?(hash, digest_value)
|
345
|
-
@errors << "Digest mismatch"
|
346
347
|
return append_error("Digest mismatch", soft)
|
347
348
|
end
|
348
349
|
|
@@ -360,6 +361,33 @@ module XMLSecurity
|
|
360
361
|
|
361
362
|
private
|
362
363
|
|
364
|
+
def process_transforms(ref, canon_algorithm)
|
365
|
+
transforms = REXML::XPath.match(
|
366
|
+
ref,
|
367
|
+
"//ds:Transforms/ds:Transform",
|
368
|
+
{ "ds" => DSIG }
|
369
|
+
)
|
370
|
+
|
371
|
+
transforms.each do |transform_element|
|
372
|
+
if transform_element.attributes && transform_element.attributes["Algorithm"]
|
373
|
+
algorithm = transform_element.attributes["Algorithm"]
|
374
|
+
case algorithm
|
375
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
376
|
+
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
377
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
|
378
|
+
when "http://www.w3.org/2006/12/xml-c14n11",
|
379
|
+
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
380
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
|
381
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n#",
|
382
|
+
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
|
383
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
canon_algorithm
|
389
|
+
end
|
390
|
+
|
363
391
|
def digests_match?(hash, digest_value)
|
364
392
|
hash == digest_value
|
365
393
|
end
|