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.

Files changed (159) hide show
  1. checksums.yaml +7 -7
  2. data/.travis.yml +21 -20
  3. data/README.md +95 -26
  4. data/changelog.md +40 -0
  5. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  6. data/lib/onelogin/ruby-saml/authrequest.rb +11 -6
  7. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +70 -24
  8. data/lib/onelogin/ruby-saml/logging.rb +3 -3
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +11 -5
  10. data/lib/onelogin/ruby-saml/logoutresponse.rb +21 -2
  11. data/lib/onelogin/ruby-saml/metadata.rb +11 -3
  12. data/lib/onelogin/ruby-saml/response.rb +64 -23
  13. data/lib/onelogin/ruby-saml/saml_message.rb +6 -0
  14. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  15. data/lib/onelogin/ruby-saml/settings.rb +72 -7
  16. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +20 -1
  17. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
  18. data/lib/onelogin/ruby-saml/utils.rb +69 -0
  19. data/lib/onelogin/ruby-saml/version.rb +1 -1
  20. data/lib/xml_security.rb +34 -6
  21. data/ruby-saml.gemspec +9 -5
  22. metadata +161 -383
  23. data/test/certificates/certificate.der +0 -0
  24. data/test/certificates/certificate1 +0 -12
  25. data/test/certificates/certificate_without_head_foot +0 -1
  26. data/test/certificates/formatted_certificate +0 -14
  27. data/test/certificates/formatted_chained_certificate +0 -42
  28. data/test/certificates/formatted_private_key +0 -12
  29. data/test/certificates/formatted_rsa_private_key +0 -12
  30. data/test/certificates/invalid_certificate1 +0 -1
  31. data/test/certificates/invalid_certificate2 +0 -1
  32. data/test/certificates/invalid_certificate3 +0 -12
  33. data/test/certificates/invalid_chained_certificate1 +0 -1
  34. data/test/certificates/invalid_private_key1 +0 -1
  35. data/test/certificates/invalid_private_key2 +0 -1
  36. data/test/certificates/invalid_private_key3 +0 -10
  37. data/test/certificates/invalid_rsa_private_key1 +0 -1
  38. data/test/certificates/invalid_rsa_private_key2 +0 -1
  39. data/test/certificates/invalid_rsa_private_key3 +0 -10
  40. data/test/certificates/ruby-saml-2.crt +0 -15
  41. data/test/certificates/ruby-saml.crt +0 -14
  42. data/test/certificates/ruby-saml.key +0 -15
  43. data/test/idp_metadata_parser_test.rb +0 -587
  44. data/test/logging_test.rb +0 -62
  45. data/test/logout_requests/invalid_slo_request.xml +0 -6
  46. data/test/logout_requests/slo_request.xml +0 -4
  47. data/test/logout_requests/slo_request.xml.base64 +0 -1
  48. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  49. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  50. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  51. data/test/logout_responses/logoutresponse_fixtures.rb +0 -86
  52. data/test/logoutrequest_test.rb +0 -260
  53. data/test/logoutresponse_test.rb +0 -409
  54. data/test/metadata/idp_descriptor.xml +0 -26
  55. data/test/metadata/idp_descriptor_2.xml +0 -56
  56. data/test/metadata/idp_descriptor_3.xml +0 -14
  57. data/test/metadata/idp_descriptor_4.xml +0 -72
  58. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  59. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  60. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  61. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  62. data/test/metadata/idp_multiple_descriptors.xml +0 -59
  63. data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
  64. data/test/metadata/no_idp_descriptor.xml +0 -21
  65. data/test/metadata_test.rb +0 -331
  66. data/test/request_test.rb +0 -340
  67. data/test/response_test.rb +0 -1620
  68. data/test/responses/adfs_response_sha1.xml +0 -46
  69. data/test/responses/adfs_response_sha256.xml +0 -46
  70. data/test/responses/adfs_response_sha384.xml +0 -46
  71. data/test/responses/adfs_response_sha512.xml +0 -46
  72. data/test/responses/adfs_response_xmlns.xml +0 -45
  73. data/test/responses/attackxee.xml +0 -13
  74. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  75. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  77. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  78. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  86. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  87. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  88. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  89. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  90. data/test/responses/invalids/no_id.xml.base64 +0 -1
  91. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  93. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  94. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  95. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  96. data/test/responses/invalids/no_status.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  98. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  100. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  101. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  102. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  103. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  104. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  106. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  107. data/test/responses/no_signature_ns.xml +0 -48
  108. data/test/responses/open_saml_response.xml +0 -56
  109. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  110. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  111. data/test/responses/response_double_status_code.xml.base64 +0 -1
  112. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  114. data/test/responses/response_eval.xml +0 -7
  115. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  116. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  117. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  119. data/test/responses/response_unsigned_xml_base64 +0 -1
  120. data/test/responses/response_with_ampersands.xml +0 -139
  121. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  122. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  123. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  124. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  125. data/test/responses/response_with_retrieval_method.xml +0 -26
  126. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  127. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  128. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  129. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  130. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  131. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  132. data/test/responses/response_without_attributes.xml.base64 +0 -79
  133. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  134. data/test/responses/response_wrapped.xml.base64 +0 -150
  135. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  136. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_nameid_in_atts.xml +0 -47
  138. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  139. data/test/responses/simple_saml_php.xml +0 -71
  140. data/test/responses/starfield_response.xml.base64 +0 -1
  141. data/test/responses/test_sign.xml +0 -43
  142. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  143. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  144. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  150. data/test/responses/valid_response.xml.base64 +0 -1
  151. data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
  152. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  153. data/test/saml_message_test.rb +0 -56
  154. data/test/settings_test.rb +0 -329
  155. data/test/slo_logoutrequest_test.rb +0 -448
  156. data/test/slo_logoutresponse_test.rb +0 -233
  157. data/test/test_helper.rb +0 -331
  158. data/test/utils_test.rb +0 -259
  159. 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)
@@ -0,0 +1,6 @@
1
+ module OneLogin
2
+ module RubySaml
3
+ class SettingError < StandardError
4
+ end
5
+ end
6
+ end
@@ -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
- attr_accessor :idp_sso_target_url
34
- attr_accessor :idp_slo_target_url
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
- }.freeze,
211
- :double_quote_xml_attribute_values => false,
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 => settings.get_idp_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.idp_slo_target_url =~ /\?/) ? '&' : '?'
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
- @logout_url = settings.idp_slo_target_url + response_params
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'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
128
+ root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
116
129
 
117
- if settings.issuer != nil
130
+ if settings.sp_entity_id != nil
118
131
  issuer = root.add_element "saml:Issuer"
119
- issuer.text = settings.issuer
132
+ issuer.text = settings.sp_entity_id
120
133
  end
121
134
 
122
- # add success message
135
+ # add status
123
136
  status = root.add_element 'samlp:Status'
124
137
 
125
- # success status code
126
- status_code = status.add_element 'samlp:StatusCode'
127
- status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
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
- # success status message
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
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.10.1'
3
+ VERSION = '1.12.2'
4
4
  end
5
5
  end
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 false
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