ciam-es 0.0.1

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/Gemfile +4 -0
  4. data/README.md +127 -0
  5. data/ciam-es.gemspec +23 -0
  6. data/lib/ciam-es.rb +14 -0
  7. data/lib/ciam/ruby-saml/authrequest.rb +206 -0
  8. data/lib/ciam/ruby-saml/coding.rb +34 -0
  9. data/lib/ciam/ruby-saml/error_handling.rb +27 -0
  10. data/lib/ciam/ruby-saml/logging.rb +26 -0
  11. data/lib/ciam/ruby-saml/logout_request.rb +126 -0
  12. data/lib/ciam/ruby-saml/logout_response.rb +132 -0
  13. data/lib/ciam/ruby-saml/metadata.rb +509 -0
  14. data/lib/ciam/ruby-saml/request.rb +81 -0
  15. data/lib/ciam/ruby-saml/response.rb +683 -0
  16. data/lib/ciam/ruby-saml/settings.rb +89 -0
  17. data/lib/ciam/ruby-saml/utils.rb +225 -0
  18. data/lib/ciam/ruby-saml/validation_error.rb +7 -0
  19. data/lib/ciam/ruby-saml/version.rb +5 -0
  20. data/lib/ciam/xml_security.rb +166 -0
  21. data/lib/ciam/xml_security_new.rb +373 -0
  22. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  23. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  24. data/lib/schemas/xenc_schema.xsd +146 -0
  25. data/lib/schemas/xmldsig_schema.xsd +318 -0
  26. data/test/certificates/certificate1 +12 -0
  27. data/test/logoutrequest_test.rb +98 -0
  28. data/test/request_test.rb +53 -0
  29. data/test/response_test.rb +219 -0
  30. data/test/responses/adfs_response_sha1.xml +46 -0
  31. data/test/responses/adfs_response_sha256.xml +46 -0
  32. data/test/responses/adfs_response_sha384.xml +46 -0
  33. data/test/responses/adfs_response_sha512.xml +46 -0
  34. data/test/responses/no_signature_ns.xml +48 -0
  35. data/test/responses/open_saml_response.xml +56 -0
  36. data/test/responses/response1.xml.base64 +1 -0
  37. data/test/responses/response2.xml.base64 +79 -0
  38. data/test/responses/response3.xml.base64 +66 -0
  39. data/test/responses/response4.xml.base64 +93 -0
  40. data/test/responses/response5.xml.base64 +102 -0
  41. data/test/responses/response_with_ampersands.xml +139 -0
  42. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  43. data/test/responses/simple_saml_php.xml +71 -0
  44. data/test/responses/wrapped_response_2.xml.base64 +150 -0
  45. data/test/settings_test.rb +43 -0
  46. data/test/test_helper.rb +65 -0
  47. data/test/xml_security_test.rb +123 -0
  48. metadata +145 -0
@@ -0,0 +1,89 @@
1
+ require_relative "../xml_security_new"
2
+
3
+ module Ciam
4
+ module Saml
5
+ class Settings
6
+
7
+ attr_accessor :sp_name_qualifier, :sp_name_identifier, :sp_cert, :sp_external_consumer_cert, :sp_private_key, :metadata_signed, :requested_attribute,:requested_attribute_eidas_min, :requested_attribute_eidas_full, :organization
8
+ attr_accessor :idp_entity_id, :idp_sso_target_url, :idp_cert_fingerprint, :idp_cert, :idp_slo_target_url, :idp_metadata, :idp_metadata_ttl, :idp_name_qualifier
9
+ attr_accessor :assertion_consumer_service_binding, :assertion_consumer_service_url, :assertion_consumer_service_index, :attribute_consuming_service_index, :hash_assertion_consumer
10
+ attr_accessor :name_identifier_value, :name_identifier_format
11
+ attr_accessor :sessionindex, :issuer, :destination_service_url, :authn_context, :requester_identificator
12
+ attr_accessor :single_logout_service_url, :single_logout_service_binding, :single_logout_destination
13
+ attr_accessor :skip_validation, :aggregato, :hash_aggregatore
14
+
15
+ def initialize(config = {})
16
+ config.each do |k,v|
17
+ acc = "#{k.to_s}=".to_sym
18
+ self.send(acc, v) if self.respond_to? acc
19
+ end
20
+
21
+ # Set some sane default values on a few options
22
+ self.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
23
+ self.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
24
+ # Default cache TTL for metadata is 1 day
25
+ self.idp_metadata_ttl = 86400
26
+ end
27
+
28
+
29
+ def get_fingerprint
30
+ idp_cert_fingerprint || begin
31
+ idp_cert = get_idp_cert
32
+ if idp_cert
33
+ fingerprint_alg = Ciam::XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
34
+ fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
35
+ end
36
+ end
37
+ end
38
+
39
+ # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
40
+ #
41
+ def get_idp_cert
42
+ return nil if idp_cert.nil? || idp_cert.empty?
43
+ #decoded_content = Base64.decode64(File.read(idp_cert))
44
+ #formatted_cert = Ciam::Saml::Utils.format_cert(idp_cert)
45
+ OpenSSL::X509::Certificate.new(File.read(idp_cert))
46
+ end
47
+
48
+ # def get_sp_cert
49
+ # return nil if certificate.nil? || certificate.empty?
50
+
51
+ # formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
52
+ # OpenSSL::X509::Certificate.new(formatted_cert)
53
+ # end
54
+
55
+ # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
56
+ #
57
+ #Questo metodo e' stato generalizzato sotto
58
+ # def get_sp_cert
59
+ # return nil if sp_cert.nil? || sp_cert.empty?
60
+ # #decoded_content = Base64.decode64(File.read(sp_cert))
61
+ # formatted_cert = Ciam::Saml::Utils.format_cert(sp_cert)
62
+ # OpenSSL::X509::Certificate.new(File.read(sp_cert))
63
+ # end
64
+
65
+ def get_cert(cert)
66
+ return nil if cert.nil? || cert.empty?
67
+ #decoded_content = Base64.decode64(File.read(cert))
68
+ formatted_cert = Ciam::Saml::Utils.format_cert(cert)
69
+ OpenSSL::X509::Certificate.new(File.read(cert))
70
+ end
71
+
72
+
73
+
74
+ # @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
75
+ #
76
+ def get_sp_key
77
+ return nil if sp_private_key.nil? || sp_private_key.empty?
78
+
79
+ #formatted_private_key = Ciam::Saml::Utils.format_private_key(sp_private_key)
80
+ OpenSSL::PKey::RSA.new(File.read(sp_private_key))
81
+ end
82
+
83
+
84
+
85
+
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,225 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'uuid'
3
+ else
4
+ require 'securerandom'
5
+ end
6
+
7
+ module Ciam
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 = Ciam::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
+
187
+
188
+ # Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
189
+ # then the fully-qualified domain name and the host should performa a case-insensitive match, per the
190
+ # RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the
191
+ # two strings. This maintains the previous functionality.
192
+ # @return [Boolean]
193
+ def self.uri_match?(destination_url, settings_url)
194
+ dest_uri = URI.parse(destination_url)
195
+ acs_uri = URI.parse(settings_url)
196
+
197
+ if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil?
198
+ raise URI::InvalidURIError
199
+ else
200
+ dest_uri.scheme.downcase == acs_uri.scheme.downcase &&
201
+ dest_uri.host.downcase == acs_uri.host.downcase &&
202
+ dest_uri.path == acs_uri.path &&
203
+ dest_uri.query == acs_uri.query
204
+ end
205
+ rescue URI::InvalidURIError
206
+ original_uri_match?(destination_url, settings_url)
207
+ end
208
+
209
+ # If Rails' URI.parse can't match to valid URL, default back to the original matching service.
210
+ # @return [Boolean]
211
+ def self.original_uri_match?(destination_url, settings_url)
212
+ destination_url == settings_url
213
+ end
214
+
215
+ # Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
216
+ # that there all children other than text nodes can be ignored (e.g. comments). If nil is
217
+ # passed, nil will be returned.
218
+ def self.element_text(element)
219
+ element.texts.map(&:value).join if element
220
+ end
221
+
222
+
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,7 @@
1
+ module Ciam
2
+ module Saml
3
+ class ValidationError < Exception
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,5 @@
1
+ module Ciam
2
+ module Saml
3
+ VERSION = '0.6.0'
4
+ end
5
+ end
@@ -0,0 +1,166 @@
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 "ciam/ruby-saml/validation_error"
33
+
34
+ module Ciam
35
+ module XMLSecurity
36
+
37
+ class SignedDocument < REXML::Document
38
+ C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
39
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
40
+
41
+ attr_accessor :signed_element_id, :sig_element, :noko_sig_element
42
+
43
+ def initialize(response)
44
+ super(response)
45
+ extract_signed_element_id
46
+ end
47
+
48
+ def validate(idp_cert_fingerprint, soft = true)
49
+ # get cert from response
50
+ cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
51
+ base64_cert = cert_element.text
52
+ cert_text = Base64.decode64(base64_cert)
53
+ cert = OpenSSL::X509::Certificate.new(cert_text)
54
+
55
+ # check cert matches registered idp cert
56
+ fingerprint = Digest::SHA2.hexdigest(cert.to_der)
57
+
58
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
59
+ return soft ? false : (raise Ciam::Saml::ValidationError.new("Fingerprint mismatch"))
60
+ end
61
+
62
+ validate_doc(base64_cert, soft)
63
+ end
64
+
65
+ def validate_doc(base64_cert, soft = true)
66
+ # validate references
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 Ciam::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 Ciam::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
166
+ end