ruby-saml 0.8.18 → 0.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -6
  4. data/Gemfile +2 -12
  5. data/README.md +363 -35
  6. data/Rakefile +14 -0
  7. data/changelog.md +22 -9
  8. data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
  9. data/lib/onelogin/ruby-saml/attributes.rb +26 -64
  10. data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
  12. data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
  13. data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
  14. data/lib/onelogin/ruby-saml/metadata.rb +46 -16
  15. data/lib/onelogin/ruby-saml/response.rb +63 -373
  16. data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +54 -122
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
  20. data/lib/onelogin/ruby-saml/utils.rb +32 -199
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/ruby-saml.rb +5 -2
  23. data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
  24. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  25. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  26. data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
  27. data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
  28. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  29. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  30. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  31. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  32. data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
  33. data/lib/schemas/xml.xsd +287 -0
  34. data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
  35. data/lib/xml_security.rb +83 -235
  36. data/ruby-saml.gemspec +1 -0
  37. data/test/idp_metadata_parser_test.rb +54 -0
  38. data/test/logoutrequest_test.rb +68 -155
  39. data/test/logoutresponse_test.rb +43 -32
  40. data/test/metadata_test.rb +87 -0
  41. data/test/request_test.rb +102 -99
  42. data/test/response_test.rb +181 -495
  43. data/test/responses/idp_descriptor.xml +3 -0
  44. data/test/responses/logoutresponse_fixtures.rb +7 -8
  45. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  46. data/test/responses/response_with_multiple_attribute_values.xml +1 -1
  47. data/test/responses/slo_request.xml +4 -0
  48. data/test/settings_test.rb +25 -112
  49. data/test/slo_logoutrequest_test.rb +40 -50
  50. data/test/slo_logoutresponse_test.rb +86 -185
  51. data/test/test_helper.rb +27 -102
  52. data/test/xml_security_test.rb +114 -337
  53. metadata +30 -81
  54. data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
  55. data/test/certificates/certificate.der +0 -0
  56. data/test/certificates/formatted_certificate +0 -14
  57. data/test/certificates/formatted_chained_certificate +0 -42
  58. data/test/certificates/formatted_private_key +0 -12
  59. data/test/certificates/formatted_rsa_private_key +0 -12
  60. data/test/certificates/invalid_certificate1 +0 -1
  61. data/test/certificates/invalid_certificate2 +0 -1
  62. data/test/certificates/invalid_certificate3 +0 -12
  63. data/test/certificates/invalid_chained_certificate1 +0 -1
  64. data/test/certificates/invalid_private_key1 +0 -1
  65. data/test/certificates/invalid_private_key2 +0 -1
  66. data/test/certificates/invalid_private_key3 +0 -10
  67. data/test/certificates/invalid_rsa_private_key1 +0 -1
  68. data/test/certificates/invalid_rsa_private_key2 +0 -1
  69. data/test/certificates/invalid_rsa_private_key3 +0 -10
  70. data/test/certificates/ruby-saml-2.crt +0 -15
  71. data/test/requests/logoutrequest_fixtures.rb +0 -47
  72. data/test/responses/encrypted_new_attack.xml.base64 +0 -1
  73. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  74. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  75. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  76. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  77. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  78. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  79. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  80. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  81. data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
  82. data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
  83. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  84. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  85. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  86. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  87. data/test/responses/response_wrapped.xml.base64 +0 -150
  88. data/test/responses/valid_response.xml.base64 +0 -1
  89. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  90. data/test/utils_test.rb +0 -231
data/lib/xml_security.rb CHANGED
@@ -30,17 +30,13 @@ require 'nokogiri'
30
30
  require "digest/sha1"
31
31
  require "digest/sha2"
32
32
  require "onelogin/ruby-saml/validation_error"
33
- require "onelogin/ruby-saml/utils"
34
33
 
35
34
  module XMLSecurity
36
35
 
37
36
  class BaseDocument < REXML::Document
38
- REXML::Document::entity_expansion_limit = 0
39
37
 
40
38
  C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
41
39
  DSIG = "http://www.w3.org/2000/09/xmldsig#"
42
- NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
43
- Nokogiri::XML::ParseOptions::NONET
44
40
 
45
41
  def canon_algorithm(element)
46
42
  algorithm = element
@@ -49,14 +45,10 @@ module XMLSecurity
49
45
  end
50
46
 
51
47
  case algorithm
52
- when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
53
- "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
54
- Nokogiri::XML::XML_C14N_1_0
55
- when "http://www.w3.org/2006/12/xml-c14n11",
56
- "http://www.w3.org/2006/12/xml-c14n11#WithComments"
57
- Nokogiri::XML::XML_C14N_1_1
58
- else
59
- Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
48
+ when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
49
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
50
+ when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
51
+ else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
60
52
  end
61
53
  end
62
54
 
@@ -64,10 +56,9 @@ module XMLSecurity
64
56
  algorithm = element
65
57
  if algorithm.is_a?(REXML::Element)
66
58
  algorithm = element.attribute("Algorithm").value
59
+ algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
67
60
  end
68
61
 
69
- algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
70
-
71
62
  case algorithm
72
63
  when 256 then OpenSSL::Digest::SHA256
73
64
  when 384 then OpenSSL::Digest::SHA384
@@ -80,24 +71,14 @@ module XMLSecurity
80
71
  end
81
72
 
82
73
  class Document < BaseDocument
83
- RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
84
- RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
85
- RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
86
- RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
87
- SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
88
- SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
89
- SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
90
- SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
74
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
75
+ SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
76
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
77
+ SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
91
78
  ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
92
- INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
79
+ INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
93
80
 
94
- attr_writer :uuid
95
-
96
- def uuid
97
- @uuid ||= begin
98
- document.root.nil? ? nil : document.root.attributes['ID']
99
- end
100
- end
81
+ attr_accessor :uuid
101
82
 
102
83
  #<Signature>
103
84
  #<SignedInfo>
@@ -114,15 +95,14 @@ module XMLSecurity
114
95
  #<KeyInfo />
115
96
  #<Object />
116
97
  #</Signature>
117
- def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
118
- noko = Nokogiri::XML(self.to_s) do |config|
119
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
120
- end
98
+ def sign_document(private_key, certificate, signature_method = SHA1, digest_method = SHA1)
99
+ noko = Nokogiri.parse(self.to_s)
100
+ canon_doc = noko.canonicalize(canon_algorithm(C14N))
121
101
 
122
102
  signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
123
103
  signed_info_element = signature_element.add_element("ds:SignedInfo")
124
104
  signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
125
- signed_info_element.add_element("ds:SignatureMethod", {"Algorithm" => signature_method})
105
+ signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
126
106
 
127
107
  # Add Reference
128
108
  reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
@@ -130,22 +110,16 @@ module XMLSecurity
130
110
  # Add Transforms
131
111
  transforms_element = reference_element.add_element("ds:Transforms")
132
112
  transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
133
- c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
134
- c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
113
+ transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
114
+ transforms_element.add_element("ds:InclusiveNamespaces", {"xmlns" => C14N, "PrefixList" => INC_PREFIX_LIST})
135
115
 
136
116
  digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
137
- inclusive_namespaces = INC_PREFIX_LIST.split(" ")
138
- canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
139
117
  reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
140
118
 
141
119
  # add SignatureValue
142
- noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
143
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
144
- end
145
-
120
+ noko_sig_element = Nokogiri.parse(signature_element.to_s)
146
121
  noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
147
122
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
148
-
149
123
  signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
150
124
  signature_element.add_element("ds:SignatureValue").text = signature
151
125
 
@@ -163,11 +137,7 @@ module XMLSecurity
163
137
  if issuer_element
164
138
  self.root.insert_after issuer_element, signature_element
165
139
  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
140
+ self.root.add_element(signature_element)
171
141
  end
172
142
  end
173
143
 
@@ -186,178 +156,96 @@ module XMLSecurity
186
156
 
187
157
  class SignedDocument < BaseDocument
188
158
 
189
- attr_writer :signed_element_id
159
+ attr_accessor :signed_element_id
160
+ attr_accessor :errors
190
161
 
191
- def signed_element_id
192
- @signed_element_id ||= extract_signed_element_id
162
+ def initialize(response, errors = [])
163
+ super(response)
164
+ @errors = errors
165
+ extract_signed_element_id
193
166
  end
194
167
 
195
- def validate_document(idp_cert_fingerprint, soft = true, options = {})
168
+ def validate_document(idp_cert_fingerprint, soft = true)
196
169
  # get cert from response
197
- cert_element = REXML::XPath.first(
198
- self,
199
- "//ds:X509Certificate",
200
- { "ds"=>DSIG }
201
- )
202
-
203
- if cert_element
204
- base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
205
- cert_text = Base64.decode64(base64_cert)
206
- begin
207
- cert = OpenSSL::X509::Certificate.new(cert_text)
208
- rescue OpenSSL::X509::CertificateError => _e
209
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate Error"))
210
- end
211
-
212
- if options[:fingerprint_alg]
213
- fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
214
- else
215
- fingerprint_alg = OpenSSL::Digest::SHA1.new
216
- end
217
- fingerprint = fingerprint_alg.hexdigest(cert.to_der)
218
-
219
- # check cert matches registered idp cert
220
- if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
221
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
222
- end
223
- else
224
- if options[:cert]
225
- cert = options[:cert]
226
- if cert.is_a? String
227
- cert = OpenSSL::X509::Certificate.new(cert)
228
- end
229
- base64_cert = Base64.encode64(cert.to_pem)
170
+ cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
171
+ unless cert_element
172
+ if soft
173
+ return false
230
174
  else
231
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings"))
175
+ raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
232
176
  end
233
177
  end
234
- validate_signature(base64_cert, soft)
235
- end
178
+ base64_cert = cert_element.text
179
+ cert_text = Base64.decode64(base64_cert)
180
+ cert = OpenSSL::X509::Certificate.new(cert_text)
236
181
 
237
- def validate_document_with_cert(idp_cert, soft = true)
238
- # get cert from response
239
- cert_element = REXML::XPath.first(
240
- self,
241
- "//ds:X509Certificate",
242
- { "ds"=>DSIG }
243
- )
244
-
245
- if cert_element
246
- base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
247
- cert_text = Base64.decode64(base64_cert)
248
- begin
249
- cert = OpenSSL::X509::Certificate.new(cert_text)
250
- rescue OpenSSL::X509::CertificateError => _e
251
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate Error"))
252
- end
182
+ # check cert matches registered idp cert
183
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
253
184
 
254
- # check saml response cert matches provided idp cert
255
- if idp_cert.to_pem != cert.to_pem
256
- return false
257
- end
258
- elsif not idp_cert
259
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings"))
260
- else
261
- base64_cert = Base64.encode64(idp_cert.to_pem)
185
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
186
+ @errors << "Fingerprint mismatch"
187
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
262
188
  end
263
- validate_signature(base64_cert, true)
189
+
190
+ validate_signature(base64_cert, soft)
264
191
  end
265
192
 
266
193
  def validate_signature(base64_cert, soft = true)
194
+ # validate references
267
195
 
268
- document = Nokogiri::XML(self.to_s) do |config|
269
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
270
- end
271
-
272
- # create a rexml document
273
- @working_copy ||= REXML::Document.new(self.to_s).root
196
+ # check for inclusive namespaces
197
+ inclusive_namespaces = extract_inclusive_namespaces
274
198
 
275
- # get signature node
276
- sig_element = REXML::XPath.first(
277
- @working_copy,
278
- "//ds:Signature",
279
- {"ds"=>DSIG}
280
- )
199
+ document = Nokogiri.parse(self.to_s)
281
200
 
282
- if sig_element.nil?
283
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("No Signature Node"))
284
- end
201
+ # create a working copy so we don't modify the original
202
+ @working_copy ||= REXML::Document.new(self.to_s).root
285
203
 
286
- # signature method
287
- sig_alg_value = REXML::XPath.first(
288
- sig_element,
289
- "./ds:SignedInfo/ds:SignatureMethod",
290
- {"ds"=>DSIG}
291
- )
292
- signature_algorithm = algorithm(sig_alg_value)
293
-
294
- # get signature
295
- base64_signature = REXML::XPath.first(
296
- sig_element,
297
- "./ds:SignatureValue",
298
- {"ds" => DSIG}
299
- )
300
-
301
- if base64_signature.nil?
302
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("SignatureValue not found"))
204
+ # store and remove signature node
205
+ @sig_element ||= begin
206
+ element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
207
+ element.remove
303
208
  end
304
209
 
305
- signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature))
306
-
307
- # canonicalization method
308
- canon_algorithm = canon_algorithm REXML::XPath.first(
309
- sig_element,
310
- './ds:SignedInfo/ds:CanonicalizationMethod',
311
- 'ds' => DSIG
312
- )
313
-
210
+ # verify signature
211
+ signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
314
212
  noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
315
213
  noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
316
-
214
+ canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
317
215
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
318
216
  noko_sig_element.remove
319
217
 
320
- # get inclusive namespaces
321
- inclusive_namespaces = extract_inclusive_namespaces
322
-
323
218
  # check digests
324
- ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
325
-
326
- hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
327
-
328
- canon_algorithm = canon_algorithm REXML::XPath.first(
329
- ref,
330
- '//ds:CanonicalizationMethod',
331
- { "ds" => DSIG }
332
- )
333
-
334
- canon_algorithm = process_transforms(ref, canon_algorithm)
335
-
336
- canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
337
-
338
- digest_algorithm = algorithm(REXML::XPath.first(
339
- ref,
340
- "//ds:DigestMethod",
341
- { "ds" => DSIG }
342
- ))
343
- hash = digest_algorithm.digest(canon_hashed_element)
344
- encoded_digest_value = REXML::XPath.first(
345
- ref,
346
- "//ds:DigestValue",
347
- { "ds" => DSIG }
348
- )
349
- digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
350
-
351
- unless digests_match?(hash, digest_value)
352
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
219
+ REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
220
+ uri = ref.attributes.get_attribute("URI").value
221
+
222
+ hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
223
+ canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
224
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
225
+
226
+ digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", 'ds' => DSIG))
227
+
228
+ hash = digest_algorithm.digest(canon_hashed_element)
229
+ digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
230
+
231
+ unless digests_match?(hash, digest_value)
232
+ @errors << "Digest mismatch"
233
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
234
+ end
353
235
  end
354
236
 
237
+ base64_signature = REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
238
+ signature = Base64.decode64(base64_signature)
239
+
355
240
  # get certificate object
356
- cert_text = Base64.decode64(base64_cert)
357
- cert = OpenSSL::X509::Certificate.new(cert_text)
241
+ cert_text = Base64.decode64(base64_cert)
242
+ cert = OpenSSL::X509::Certificate.new(cert_text)
243
+
244
+ # signature method
245
+ signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
358
246
 
359
- # verify signature
360
247
  unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
248
+ @errors << "Key validation error"
361
249
  return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error"))
362
250
  end
363
251
 
@@ -366,61 +254,21 @@ module XMLSecurity
366
254
 
367
255
  private
368
256
 
369
- def process_transforms(ref, canon_algorithm)
370
- transforms = REXML::XPath.match(
371
- ref,
372
- "//ds:Transforms/ds:Transform",
373
- { "ds" => DSIG }
374
- )
375
-
376
- transforms.each do |transform_element|
377
- if transform_element.attributes && transform_element.attributes["Algorithm"]
378
- algorithm = transform_element.attributes["Algorithm"]
379
- case algorithm
380
- when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
381
- "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
382
- canon_algorithm = Nokogiri::XML::XML_C14N_1_0
383
- when "http://www.w3.org/2006/12/xml-c14n11",
384
- "http://www.w3.org/2006/12/xml-c14n11#WithComments"
385
- canon_algorithm = Nokogiri::XML::XML_C14N_1_1
386
- when "http://www.w3.org/2001/10/xml-exc-c14n#",
387
- "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
388
- canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
389
- end
390
- end
391
- end
392
-
393
- canon_algorithm
394
- end
395
-
396
257
  def digests_match?(hash, digest_value)
397
258
  hash == digest_value
398
259
  end
399
260
 
400
261
  def extract_signed_element_id
401
- reference_element = REXML::XPath.first(
402
- self,
403
- "//ds:Signature/ds:SignedInfo/ds:Reference",
404
- {"ds"=>DSIG}
405
- )
406
-
407
- return nil if reference_element.nil?
408
-
409
- sei = reference_element.attribute("URI").value[1..-1]
410
- sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
262
+ reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
263
+ self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
411
264
  end
412
265
 
413
266
  def extract_inclusive_namespaces
414
- element = REXML::XPath.first(
415
- self,
416
- "//ec:InclusiveNamespaces",
417
- { "ec" => C14N }
418
- )
419
- if element
267
+ if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
420
268
  prefix_list = element.attributes.get_attribute("PrefixList").value
421
269
  prefix_list.split(" ")
422
270
  else
423
- nil
271
+ []
424
272
  end
425
273
  end
426
274
 
data/ruby-saml.gemspec CHANGED
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.date = Time.now.strftime("%Y-%m-%d")
11
11
  s.description = %q{SAML toolkit for Ruby on Rails}
12
12
  s.email = %q{support@onelogin.com}
13
+ s.license = 'MIT'
13
14
  s.extra_rdoc_files = [
14
15
  "LICENSE",
15
16
  "README.md"
@@ -0,0 +1,54 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+ require 'net/http'
3
+ require 'net/https'
4
+
5
+ class IdpMetadataParserTest < Test::Unit::TestCase
6
+
7
+ class MockResponse
8
+ attr_accessor :body
9
+ end
10
+
11
+ context "parsing an IdP descriptor file" do
12
+ should "extract settings details from xml" do
13
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
14
+
15
+ settings = idp_metadata_parser.parse(idp_metadata)
16
+
17
+ assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
18
+ assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
19
+ assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
20
+ end
21
+ end
22
+
23
+ context "download and parse IdP descriptor file" do
24
+ setup do
25
+ mock_response = MockResponse.new
26
+ mock_response.body = idp_metadata
27
+ @url = "https://example.com"
28
+ uri = URI(@url)
29
+
30
+ @http = Net::HTTP.new(uri.host, uri.port)
31
+ Net::HTTP.expects(:new).returns(@http)
32
+ @http.expects(:request).returns(mock_response)
33
+ end
34
+
35
+
36
+ should "extract settings from remote xml" do
37
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
38
+ settings = idp_metadata_parser.parse_remote(@url)
39
+
40
+ assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
41
+ assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
42
+ assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
43
+ assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
44
+ end
45
+
46
+ should "accept self signed certificate if insturcted" do
47
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
48
+ settings = idp_metadata_parser.parse_remote(@url, false)
49
+
50
+ assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode
51
+ end
52
+ end
53
+
54
+ end