ruby-saml 0.8.16 → 0.9
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -89
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +34 -93
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -24
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +62 -322
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -121
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -61
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -84
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -144
- data/test/logoutresponse_test.rb +43 -25
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +103 -90
- data/test/response_test.rb +181 -471
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +5 -5
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +41 -44
- data/test/slo_logoutresponse_test.rb +87 -167
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +34 -84
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<!DOCTYPE schema
|
|
3
|
-
PUBLIC "-//W3C//DTD XMLSchema 200102//EN" "http://www.w3.org/2001/XMLSchema.dtd"
|
|
4
|
-
[
|
|
5
|
-
<!ATTLIST schema
|
|
6
|
-
xmlns:ds CDATA #FIXED "http://www.w3.org/2000/09/xmldsig#">
|
|
7
|
-
<!ENTITY dsig 'http://www.w3.org/2000/09/xmldsig#'>
|
|
8
|
-
<!ENTITY % p ''>
|
|
9
|
-
<!ENTITY % s ''>
|
|
10
|
-
]>
|
|
11
2
|
|
|
12
3
|
<!-- Schema for XML Signatures
|
|
13
4
|
http://www.w3.org/2000/09/xmldsig#
|
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/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
79
|
+
INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
|
|
93
80
|
|
|
94
|
-
|
|
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 =
|
|
118
|
-
noko = Nokogiri
|
|
119
|
-
|
|
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"
|
|
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
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
159
|
+
attr_accessor :signed_element_id
|
|
160
|
+
attr_accessor :errors
|
|
190
161
|
|
|
191
|
-
def
|
|
192
|
-
|
|
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
|
|
168
|
+
def validate_document(idp_cert_fingerprint, soft = true)
|
|
196
169
|
# get cert from response
|
|
197
|
-
cert_element = REXML::XPath.first(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
175
|
+
raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
|
|
232
176
|
end
|
|
233
177
|
end
|
|
234
|
-
|
|
235
|
-
|
|
178
|
+
base64_cert = cert_element.text
|
|
179
|
+
cert_text = Base64.decode64(base64_cert)
|
|
180
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
|
236
181
|
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
|
|
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
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
357
|
-
cert
|
|
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
|
|
402
|
-
|
|
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
|
-
|
|
271
|
+
[]
|
|
424
272
|
end
|
|
425
273
|
end
|
|
426
274
|
|