spid-es 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/lib/xml_security.rb DELETED
@@ -1,165 +0,0 @@
1
- # The contents of this file are subject to the terms
2
- # of the Common Development and Distribution License
3
- # (the License). You may not use this file except in
4
- # compliance with the License.
5
- #
6
- # You can obtain a copy of the License at
7
- # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
- # opensso/legal/CDDLv1.0.txt
9
- # See the License for the specific language governing
10
- # permission and limitations under the License.
11
- #
12
- # When distributing Covered Code, include this CDDL
13
- # Header Notice in each file and include the License file
14
- # at opensso/legal/CDDLv1.0.txt.
15
- # If applicable, add the following below the CDDL Header,
16
- # with the fields enclosed by brackets [] replaced by
17
- # your own identifying information:
18
- # "Portions Copyrighted [year] [name of copyright owner]"
19
- #
20
- # $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
21
- #
22
- # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
- # Portions Copyrighted 2007 Todd W Saxton.
24
-
25
- require 'rubygems'
26
- require "rexml/document"
27
- require "rexml/xpath"
28
- require "openssl"
29
- require 'nokogiri'
30
- require "digest/sha1"
31
- require "digest/sha2"
32
- require "spid/ruby-saml/validation_error"
33
-
34
- module XMLSecurity
35
-
36
- class SignedDocument < REXML::Document
37
- C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
38
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
39
-
40
- attr_accessor :signed_element_id, :sig_element, :noko_sig_element
41
-
42
- def initialize(response)
43
- super(response)
44
- extract_signed_element_id
45
- end
46
-
47
- def validate(idp_cert_fingerprint, soft = true)
48
- # get cert from response
49
- cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
50
- base64_cert = cert_element.text
51
- cert_text = Base64.decode64(base64_cert)
52
- cert = OpenSSL::X509::Certificate.new(cert_text)
53
-
54
- # check cert matches registered idp cert
55
- fingerprint = Digest::SHA1.hexdigest(cert.to_der)
56
-
57
- if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
- return soft ? false : (raise Spid::Saml::ValidationError.new("Fingerprint mismatch"))
59
- end
60
-
61
- validate_doc(base64_cert, soft)
62
- end
63
-
64
- def validate_doc(base64_cert, soft = true)
65
- # validate references
66
-
67
- # check for inclusive namespaces
68
- inclusive_namespaces = extract_inclusive_namespaces
69
-
70
- document = Nokogiri.parse(self.to_s)
71
-
72
- # store and remove signature node
73
- self.sig_element ||= begin
74
- element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>DSIG})
75
- element.remove
76
- end
77
-
78
-
79
- # verify signature
80
- signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
81
- self.noko_sig_element ||= document.at_xpath('//ds:Signature', 'ds' => DSIG)
82
- noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
83
- canon_algorithm = canon_algorithm REXML::XPath.first(sig_element, '//ds:CanonicalizationMethod')
84
- canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
85
- noko_sig_element.remove
86
-
87
- # check digests
88
- REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
89
- uri = ref.attributes.get_attribute("URI").value
90
-
91
- hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
92
- canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod')
93
- canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces).gsub('&','&amp;')
94
-
95
- digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
96
-
97
- hash = digest_algorithm.digest(canon_hashed_element)
98
- digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
99
-
100
- unless digests_match?(hash, digest_value)
101
- return soft ? false : (raise Spid::Saml::ValidationError.new("Digest mismatch"))
102
- end
103
- end
104
-
105
- base64_signature = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
106
- signature = Base64.decode64(base64_signature)
107
-
108
- # get certificate object
109
- cert_text = Base64.decode64(base64_cert)
110
- cert = OpenSSL::X509::Certificate.new(cert_text)
111
-
112
- # signature method
113
- signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
114
-
115
- unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
116
- return soft ? false : (raise Spid::Saml::ValidationError.new("Key validation error"))
117
- end
118
-
119
- return true
120
- end
121
-
122
- private
123
-
124
- def digests_match?(hash, digest_value)
125
- hash == digest_value
126
- end
127
-
128
- def extract_signed_element_id
129
- reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
130
- self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
131
- end
132
-
133
- def canon_algorithm(element)
134
- algorithm = element.attribute('Algorithm').value if element
135
- case algorithm
136
- when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
137
- when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
138
- when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
139
- else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
140
- end
141
- end
142
-
143
- def algorithm(element)
144
- algorithm = element.attribute("Algorithm").value if element
145
- algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
146
- case algorithm
147
- when 256 then OpenSSL::Digest::SHA256
148
- when 384 then OpenSSL::Digest::SHA384
149
- when 512 then OpenSSL::Digest::SHA512
150
- else
151
- OpenSSL::Digest::SHA1
152
- end
153
- end
154
-
155
- def extract_inclusive_namespaces
156
- if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
157
- prefix_list = element.attributes.get_attribute("PrefixList").value
158
- prefix_list.split(" ")
159
- else
160
- []
161
- end
162
- end
163
-
164
- end
165
- end
@@ -1,374 +0,0 @@
1
- # The contents of this file are subject to the terms
2
- # of the Common Development and Distribution License
3
- # (the License). You may not use this file except in
4
- # compliance with the License.
5
- #
6
- # You can obtain a copy of the License at
7
- # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
- # opensso/legal/CDDLv1.0.txt
9
- # See the License for the specific language governing
10
- # permission and limitations under the License.
11
- #
12
- # When distributing Covered Code, include this CDDL
13
- # Header Notice in each file and include the License file
14
- # at opensso/legal/CDDLv1.0.txt.
15
- # If applicable, add the following below the CDDL Header,
16
- # with the fields enclosed by brackets [] replaced by
17
- # your own identifying information:
18
- # "Portions Copyrighted [year] [name of copyright owner]"
19
- #
20
- # $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
21
- #
22
- # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
- # Portions Copyrighted 2007 Todd W Saxton.
24
-
25
- require 'rubygems'
26
- require "rexml/document"
27
- require "rexml/xpath"
28
- require "openssl"
29
- require 'nokogiri'
30
- require "digest/sha1"
31
- require "digest/sha2"
32
- require "spid/ruby-saml/error_handling"
33
-
34
- module XMLSecurityNew
35
-
36
- class BaseDocument < REXML::Document
37
- REXML::Document::entity_expansion_limit = 0
38
-
39
- C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
40
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
41
- NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
42
- Nokogiri::XML::ParseOptions::NONET
43
-
44
- def canon_algorithm(element)
45
- algorithm = element
46
- if algorithm.is_a?(REXML::Element)
47
- algorithm = element.attribute('Algorithm').value
48
- end
49
-
50
- case algorithm
51
- when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
52
- "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
53
- Nokogiri::XML::XML_C14N_1_0
54
- when "http://www.w3.org/2006/12/xml-c14n11",
55
- "http://www.w3.org/2006/12/xml-c14n11#WithComments"
56
- Nokogiri::XML::XML_C14N_1_1
57
- else
58
- Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
59
- end
60
- Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
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
-
80
- end
81
-
82
- 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'
91
- ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
92
- INC_PREFIX_LIST = "#default samlp saml2p saml ds xs xsi md"
93
-
94
- attr_accessor :uuid
95
-
96
- def uuid
97
- @uuid ||= begin
98
- document.root.nil? ? nil : document.root.attributes['ID']
99
- end
100
- end
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>
117
- def sign_document(private_key, certificate, signature_method = RSA_SHA256, digest_method = SHA256)
118
- noko = Nokogiri::XML(self.to_s) do |config|
119
- config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
120
- end
121
-
122
- signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
123
- signed_info_element = signature_element.add_element("ds:SignedInfo")
124
- signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
125
- signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
126
-
127
- # Add Reference
128
- reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
129
-
130
- # Add Transforms
131
- transforms_element = reference_element.add_element("ds:Transforms")
132
- 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})
135
-
136
- 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
- #canon_doc = noko.canonicalize(canon_algorithm(C14N))
140
- reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
141
-
142
- # add SignatureValue
143
- noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
144
- config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
145
- end
146
-
147
- noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
148
- canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
149
-
150
- signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
151
- signature_element.add_element("ds:SignatureValue").text = signature.to_s.gsub(/\n/, "").gsub(/\t/, "")
152
-
153
- # add KeyInfo
154
- key_info_element = signature_element.add_element("ds:KeyInfo")
155
- x509_element = key_info_element.add_element("ds:X509Data")
156
- x509_cert_element = x509_element.add_element("ds:X509Certificate")
157
- if certificate.is_a?(String)
158
- certificate = OpenSSL::X509::Certificate.new(certificate)
159
- end
160
- x509_cert_element.text = Base64.encode64(certificate.to_der).to_s.gsub(/\n/, "").gsub(/\t/, "")
161
-
162
- # add the signature
163
- # issuer_element = self.elements["//saml:Issuer"]
164
- # if issuer_element
165
- # self.root.insert_after issuer_element, signature_element
166
- # else
167
- # if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
168
- # self.root.insert_before sp_sso_descriptor, signature_element
169
- # else
170
- # self.root.add_element(signature_element)
171
- # end
172
- # end
173
- #imposto fissa la posizione in testa
174
- sp_sso_descriptor = self.root.elements["md:SPSSODescriptor"]
175
- self.root.insert_before sp_sso_descriptor, signature_element
176
-
177
- end
178
-
179
- protected
180
-
181
- def compute_signature(private_key, signature_algorithm, document)
182
- Base64.encode64(private_key.sign(signature_algorithm, document)).to_s.gsub(/\n/, "").gsub(/\t/, "")
183
- end
184
-
185
- def compute_digest(document, digest_algorithm)
186
- digest = digest_algorithm.digest(document)
187
- Base64.encode64(digest).strip!
188
- end
189
-
190
- end
191
-
192
- class SignedDocument < BaseDocument
193
- include Spid::Saml::ErrorHandling
194
-
195
- attr_accessor :signed_element_id
196
-
197
- def initialize(response, errors = [])
198
- super(response)
199
- @errors = errors
200
- end
201
-
202
- def signed_element_id
203
- @signed_element_id ||= extract_signed_element_id
204
- end
205
-
206
- def validate_document(idp_cert_fingerprint, soft = true, options = {})
207
- # get cert from response
208
- cert_element = REXML::XPath.first(
209
- self,
210
- "//ds:X509Certificate",
211
- { "ds"=>DSIG }
212
- )
213
-
214
- if cert_element
215
- base64_cert = cert_element.text
216
- cert_text = Base64.decode64(base64_cert)
217
- begin
218
- cert = OpenSSL::X509::Certificate.new(cert_text)
219
- rescue OpenSSL::X509::CertificateError => e
220
- return append_error("Certificate Error", soft)
221
- end
222
-
223
- if options[:fingerprint_alg]
224
- fingerprint_alg = XMLSecurityNew::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
225
- else
226
- fingerprint_alg = OpenSSL::Digest::SHA1.new
227
- end
228
- fingerprint = fingerprint_alg.hexdigest(cert.to_der)
229
-
230
- # check cert matches registered idp cert
231
- if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
232
- @errors << "Fingerprint mismatch"
233
- return append_error("Fingerprint mismatch", soft)
234
- end
235
- else
236
- if options[:cert]
237
- base64_cert = Base64.encode64(options[:cert].to_pem)
238
- else
239
- if soft
240
- return false
241
- else
242
- return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft)
243
- end
244
- end
245
- end
246
- validate_signature(base64_cert, soft)
247
- end
248
-
249
- def validate_signature(base64_cert, soft = true)
250
-
251
- document = Nokogiri::XML(self.to_s) do |config|
252
- config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
253
- end
254
-
255
- # create a rexml document
256
- @working_copy ||= REXML::Document.new(self.to_s).root
257
-
258
- # get signature node
259
- sig_element = REXML::XPath.first(
260
- @working_copy,
261
- "//ds:Signature",
262
- {"ds"=>DSIG}
263
- )
264
-
265
- # signature method
266
- sig_alg_value = REXML::XPath.first(
267
- sig_element,
268
- "./ds:SignedInfo/ds:SignatureMethod",
269
- {"ds"=>DSIG}
270
- )
271
- signature_algorithm = algorithm(sig_alg_value)
272
-
273
- # get signature
274
- base64_signature = REXML::XPath.first(
275
- sig_element,
276
- "./ds:SignatureValue",
277
- {"ds" => DSIG}
278
- ).text
279
- signature = Base64.decode64(base64_signature)
280
-
281
- # canonicalization method
282
- canon_algorithm = canon_algorithm REXML::XPath.first(
283
- sig_element,
284
- './ds:SignedInfo/ds:CanonicalizationMethod',
285
- 'ds' => DSIG
286
- )
287
-
288
- noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
289
- noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
290
-
291
- canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
292
- noko_sig_element.remove
293
-
294
- # get inclusive namespaces
295
- inclusive_namespaces = extract_inclusive_namespaces
296
-
297
- # check digests
298
- ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
299
- uri = ref.attributes.get_attribute("URI").value
300
-
301
- hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
302
-
303
- canon_algorithm = canon_algorithm REXML::XPath.first(
304
- ref,
305
- '//ds:CanonicalizationMethod',
306
- { "ds" => DSIG }
307
- )
308
- canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
309
-
310
- digest_algorithm = algorithm(REXML::XPath.first(
311
- ref,
312
- "//ds:DigestMethod",
313
- { "ds" => DSIG }
314
- ))
315
- hash = digest_algorithm.digest(canon_hashed_element)
316
- encoded_digest_value = REXML::XPath.first(
317
- ref,
318
- "//ds:DigestValue",
319
- { "ds" => DSIG }
320
- ).text
321
- digest_value = Base64.decode64(encoded_digest_value)
322
-
323
- unless digests_match?(hash, digest_value)
324
- @errors << "Digest mismatch"
325
- return append_error("Digest mismatch", soft)
326
- end
327
-
328
- # get certificate object
329
- cert_text = Base64.decode64(base64_cert)
330
- cert = OpenSSL::X509::Certificate.new(cert_text)
331
-
332
- # verify signature
333
- unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
334
- return append_error("Key validation error", soft)
335
- end
336
-
337
- return true
338
- end
339
-
340
- private
341
-
342
- def digests_match?(hash, digest_value)
343
- hash == digest_value
344
- end
345
-
346
- def extract_signed_element_id
347
- reference_element = REXML::XPath.first(
348
- self,
349
- "//ds:Signature/ds:SignedInfo/ds:Reference",
350
- {"ds"=>DSIG}
351
- )
352
-
353
- return nil if reference_element.nil?
354
-
355
- sei = reference_element.attribute("URI").value[1..-1]
356
- sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
357
- end
358
-
359
- def extract_inclusive_namespaces
360
- element = REXML::XPath.first(
361
- self,
362
- "//ec:InclusiveNamespaces",
363
- { "ec" => C14N }
364
- )
365
- if element
366
- prefix_list = element.attributes.get_attribute("PrefixList").value
367
- prefix_list.split(" ")
368
- else
369
- nil
370
- end
371
- end
372
-
373
- end
374
- end