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.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +66 -1
  4. data/README.md +365 -209
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
  7. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  8. data/lib/onelogin/ruby-saml/authrequest.rb +25 -9
  9. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
  10. data/lib/onelogin/ruby-saml/logging.rb +4 -1
  11. data/lib/onelogin/ruby-saml/logoutrequest.rb +25 -10
  12. data/lib/onelogin/ruby-saml/logoutresponse.rb +33 -17
  13. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  14. data/lib/onelogin/ruby-saml/response.rb +89 -45
  15. data/lib/onelogin/ruby-saml/saml_message.rb +17 -8
  16. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +124 -43
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +38 -11
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +42 -19
  20. data/lib/onelogin/ruby-saml/utils.rb +94 -12
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/xml_security.rb +44 -19
  23. data/ruby-saml.gemspec +16 -8
  24. metadata +44 -282
  25. data/.travis.yml +0 -26
  26. data/test/certificates/certificate1 +0 -12
  27. data/test/certificates/certificate_without_head_foot +0 -1
  28. data/test/certificates/formatted_certificate +0 -14
  29. data/test/certificates/formatted_chained_certificate +0 -42
  30. data/test/certificates/formatted_private_key +0 -12
  31. data/test/certificates/formatted_rsa_private_key +0 -12
  32. data/test/certificates/invalid_certificate1 +0 -1
  33. data/test/certificates/invalid_certificate2 +0 -1
  34. data/test/certificates/invalid_certificate3 +0 -12
  35. data/test/certificates/invalid_chained_certificate1 +0 -1
  36. data/test/certificates/invalid_private_key1 +0 -1
  37. data/test/certificates/invalid_private_key2 +0 -1
  38. data/test/certificates/invalid_private_key3 +0 -10
  39. data/test/certificates/invalid_rsa_private_key1 +0 -1
  40. data/test/certificates/invalid_rsa_private_key2 +0 -1
  41. data/test/certificates/invalid_rsa_private_key3 +0 -10
  42. data/test/certificates/ruby-saml-2.crt +0 -15
  43. data/test/certificates/ruby-saml.crt +0 -14
  44. data/test/certificates/ruby-saml.key +0 -15
  45. data/test/idp_metadata_parser_test.rb +0 -579
  46. data/test/logging_test.rb +0 -62
  47. data/test/logout_requests/invalid_slo_request.xml +0 -6
  48. data/test/logout_requests/slo_request.xml +0 -4
  49. data/test/logout_requests/slo_request.xml.base64 +0 -1
  50. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  51. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  52. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  53. data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
  54. data/test/logoutrequest_test.rb +0 -226
  55. data/test/logoutresponse_test.rb +0 -402
  56. data/test/metadata/idp_descriptor.xml +0 -26
  57. data/test/metadata/idp_descriptor_2.xml +0 -56
  58. data/test/metadata/idp_descriptor_3.xml +0 -14
  59. data/test/metadata/idp_descriptor_4.xml +0 -72
  60. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  61. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  62. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  63. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  64. data/test/metadata/idp_multiple_descriptors.xml +0 -53
  65. data/test/metadata/no_idp_descriptor.xml +0 -21
  66. data/test/metadata_test.rb +0 -331
  67. data/test/request_test.rb +0 -323
  68. data/test/response_test.rb +0 -1586
  69. data/test/responses/adfs_response_sha1.xml +0 -46
  70. data/test/responses/adfs_response_sha256.xml +0 -46
  71. data/test/responses/adfs_response_sha384.xml +0 -46
  72. data/test/responses/adfs_response_sha512.xml +0 -46
  73. data/test/responses/adfs_response_xmlns.xml +0 -45
  74. data/test/responses/attackxee.xml +0 -13
  75. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  77. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  78. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  86. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  87. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  88. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  89. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  90. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  91. data/test/responses/invalids/no_id.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  93. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  94. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  95. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  96. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status.xml.base64 +0 -1
  98. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  100. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  101. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  102. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  103. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  104. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  106. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  107. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  108. data/test/responses/no_signature_ns.xml +0 -48
  109. data/test/responses/open_saml_response.xml +0 -56
  110. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  111. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  112. data/test/responses/response_double_status_code.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  114. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  115. data/test/responses/response_eval.xml +0 -7
  116. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  117. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  119. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  120. data/test/responses/response_unsigned_xml_base64 +0 -1
  121. data/test/responses/response_with_ampersands.xml +0 -139
  122. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  123. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  124. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  125. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  126. data/test/responses/response_with_retrieval_method.xml +0 -26
  127. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  128. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  129. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  130. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  131. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  132. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  133. data/test/responses/response_without_attributes.xml.base64 +0 -79
  134. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  135. data/test/responses/response_wrapped.xml.base64 +0 -150
  136. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  138. data/test/responses/signed_nameid_in_atts.xml +0 -47
  139. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  140. data/test/responses/simple_saml_php.xml +0 -71
  141. data/test/responses/starfield_response.xml.base64 +0 -1
  142. data/test/responses/test_sign.xml +0 -43
  143. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  144. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  150. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  151. data/test/responses/valid_response.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 -301
  155. data/test/slo_logoutrequest_test.rb +0 -448
  156. data/test/slo_logoutresponse_test.rb +0 -199
  157. data/test/test_helper.rb +0 -327
  158. data/test/utils_test.rb +0 -254
  159. 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
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
16
- XENC = "http://www.w3.org/2001/04/xmlenc#"
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
- return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
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(/[\n\r\s]/, "")
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(/[\n\r\s]/, "")
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, status_code = nil, status_message = nil)
140
- unless status_code.nil?
141
- if status_code.include? "|"
142
- status_codes = status_code.split(' | ')
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 = status_code.split(':').last
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
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.8.0'
3
+ VERSION = '1.13.0'
4
4
  end
5
5
  end
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
- attr_accessor :uuid
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 = self.elements["//saml:Issuer"]
162
+ issuer_element = elements["//saml:Issuer"]
163
163
  if issuer_element
164
- self.root.insert_after issuer_element, signature_element
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
- if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
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
- attr_accessor :signed_element_id
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 => e
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 => e
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 false
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("\n")
19
- s.homepage = %q{http://github.com/onelogin/ruby-saml}
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
- s.add_runtime_dependency('nokogiri', '>= 1.6.0')
33
- s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
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.10')
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','~> 0.9.0')
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