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 +7 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -24
- data/lib/onelogin/ruby-saml/response.rb +3 -1
- data/lib/onelogin/ruby-saml/utils.rb +65 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +216 -86
- data/test/certificates/ruby-saml-2.crt +15 -0
- data/test/response_test.rb +20 -0
- data/test/responses/adfs_response_xmlns.xml +45 -0
- data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
- data/test/responses/invalids/no_signature.xml.base64 +1 -0
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +51 -0
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +49 -0
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +1 -0
- data/test/responses/response_with_signed_assertion_3.xml +30 -0
- data/test/responses/response_with_signed_message_and_assertion.xml +34 -0
- data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
- data/test/responses/valid_response_without_x509certificate.xml.base64 +1 -0
- data/test/test_helper.rb +62 -30
- data/test/xml_security_test.rb +281 -37
- metadata +70 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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 =
|
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
|
data/lib/xml_security.rb
CHANGED
@@ -42,40 +42,41 @@ module XMLSecurity
|
|
42
42
|
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
|
43
43
|
Nokogiri::XML::ParseOptions::NONET
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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(
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
197
|
+
cert_element = REXML::XPath.first(
|
198
|
+
self,
|
199
|
+
"//ds:X509Certificate",
|
200
|
+
{ "ds"=>DSIG }
|
201
|
+
)
|
191
202
|
|
192
|
-
|
193
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
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
|
203
|
-
#
|
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
|
-
|
206
|
-
|
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
|
266
|
+
# create a rexml document
|
213
267
|
@working_copy ||= REXML::Document.new(self.to_s).root
|
214
268
|
|
215
|
-
#
|
216
|
-
|
217
|
-
|
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
|
-
#
|
221
|
-
|
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
|
-
|
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.
|
230
|
-
uri = ref.attributes.get_attribute("URI").value
|
318
|
+
ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
|
231
319
|
|
232
|
-
|
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
|
-
|
322
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(
|
323
|
+
ref,
|
324
|
+
'//ds:CanonicalizationMethod',
|
325
|
+
{ "ds" => DSIG }
|
326
|
+
)
|
237
327
|
|
238
|
-
|
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
|
-
|
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
|
-
|
247
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
345
|
+
unless digests_match?(hash, digest_value)
|
346
|
+
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
|
347
|
+
end
|
252
348
|
|
253
|
-
#
|
254
|
-
|
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
|
-
|
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
|
|