spid-es 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- M2ZkYTA0N2Y4ODYwZjhmOTEzY2ZmMWYwNjc3MWZkOTI5NzE3OGE0Nw==
4
+ OGQ4MzVkZDM3MzIwYjA2ZDc5M2YwMzExMGM5OWFiMzkyNDAwOGM1ZA==
5
5
  data.tar.gz: !binary |-
6
- NDhmODhjNjVjZDBjYWY4ZGZhZWJiMDg3MmQxMzBhNTQ5YjlkNWI2MQ==
6
+ NDQ1ZDQ2MmE3ZDhkNWExYzQxY2FkZTBmMTBhNTAyNzBjMTAyNTkyMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZWIxMWE3MDk0MmFlODJiYmY4MDI3OWMzOGQ5NzA0Mzk1ZGNjZDUwZTAxZDUx
10
- M2Q4MDczNjNjOGE2ZTNkYTM3NzE3NTFjZmQ1NGQ0ZWM3MDZkZGMyMmMxMGVi
11
- MWY3ZTczOTVjZmQ2NmFkMmI1MGRhZTRlMTNmNmExOTg1NDVlNDg=
9
+ YjEzM2JjZDdlNTljZWY0OWU5ZTY4ZWEyZjBhZjgzODAyYjVkMDNlZWFkYjEy
10
+ NjZjY2NlOGY2NGFmYzA4ODY4MTM2YzNhMGE4ZmIyYzhkN2VmYjNmMTA3MmQ3
11
+ ZWIwOGMyMjU1OWE3OWM3MmRhZjZjZTRlMzNmZmJmYzg0Mzc3MzE=
12
12
  data.tar.gz: !binary |-
13
- NTZlOWI4OTRiYmZmYTU2OGIzMjhiYWM2NzRjOGE0ZjI4MGE0MjRkMWQyNWU2
14
- NzMzMzVhNWJlMDE0N2JlMzUyZGMzY2I2NTY5NzhiZmZiMzhlNzJlNGU3YTVh
15
- ZDIxMTk4YjNiNWIyNGEzMDA5MDRiMTQwYzg3ZTFlZDI5ZDliOTg=
13
+ MTM3NWQ5ZDI3ZmMxYzY5MmE2OTkzMTdkNzBjMTI2OTU5MGI1YzE2NWQxZmZh
14
+ YWI3ODA2MjY3YjQzYTdjN2JjZDg5OTgxZTQwOTlhNzY2Y2FlZTFiN2VkOGVh
15
+ NDQzN2JhODJlYmQ1MDcwY2UwNmYyNjZkM2MxMWFjMDkzZWM5MzY=
@@ -0,0 +1,27 @@
1
+ require "spid/ruby-saml/validation_error"
2
+
3
+ module Spid
4
+ module Saml
5
+ module ErrorHandling
6
+ attr_accessor :errors
7
+
8
+ # Append the cause to the errors array, and based on the value of soft, return false or raise
9
+ # an exception. soft_override is provided as a means of overriding the object's notion of
10
+ # soft for just this invocation.
11
+ def append_error(error_msg, soft_override = nil)
12
+ @errors << error_msg
13
+
14
+ unless soft_override.nil? ? soft : soft_override
15
+ raise ValidationError.new(error_msg)
16
+ end
17
+
18
+ false
19
+ end
20
+
21
+ # Reset the errors array
22
+ def reset_errors!
23
+ @errors = []
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,188 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'uuid'
3
+ else
4
+ require 'securerandom'
5
+ end
6
+
7
+ module Spid
8
+ module Saml
9
+
10
+ # SAML2 Auxiliary class
11
+ #
12
+ class Utils
13
+ @@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
14
+
15
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
16
+ XENC = "http://www.w3.org/2001/04/xmlenc#"
17
+
18
+ # Return a properly formatted x509 certificate
19
+ #
20
+ # @param cert [String] The original certificate
21
+ # @return [String] The formatted certificate
22
+ #
23
+ def self.format_cert(cert)
24
+ # don't try to format an encoded certificate or if is empty or nil
25
+ return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
26
+
27
+ cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
28
+ cert = cert.gsub(/[\n\r\s]/, "")
29
+ cert = cert.scan(/.{1,64}/)
30
+ cert = cert.join("\n")
31
+ "-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
32
+ end
33
+
34
+ # Return a properly formatted private key
35
+ #
36
+ # @param key [String] The original private key
37
+ # @return [String] The formatted private key
38
+ #
39
+ def self.format_private_key(key)
40
+ # don't try to format an encoded private key or if is empty
41
+ return key if key.nil? || key.empty? || key.match(/\x0d/)
42
+
43
+ # is this an rsa key?
44
+ rsa_key = key.match("RSA PRIVATE KEY")
45
+ key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
46
+ key = key.gsub(/[\n\r\s]/, "")
47
+ key = key.scan(/.{1,64}/)
48
+ key = key.join("\n")
49
+ key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
50
+ "-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
51
+ end
52
+
53
+ # Build the Query String signature that will be used in the HTTP-Redirect binding
54
+ # to generate the Signature
55
+ # @param params [Hash] Parameters to build the Query String
56
+ # @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
57
+ # @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
58
+ # @option params [String] :relay_state The RelayState parameter
59
+ # @option params [String] :sig_alg The SigAlg parameter
60
+ # @return [String] The Query String
61
+ #
62
+ def self.build_query(params)
63
+ type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}
64
+
65
+ url_string = "#{type}=#{CGI.escape(data)}"
66
+ url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
67
+ url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
68
+ end
69
+
70
+ # Validate the Signature parameter sent on the HTTP-Redirect binding
71
+ # @param params [Hash] Parameters to be used in the validation process
72
+ # @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
73
+ # @option params [String] sig_alg The SigAlg parameter
74
+ # @option params [String] signature The Signature parameter (base64 encoded)
75
+ # @option params [String] query_string The full GET Query String to be compared
76
+ # @return [Boolean] True if the Signature is valid, False otherwise
77
+ #
78
+ def self.verify_signature(params)
79
+ cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}
80
+ signature_algorithm = XMLSecurityNew::BaseDocument.new.algorithm(sig_alg)
81
+ return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
82
+ end
83
+
84
+ # Build the status error message
85
+ # @param status_code [String] StatusCode value
86
+ # @param status_message [Strig] StatusMessage value
87
+ # @return [String] The status error message
88
+ def self.status_error_msg(error_msg, status_code = nil, status_message = nil)
89
+ unless status_code.nil?
90
+ printable_code = status_code.split(':').last
91
+ error_msg << ', was ' + printable_code
92
+ end
93
+
94
+ unless status_message.nil?
95
+ error_msg << ' -> ' + status_message
96
+ end
97
+
98
+ error_msg
99
+ end
100
+
101
+ # Obtains the decrypted string from an Encrypted node element in XML
102
+ # @param encrypted_node [REXML::Element] The Encrypted element
103
+ # @param private_key [OpenSSL::PKey::RSA] The Service provider private key
104
+ # @return [String] The decrypted data
105
+ def self.decrypt_data(encrypted_node, private_key)
106
+ encrypt_data = REXML::XPath.first(
107
+ encrypted_node,
108
+ "./xenc:EncryptedData",
109
+ { 'xenc' => XENC }
110
+ )
111
+ symmetric_key = retrieve_symmetric_key(encrypt_data, private_key)
112
+ cipher_value = REXML::XPath.first(
113
+ encrypt_data,
114
+ "//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue",
115
+ { 'xenc' => XENC }
116
+ )
117
+ node = Base64.decode64(cipher_value.text)
118
+ encrypt_method = REXML::XPath.first(
119
+ encrypt_data,
120
+ "//xenc:EncryptedData/xenc:EncryptionMethod",
121
+ { 'xenc' => XENC }
122
+ )
123
+ algorithm = encrypt_method.attributes['Algorithm']
124
+ retrieve_plaintext(node, symmetric_key, algorithm)
125
+ end
126
+
127
+ # Obtains the symmetric key from the EncryptedData element
128
+ # @param encrypt_data [REXML::Element] The EncryptedData element
129
+ # @param private_key [OpenSSL::PKey::RSA] The Service provider private key
130
+ # @return [String] The symmetric key
131
+ def self.retrieve_symmetric_key(encrypt_data, private_key)
132
+ encrypted_key = REXML::XPath.first(
133
+ encrypt_data,
134
+ "//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey or \
135
+ //xenc:EncryptedKey[@Id=substring-after(//xenc:EncryptedData/ds:KeyInfo/ds:RetrievalMethod/@URI, '#')]",
136
+ { "ds" => DSIG, "xenc" => XENC }
137
+ )
138
+ encrypted_symmetric_key_element = REXML::XPath.first(
139
+ encrypted_key,
140
+ "./xenc:CipherData/xenc:CipherValue",
141
+ { "ds" => DSIG, "xenc" => XENC }
142
+ )
143
+ cipher_text = Base64.decode64(encrypted_symmetric_key_element.text)
144
+ encrypt_method = REXML::XPath.first(
145
+ encrypted_key,
146
+ "./xenc:EncryptionMethod",
147
+ {"ds" => DSIG, "xenc" => XENC }
148
+ )
149
+ algorithm = encrypt_method.attributes['Algorithm']
150
+ retrieve_plaintext(cipher_text, private_key, algorithm)
151
+ end
152
+
153
+ # Obtains the deciphered text
154
+ # @param cipher_text [String] The ciphered text
155
+ # @param symmetric_key [String] The symetric key used to encrypt the text
156
+ # @param algorithm [String] The encrypted algorithm
157
+ # @return [String] The deciphered text
158
+ def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
159
+ case algorithm
160
+ when 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' then cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
161
+ when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
162
+ when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
163
+ when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
164
+ when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
165
+ when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
166
+ end
167
+
168
+ if cipher
169
+ iv_len = cipher.iv_len
170
+ data = cipher_text[iv_len..-1]
171
+ cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
172
+ assertion_plaintext = cipher.update(data)
173
+ assertion_plaintext << cipher.final
174
+ elsif rsa
175
+ rsa.private_decrypt(cipher_text)
176
+ elsif oaep
177
+ oaep.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
178
+ else
179
+ cipher_text
180
+ end
181
+ end
182
+
183
+ def self.uuid
184
+ RUBY_VERSION < '1.9' ? "_#{@@uuid_generator.generate}" : "_#{SecureRandom.uuid}"
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,368 @@
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
+ end
61
+
62
+ def algorithm(element)
63
+ algorithm = element
64
+ if algorithm.is_a?(REXML::Element)
65
+ algorithm = element.attribute("Algorithm").value
66
+ end
67
+
68
+ algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
69
+
70
+ case algorithm
71
+ when 256 then OpenSSL::Digest::SHA256
72
+ when 384 then OpenSSL::Digest::SHA384
73
+ when 512 then OpenSSL::Digest::SHA512
74
+ else
75
+ OpenSSL::Digest::SHA1
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ class Document < BaseDocument
82
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
83
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
84
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
85
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
86
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
87
+ SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
88
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
89
+ SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
90
+ ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
91
+ INC_PREFIX_LIST = "#default samlp saml2p saml ds xs xsi md"
92
+
93
+ attr_accessor :uuid
94
+
95
+ def uuid
96
+ @uuid ||= begin
97
+ document.root.nil? ? nil : document.root.attributes['ID']
98
+ end
99
+ end
100
+
101
+ #<Signature>
102
+ #<SignedInfo>
103
+ #<CanonicalizationMethod />
104
+ #<SignatureMethod />
105
+ #<Reference>
106
+ #<Transforms>
107
+ #<DigestMethod>
108
+ #<DigestValue>
109
+ #</Reference>
110
+ #<Reference /> etc.
111
+ #</SignedInfo>
112
+ #<SignatureValue />
113
+ #<KeyInfo />
114
+ #<Object />
115
+ #</Signature>
116
+ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA256)
117
+ noko = Nokogiri::XML(self.to_s) do |config|
118
+ config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
119
+ end
120
+
121
+ signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
122
+ signed_info_element = signature_element.add_element("ds:SignedInfo")
123
+ signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
124
+ signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
125
+
126
+ # Add Reference
127
+ reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
128
+
129
+ # Add Transforms
130
+ transforms_element = reference_element.add_element("ds:Transforms")
131
+ transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
132
+ c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
133
+ c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
134
+
135
+ digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
136
+ inclusive_namespaces = INC_PREFIX_LIST.split(" ")
137
+ canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
138
+ reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
139
+
140
+ # add SignatureValue
141
+ noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
142
+ config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
143
+ end
144
+
145
+ noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
146
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
147
+
148
+ signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
149
+ signature_element.add_element("ds:SignatureValue").text = signature
150
+
151
+ # add KeyInfo
152
+ key_info_element = signature_element.add_element("ds:KeyInfo")
153
+ x509_element = key_info_element.add_element("ds:X509Data")
154
+ x509_cert_element = x509_element.add_element("ds:X509Certificate")
155
+ if certificate.is_a?(String)
156
+ certificate = OpenSSL::X509::Certificate.new(certificate)
157
+ end
158
+ x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
159
+
160
+ # add the signature
161
+ issuer_element = self.elements["//saml:Issuer"]
162
+ if issuer_element
163
+ self.root.insert_after issuer_element, signature_element
164
+ else
165
+ if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
166
+ self.root.insert_before sp_sso_descriptor, signature_element
167
+ else
168
+ self.root.add_element(signature_element)
169
+ end
170
+ end
171
+ end
172
+
173
+ protected
174
+
175
+ def compute_signature(private_key, signature_algorithm, document)
176
+ Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
177
+ end
178
+
179
+ def compute_digest(document, digest_algorithm)
180
+ digest = digest_algorithm.digest(document)
181
+ Base64.encode64(digest).strip!
182
+ end
183
+
184
+ end
185
+
186
+ class SignedDocument < BaseDocument
187
+ include Spid::Saml::ErrorHandling
188
+
189
+ attr_accessor :signed_element_id
190
+
191
+ def initialize(response, errors = [])
192
+ super(response)
193
+ @errors = errors
194
+ end
195
+
196
+ def signed_element_id
197
+ @signed_element_id ||= extract_signed_element_id
198
+ end
199
+
200
+ def validate_document(idp_cert_fingerprint, soft = true, options = {})
201
+ # get cert from response
202
+ cert_element = REXML::XPath.first(
203
+ self,
204
+ "//ds:X509Certificate",
205
+ { "ds"=>DSIG }
206
+ )
207
+
208
+ if cert_element
209
+ base64_cert = cert_element.text
210
+ cert_text = Base64.decode64(base64_cert)
211
+ begin
212
+ cert = OpenSSL::X509::Certificate.new(cert_text)
213
+ rescue OpenSSL::X509::CertificateError => e
214
+ return append_error("Certificate Error", soft)
215
+ end
216
+
217
+ if options[:fingerprint_alg]
218
+ fingerprint_alg = XMLSecurityNew::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
219
+ else
220
+ fingerprint_alg = OpenSSL::Digest::SHA1.new
221
+ end
222
+ fingerprint = fingerprint_alg.hexdigest(cert.to_der)
223
+
224
+ # check cert matches registered idp cert
225
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
226
+ @errors << "Fingerprint mismatch"
227
+ return append_error("Fingerprint mismatch", soft)
228
+ end
229
+ else
230
+ if options[:cert]
231
+ base64_cert = Base64.encode64(options[:cert].to_pem)
232
+ else
233
+ if soft
234
+ return false
235
+ else
236
+ return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft)
237
+ end
238
+ end
239
+ end
240
+ validate_signature(base64_cert, soft)
241
+ end
242
+
243
+ def validate_signature(base64_cert, soft = true)
244
+
245
+ document = Nokogiri::XML(self.to_s) do |config|
246
+ config.options = XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
247
+ end
248
+
249
+ # create a rexml document
250
+ @working_copy ||= REXML::Document.new(self.to_s).root
251
+
252
+ # get signature node
253
+ sig_element = REXML::XPath.first(
254
+ @working_copy,
255
+ "//ds:Signature",
256
+ {"ds"=>DSIG}
257
+ )
258
+
259
+ # signature method
260
+ sig_alg_value = REXML::XPath.first(
261
+ sig_element,
262
+ "./ds:SignedInfo/ds:SignatureMethod",
263
+ {"ds"=>DSIG}
264
+ )
265
+ signature_algorithm = algorithm(sig_alg_value)
266
+
267
+ # get signature
268
+ base64_signature = REXML::XPath.first(
269
+ sig_element,
270
+ "./ds:SignatureValue",
271
+ {"ds" => DSIG}
272
+ ).text
273
+ signature = Base64.decode64(base64_signature)
274
+
275
+ # canonicalization method
276
+ canon_algorithm = canon_algorithm REXML::XPath.first(
277
+ sig_element,
278
+ './ds:SignedInfo/ds:CanonicalizationMethod',
279
+ 'ds' => DSIG
280
+ )
281
+
282
+ noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
283
+ noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
284
+
285
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
286
+ noko_sig_element.remove
287
+
288
+ # get inclusive namespaces
289
+ inclusive_namespaces = extract_inclusive_namespaces
290
+
291
+ # check digests
292
+ ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
293
+ uri = ref.attributes.get_attribute("URI").value
294
+
295
+ hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
296
+
297
+ canon_algorithm = canon_algorithm REXML::XPath.first(
298
+ ref,
299
+ '//ds:CanonicalizationMethod',
300
+ { "ds" => DSIG }
301
+ )
302
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
303
+
304
+ digest_algorithm = algorithm(REXML::XPath.first(
305
+ ref,
306
+ "//ds:DigestMethod",
307
+ { "ds" => DSIG }
308
+ ))
309
+ hash = digest_algorithm.digest(canon_hashed_element)
310
+ encoded_digest_value = REXML::XPath.first(
311
+ ref,
312
+ "//ds:DigestValue",
313
+ { "ds" => DSIG }
314
+ ).text
315
+ digest_value = Base64.decode64(encoded_digest_value)
316
+
317
+ unless digests_match?(hash, digest_value)
318
+ @errors << "Digest mismatch"
319
+ return append_error("Digest mismatch", soft)
320
+ end
321
+
322
+ # get certificate object
323
+ cert_text = Base64.decode64(base64_cert)
324
+ cert = OpenSSL::X509::Certificate.new(cert_text)
325
+
326
+ # verify signature
327
+ unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
328
+ return append_error("Key validation error", soft)
329
+ end
330
+
331
+ return true
332
+ end
333
+
334
+ private
335
+
336
+ def digests_match?(hash, digest_value)
337
+ hash == digest_value
338
+ end
339
+
340
+ def extract_signed_element_id
341
+ reference_element = REXML::XPath.first(
342
+ self,
343
+ "//ds:Signature/ds:SignedInfo/ds:Reference",
344
+ {"ds"=>DSIG}
345
+ )
346
+
347
+ return nil if reference_element.nil?
348
+
349
+ sei = reference_element.attribute("URI").value[1..-1]
350
+ sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
351
+ end
352
+
353
+ def extract_inclusive_namespaces
354
+ element = REXML::XPath.first(
355
+ self,
356
+ "//ec:InclusiveNamespaces",
357
+ { "ec" => C14N }
358
+ )
359
+ if element
360
+ prefix_list = element.attributes.get_attribute("PrefixList").value
361
+ prefix_list.split(" ")
362
+ else
363
+ nil
364
+ end
365
+ end
366
+
367
+ end
368
+ end
data/spid-es.gemspec CHANGED
@@ -2,7 +2,7 @@ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'spid-es'
5
- s.version = '0.0.1'
5
+ s.version = '0.0.2'
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Fabiano Pavan"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spid-es
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fabiano Pavan
@@ -97,6 +97,7 @@ files:
97
97
  - lib/spid-es.rb
98
98
  - lib/spid/ruby-saml/authrequest.rb
99
99
  - lib/spid/ruby-saml/coding.rb
100
+ - lib/spid/ruby-saml/error_handling.rb
100
101
  - lib/spid/ruby-saml/logging.rb
101
102
  - lib/spid/ruby-saml/logout_request.rb
102
103
  - lib/spid/ruby-saml/logout_response.rb
@@ -104,9 +105,11 @@ files:
104
105
  - lib/spid/ruby-saml/request.rb
105
106
  - lib/spid/ruby-saml/response.rb
106
107
  - lib/spid/ruby-saml/settings.rb
108
+ - lib/spid/ruby-saml/utils.rb
107
109
  - lib/spid/ruby-saml/validation_error.rb
108
110
  - lib/spid/ruby-saml/version.rb
109
111
  - lib/xml_security.rb
112
+ - lib/xml_security_new.rb
110
113
  - spid-es.gemspec
111
114
  - test/certificates/certificate1
112
115
  - test/logoutrequest_test.rb