spid-es 0.0.1 → 0.0.2

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 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