ruby-saml 0.8.12 → 0.8.13

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 CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA512:
3
- metadata.gz: 8a2479b6725a5a9e7fdc76a4bec612e2f0c66cf53cbb79ff7c1dc0343d1cc56c09e9fd3b2d3490bdacbb09b16b473702d42fa42fdb6fedff6e7fa5a44fa421a2
4
- data.tar.gz: 43b1cfb12dc3fc14a2cbc139430e3ad15b975dca0d95d0d8c14363f362833de181a52ca10b8b607215c4b5aa23ffa62f2d8f3a3b2c9b5541e9387c635ca77150
5
- SHA256:
6
- metadata.gz: 694ade703ed05cc38aa2ca98cbfee57cc16223991ae6539422c136164cf29608
7
- data.tar.gz: ee07b69a9391b26c9af95d0cfdbaa57c8991fa187b869660e15c549fcbbe47e3
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3f3a436bf74c3342e13ed40b9d6d7c71e8b25f1
4
+ data.tar.gz: c39cb2b2fa7844d97cd83e2d6a34f7a5ab68151e
5
+ SHA512:
6
+ metadata.gz: 38e6e375700d52f5bd4300dc5a1e7b9b20e5283b00371418730b1857ffc9b98857e72066a9ea67b504953eddaefc8683a0d40a29156f614dc18f9aaea7e7e0e5
7
+ data.tar.gz: a93d2f2c35bed0a8c44db64e3672aa8e811883d37b0386618dd51d0d7a9f19ddd37c59381dfa2c94cc04a361f3ecce8cd9677dc9ab6f44dee4eb653fefedba91
@@ -1,7 +1,5 @@
1
1
  require "xml_security"
2
2
  require "time"
3
- require "base64"
4
- require "zlib"
5
3
 
6
4
  module OneLogin
7
5
  module RubySaml
@@ -30,7 +28,7 @@ module OneLogin
30
28
  self.settings = settings
31
29
 
32
30
  @options = options
33
- @response = decode_raw_response(response)
31
+ @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
34
32
  @document = XMLSecurity::SignedDocument.new(response)
35
33
  end
36
34
 
@@ -75,27 +73,6 @@ module OneLogin
75
73
 
76
74
  private
77
75
 
78
- def decode(encoded)
79
- Base64.decode64(encoded)
80
- end
81
-
82
- def inflate(deflated)
83
- zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
84
- zlib.inflate(deflated)
85
- end
86
-
87
- def decode_raw_response(response)
88
- if response =~ /^</
89
- return response
90
- elsif (decoded = decode(response)) =~ /^</
91
- return decoded
92
- elsif (inflated = inflate(decoded)) =~ /^</
93
- return inflated
94
- end
95
-
96
- raise "Couldn't decode SAMLResponse"
97
- end
98
-
99
76
  def valid_saml?(soft = true)
100
77
  Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
101
78
  @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
@@ -1,6 +1,7 @@
1
1
  require "xml_security"
2
2
  require "time"
3
3
  require "nokogiri"
4
+ require "onelogin/ruby-saml/utils"
4
5
  require 'onelogin/ruby-saml/attributes'
5
6
 
6
7
  # Only supports SAML 2.0
@@ -22,7 +23,7 @@ module OneLogin
22
23
  def initialize(response, options = {})
23
24
  raise ArgumentError.new("Response cannot be nil") if response.nil?
24
25
  @options = options
25
- @response = (response =~ /^</) ? response : Base64.decode64(response)
26
+ @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
26
27
  @document = XMLSecurity::SignedDocument.new(@response)
27
28
  end
28
29
 
@@ -421,6 +422,7 @@ module OneLogin
421
422
 
422
423
  true
423
424
  end
425
+
424
426
  end
425
427
  end
426
428
  end
@@ -4,6 +4,9 @@ else
4
4
  require 'securerandom'
5
5
  end
6
6
 
7
+ require "base64"
8
+ require "zlib"
9
+
7
10
  module OneLogin
8
11
  module RubySaml
9
12
 
@@ -12,6 +15,8 @@ module OneLogin
12
15
  class Utils
13
16
  @@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
14
17
 
18
+ BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
19
+
15
20
  # Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
16
21
  # that there all children other than text nodes can be ignored (e.g. comments). If nil is
17
22
  # passed, nil will be returned.
@@ -114,6 +119,66 @@ module OneLogin
114
119
 
115
120
  error_msg
116
121
  end
122
+
123
+ # Base64 decode and try also to inflate a SAML Message
124
+ # @param saml [String] The deflated and encoded SAML Message
125
+ # @return [String] The plain SAML Message
126
+ #
127
+ def self.decode_raw_saml(saml)
128
+ return saml unless base64_encoded?(saml)
129
+
130
+ decoded = decode(saml)
131
+ begin
132
+ inflate(decoded)
133
+ rescue
134
+ decoded
135
+ end
136
+ end
137
+
138
+ # Base 64 decode method
139
+ # @param string [String] The string message
140
+ # @return [String] The decoded string
141
+ #
142
+ def self.decode(string)
143
+ Base64.decode64(string)
144
+ end
145
+
146
+ # Base 64 encode method
147
+ # @param string [String] The string
148
+ # @return [String] The encoded string
149
+ #
150
+ def self.encode(string)
151
+ if Base64.respond_to?('strict_encode64')
152
+ Base64.strict_encode64(string)
153
+ else
154
+ Base64.encode64(string).gsub(/\n/, "")
155
+ end
156
+ end
157
+
158
+ # Check if a string is base64 encoded
159
+ # @param string [String] string to check the encoding of
160
+ # @return [true, false] whether or not the string is base64 encoded
161
+ #
162
+ def self.base64_encoded?(string)
163
+ !!string.gsub(/[\r\n]|\\r|\\n|\s/, "").match(BASE64_FORMAT)
164
+ end
165
+
166
+ # Inflate method
167
+ # @param deflated [String] The string
168
+ # @return [String] The inflated string
169
+ #
170
+ def self.inflate(deflated)
171
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated)
172
+ end
173
+
174
+ # Deflate method
175
+ # @param inflated [String] The string
176
+ # @return [String] The deflated string
177
+ #
178
+ def self.deflate(inflated)
179
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
180
+ end
181
+
117
182
  end
118
183
  end
119
184
  end
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '0.8.12'
3
+ VERSION = '0.8.13'
4
4
  end
5
5
  end
@@ -42,40 +42,41 @@ module XMLSecurity
42
42
  NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
43
43
  Nokogiri::XML::ParseOptions::NONET
44
44
 
45
- def canon_algorithm(element)
46
- algorithm = element
47
- if algorithm.is_a?(REXML::Element)
48
- algorithm = element.attribute('Algorithm').value
49
- end
50
-
51
- 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
60
- end
61
- end
62
-
63
- def algorithm(element)
64
- algorithm = element
65
- if algorithm.is_a?(REXML::Element)
66
- algorithm = element.attribute("Algorithm").value
67
- end
68
-
69
- algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
70
-
71
- case algorithm
72
- when 256 then OpenSSL::Digest::SHA256
73
- when 384 then OpenSSL::Digest::SHA384
74
- when 512 then OpenSSL::Digest::SHA512
75
- else
76
- OpenSSL::Digest::SHA1
77
- end
78
- end
45
+ def canon_algorithm(element)
46
+ algorithm = element
47
+ if algorithm.is_a?(REXML::Element)
48
+ algorithm = element.attribute('Algorithm').value
49
+ end
50
+
51
+ 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
60
+ end
61
+ end
62
+
63
+ def algorithm(element)
64
+ algorithm = element
65
+ if algorithm.is_a?(REXML::Element)
66
+ algorithm = element.attribute("Algorithm").value
67
+ end
68
+
69
+ algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
70
+
71
+ case algorithm
72
+ when 256 then OpenSSL::Digest::SHA256
73
+ when 384 then OpenSSL::Digest::SHA384
74
+ when 512 then OpenSSL::Digest::SHA512
75
+ else
76
+ OpenSSL::Digest::SHA1
77
+ end
78
+ end
79
+
79
80
  end
80
81
 
81
82
  class Document < BaseDocument
@@ -98,15 +99,30 @@ module XMLSecurity
98
99
  end
99
100
  end
100
101
 
102
+ #<Signature>
103
+ #<SignedInfo>
104
+ #<CanonicalizationMethod />
105
+ #<SignatureMethod />
106
+ #<Reference>
107
+ #<Transforms>
108
+ #<DigestMethod>
109
+ #<DigestValue>
110
+ #</Reference>
111
+ #<Reference /> etc.
112
+ #</SignedInfo>
113
+ #<SignatureValue />
114
+ #<KeyInfo />
115
+ #<Object />
116
+ #</Signature>
101
117
  def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
102
118
  noko = Nokogiri::XML(self.to_s) do |config|
103
- config.options = NOKOGIRI_OPTIONS
119
+ config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
104
120
  end
105
121
 
106
122
  signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
107
123
  signed_info_element = signature_element.add_element("ds:SignedInfo")
108
124
  signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
109
- signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
125
+ signed_info_element.add_element("ds:SignatureMethod", {"Algorithm" => signature_method})
110
126
 
111
127
  # Add Reference
112
128
  reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
@@ -124,7 +140,7 @@ module XMLSecurity
124
140
 
125
141
  # add SignatureValue
126
142
  noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
127
- config.options = NOKOGIRI_OPTIONS
143
+ config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
128
144
  end
129
145
 
130
146
  noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
@@ -172,87 +188,169 @@ module XMLSecurity
172
188
 
173
189
  attr_writer :signed_element_id
174
190
 
175
- def initialize(response)
176
- super(response)
177
- extract_signed_element_id
178
- end
179
-
180
191
  def signed_element_id
181
192
  @signed_element_id ||= extract_signed_element_id
182
193
  end
183
194
 
184
- def validate_document(idp_cert_fingerprint, soft = true)
195
+ def validate_document(idp_cert_fingerprint, soft = true, options = {})
185
196
  # get cert from response
186
- cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
187
- raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)") unless cert_element
188
- base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
189
- cert_text = Base64.decode64(base64_cert)
190
- cert = OpenSSL::X509::Certificate.new(cert_text)
197
+ cert_element = REXML::XPath.first(
198
+ self,
199
+ "//ds:X509Certificate",
200
+ { "ds"=>DSIG }
201
+ )
191
202
 
192
- # check cert matches registered idp cert
193
- fingerprint = Digest::SHA1.hexdigest(cert.to_der)
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
194
211
 
195
- if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
196
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
197
- end
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)
198
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
+ base64_cert = Base64.encode64(options[:cert].to_pem)
226
+ else
227
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings"))
228
+ end
229
+ end
199
230
  validate_signature(base64_cert, soft)
200
231
  end
201
232
 
202
- def validate_signature(base64_cert, soft = true)
203
- # validate references
233
+ def validate_document_with_cert(idp_cert, soft = true)
234
+ # get cert from response
235
+ cert_element = REXML::XPath.first(
236
+ self,
237
+ "//ds:X509Certificate",
238
+ { "ds"=>DSIG }
239
+ )
204
240
 
205
- # check for inclusive namespaces
206
- inclusive_namespaces = extract_inclusive_namespaces
241
+ if cert_element
242
+ base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
243
+ cert_text = Base64.decode64(base64_cert)
244
+ begin
245
+ cert = OpenSSL::X509::Certificate.new(cert_text)
246
+ rescue OpenSSL::X509::CertificateError => _e
247
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate Error"))
248
+ end
249
+
250
+ # check saml response cert matches provided idp cert
251
+ if idp_cert.to_pem != cert.to_pem
252
+ return false
253
+ end
254
+ else
255
+ base64_cert = Base64.encode64(idp_cert.to_pem)
256
+ end
257
+ validate_signature(base64_cert, true)
258
+ end
259
+
260
+ def validate_signature(base64_cert, soft = true)
207
261
 
208
262
  document = Nokogiri::XML(self.to_s) do |config|
209
- config.options = NOKOGIRI_OPTIONS
263
+ config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
210
264
  end
211
265
 
212
- # create a working copy so we don't modify the original
266
+ # create a rexml document
213
267
  @working_copy ||= REXML::Document.new(self.to_s).root
214
268
 
215
- # store signature node
216
- @sig_element ||= begin
217
- element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
269
+ # get signature node
270
+ sig_element = REXML::XPath.first(
271
+ @working_copy,
272
+ "//ds:Signature",
273
+ {"ds"=>DSIG}
274
+ )
275
+
276
+ if sig_element.nil?
277
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("No Signature Node"))
218
278
  end
219
279
 
220
- # verify signature
221
- signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
280
+ # signature method
281
+ sig_alg_value = REXML::XPath.first(
282
+ sig_element,
283
+ "./ds:SignedInfo/ds:SignatureMethod",
284
+ {"ds"=>DSIG}
285
+ )
286
+ signature_algorithm = algorithm(sig_alg_value)
287
+
288
+ # get signature
289
+ base64_signature = REXML::XPath.first(
290
+ sig_element,
291
+ "./ds:SignatureValue",
292
+ {"ds" => DSIG}
293
+ )
294
+
295
+ if base64_signature.nil?
296
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("SignatureValue not found"))
297
+ end
298
+
299
+ signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature))
300
+
301
+ # canonicalization method
302
+ canon_algorithm = canon_algorithm REXML::XPath.first(
303
+ sig_element,
304
+ './ds:SignedInfo/ds:CanonicalizationMethod',
305
+ 'ds' => DSIG
306
+ )
307
+
222
308
  noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
223
309
  noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
224
- canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
310
+
225
311
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
226
312
  noko_sig_element.remove
227
313
 
314
+ # get inclusive namespaces
315
+ inclusive_namespaces = extract_inclusive_namespaces
316
+
228
317
  # check digests
229
- REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
230
- uri = ref.attributes.get_attribute("URI").value
318
+ ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
231
319
 
232
- hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
233
- canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
234
- canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
320
+ hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
235
321
 
236
- digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", 'ds' => DSIG))
322
+ canon_algorithm = canon_algorithm REXML::XPath.first(
323
+ ref,
324
+ '//ds:CanonicalizationMethod',
325
+ { "ds" => DSIG }
326
+ )
237
327
 
238
- hash = digest_algorithm.digest(canon_hashed_element)
239
- digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG})))
328
+ canon_algorithm = process_transforms(ref, canon_algorithm)
240
329
 
241
- unless digests_match?(hash, digest_value)
242
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
243
- end
244
- end
330
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
245
331
 
246
- base64_signature = OneLogin::RubySaml::Utils.element_text(REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}))
247
- signature = Base64.decode64(base64_signature)
332
+ digest_algorithm = algorithm(REXML::XPath.first(
333
+ ref,
334
+ "//ds:DigestMethod",
335
+ { "ds" => DSIG }
336
+ ))
337
+ hash = digest_algorithm.digest(canon_hashed_element)
338
+ encoded_digest_value = REXML::XPath.first(
339
+ ref,
340
+ "//ds:DigestValue",
341
+ { "ds" => DSIG }
342
+ )
343
+ digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
248
344
 
249
- # get certificate object
250
- cert_text = Base64.decode64(base64_cert)
251
- cert = OpenSSL::X509::Certificate.new(cert_text)
345
+ unless digests_match?(hash, digest_value)
346
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
347
+ end
252
348
 
253
- # signature method
254
- signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
349
+ # get certificate object
350
+ cert_text = Base64.decode64(base64_cert)
351
+ cert = OpenSSL::X509::Certificate.new(cert_text)
255
352
 
353
+ # verify signature
256
354
  unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
257
355
  return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error"))
258
356
  end
@@ -262,6 +360,33 @@ module XMLSecurity
262
360
 
263
361
  private
264
362
 
363
+ def process_transforms(ref, canon_algorithm)
364
+ transforms = REXML::XPath.match(
365
+ ref,
366
+ "//ds:Transforms/ds:Transform",
367
+ { "ds" => DSIG }
368
+ )
369
+
370
+ transforms.each do |transform_element|
371
+ if transform_element.attributes && transform_element.attributes["Algorithm"]
372
+ algorithm = transform_element.attributes["Algorithm"]
373
+ case algorithm
374
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
375
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
376
+ canon_algorithm = Nokogiri::XML::XML_C14N_1_0
377
+ when "http://www.w3.org/2006/12/xml-c14n11",
378
+ "http://www.w3.org/2006/12/xml-c14n11#WithComments"
379
+ canon_algorithm = Nokogiri::XML::XML_C14N_1_1
380
+ when "http://www.w3.org/2001/10/xml-exc-c14n#",
381
+ "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
382
+ canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
383
+ end
384
+ end
385
+ end
386
+
387
+ canon_algorithm
388
+ end
389
+
265
390
  def digests_match?(hash, digest_value)
266
391
  hash == digest_value
267
392
  end
@@ -280,11 +405,16 @@ module XMLSecurity
280
405
  end
281
406
 
282
407
  def extract_inclusive_namespaces
283
- if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
408
+ element = REXML::XPath.first(
409
+ self,
410
+ "//ec:InclusiveNamespaces",
411
+ { "ec" => C14N }
412
+ )
413
+ if element
284
414
  prefix_list = element.attributes.get_attribute("PrefixList").value
285
415
  prefix_list.split(" ")
286
416
  else
287
- []
417
+ nil
288
418
  end
289
419
  end
290
420