From 88df3f6e86b32c1328e5df5ef8e237d434f1cee9 Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:30:12 +0900 Subject: [PATCH 1/2] WIP on EC and DSA signing --- lib/onelogin/ruby-saml/authrequest.rb | 6 +- lib/onelogin/ruby-saml/idp_metadata_parser.rb | 2 +- lib/onelogin/ruby-saml/logoutrequest.rb | 6 +- lib/onelogin/ruby-saml/metadata.rb | 2 +- lib/onelogin/ruby-saml/settings.rb | 43 ++- lib/onelogin/ruby-saml/slo_logoutresponse.rb | 7 +- lib/onelogin/ruby-saml/utils.rb | 25 +- lib/xml_security.rb | 263 +++++++++++------- test/idp_metadata_parser_test.rb | 8 +- test/logoutrequest_test.rb | 12 +- test/logoutresponse_test.rb | 4 +- test/request_test.rb | 8 +- test/settings_test.rb | 6 +- test/slo_logoutrequest_test.rb | 10 +- test/slo_logoutresponse_test.rb | 12 +- test/xml_security_test.rb | 40 +-- 16 files changed, 280 insertions(+), 174 deletions(-) diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb index 0aadff2f..426b5c1b 100644 --- a/lib/onelogin/ruby-saml/authrequest.rb +++ b/lib/onelogin/ruby-saml/authrequest.rb @@ -75,14 +75,14 @@ def create_params(settings, params={}) sp_signing_key = settings.get_sp_signing_key if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && sp_signing_key - params['SigAlg'] = settings.security[:signature_method] + params['SigAlg'] = settings.get_sp_signature_method url_string = OneLogin::RubySaml::Utils.build_query( :type => 'SAMLRequest', :data => base64_request, :relay_state => relay_state, :sig_alg => params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -182,7 +182,7 @@ def create_xml_document(settings) def sign_document(document, settings) cert, private_key = settings.get_sp_signing_pair if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && private_key && cert - document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method) end document diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index d4205896..a11a2362 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -389,7 +389,7 @@ def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1 cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate)) - fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new + fingerprint_alg = XMLSecurity::Crypto.hash_algorithm(fingerprint_algorithm).new fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":") end end diff --git a/lib/onelogin/ruby-saml/logoutrequest.rb b/lib/onelogin/ruby-saml/logoutrequest.rb index d2bb21ec..10b7eb59 100644 --- a/lib/onelogin/ruby-saml/logoutrequest.rb +++ b/lib/onelogin/ruby-saml/logoutrequest.rb @@ -72,14 +72,14 @@ def create_params(settings, params={}) sp_signing_key = settings.get_sp_signing_key if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && sp_signing_key - params['SigAlg'] = settings.security[:signature_method] + params['SigAlg'] = settings.get_sp_signature_method url_string = OneLogin::RubySaml::Utils.build_query( :type => 'SAMLRequest', :data => base64_request, :relay_state => relay_state, :sig_alg => params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -141,7 +141,7 @@ def sign_document(document, settings) # embed signature cert, private_key = settings.get_sp_signing_pair if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && private_key && cert - document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method) end document diff --git a/lib/onelogin/ruby-saml/metadata.rb b/lib/onelogin/ruby-saml/metadata.rb index fed96b67..88de690c 100644 --- a/lib/onelogin/ruby-saml/metadata.rb +++ b/lib/onelogin/ruby-saml/metadata.rb @@ -141,7 +141,7 @@ def embed_signature(meta_doc, settings) cert, private_key = settings.get_sp_signing_pair return unless private_key && cert - meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + meta_doc.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method) end def output_xml(meta_doc, pretty_print) diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb index 401732ac..019d4d48 100644 --- a/lib/onelogin/ruby-saml/settings.rb +++ b/lib/onelogin/ruby-saml/settings.rb @@ -172,7 +172,7 @@ def get_fingerprint idp_cert_fingerprint || begin idp_cert = get_idp_cert if idp_cert - fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new + fingerprint_alg = XMLSecurity::Crypto.hash_algorithm(idp_cert_fingerprint_algorithm).new fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":") end end @@ -205,7 +205,7 @@ def get_idp_cert_multi certs end - # @return [Hash>>] + # @return [Hash>>] # Build the SP certificates and private keys from the settings. If # check_sp_cert_expiration is true, only returns certificates and private keys # that are not expired. @@ -225,7 +225,7 @@ def get_sp_certs active_certs.freeze end - # @return [Array] + # @return [Array] # The SP signing certificate and private key. def get_sp_signing_pair get_sp_certs[:signing].first @@ -263,6 +263,43 @@ def get_sp_cert_new node[0] if node end + # @return [String] The XML Signature Algorithm attribute. + # + # This method is intentionally hacky for backwards compatibility of the + # settings.security[:signature_method] parameter. Previously, this parameter + # could have a value such as "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + # which assumes the public key type RSA. To add support for DSA and ECDSA, we will now + # ignore the "rsa-" prefix and only use the "sha256" hash algorithm component. + def get_sp_signature_method + sig_alg = security[:signature_method] || 'sha1' # TODO: change to sha256 by default + hash_alg = sig_alg.to_s.match(/(?:\A|[#_-])(sha\d+)\z/i)[1] + key_alg = case get_sp_signing_key + when OpenSSL::PKey::RSA then 'RSA' + when OpenSSL::PKey::DSA then 'DSA' + when OpenSSL::PKey::EC then 'ECDSA' + else + 'RSA' + # raise ArgumentError.new("Unsupported signing key type: #{get_sp_signing_key.class}") + end + + begin + XMLSecurity::Crypto.const_get("#{key_alg}_#{hash_alg}".upcase) + rescue NameError + raise ArgumentError.new("Unsupported signature method: #{sig_alg}") + end + end + + def get_sp_digest_method + digest_alg = security[:digest_method] || 'sha1' # TODO: change to sha256 by default + alg = digest_alg.to_s.match(/(?:\A|#)(sha\d+)\z/i)[1] + + begin + XMLSecurity::Crypto.const_get(alg.upcase) + rescue NameError + raise ArgumentError.new("Unsupported signature method: #{digest_alg}") + end + end + def idp_binding_from_embed_sign security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect] end diff --git a/lib/onelogin/ruby-saml/slo_logoutresponse.rb b/lib/onelogin/ruby-saml/slo_logoutresponse.rb index c2c73d0c..5c0f02a3 100644 --- a/lib/onelogin/ruby-saml/slo_logoutresponse.rb +++ b/lib/onelogin/ruby-saml/slo_logoutresponse.rb @@ -81,14 +81,14 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}, sp_signing_key = settings.get_sp_signing_key if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key - params['SigAlg'] = settings.security[:signature_method] + params['SigAlg'] = settings.get_sp_signature_method url_string = OneLogin::RubySaml::Utils.build_query( :type => 'SAMLResponse', :data => base64_response, :relay_state => relay_state, :sig_alg => params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -153,12 +153,11 @@ def sign_document(document, settings) # embed signature cert, private_key = settings.get_sp_signing_pair if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert - document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method) end document end - end end end diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 5756e696..6b1eaf20 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -150,12 +150,25 @@ def self.build_cert_object(cert) # Given a private key string, return an OpenSSL::PKey::RSA object. # # @param cert [String] The original private key - # @return [OpenSSL::PKey::RSA] The private key object + # @return [OpenSSL::PKey::PKey] The private key object # def self.build_private_key_object(private_key) return nil if private_key.nil? || private_key.empty? - OpenSSL::PKey::RSA.new(format_private_key(private_key)) + private_key = format_private_key(private_key) + error = nil + + [ OpenSSL::PKey::RSA, + OpenSSL::PKey::DSA, + OpenSSL::PKey::EC ].each do |key_class| + begin + return key_class.new(private_key) + rescue OpenSSL::PKey::PKeyError => e + error ||= e + end + end + + raise error end # Build the Query String signature that will be used in the HTTP-Redirect binding @@ -237,7 +250,7 @@ def self.escape_request_param(param, lowercase_url_encoding) # def self.verify_signature(params) cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]} - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(sig_alg) return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string) end @@ -269,7 +282,7 @@ def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil # Obtains the decrypted string from an Encrypted node element in XML, # given multiple private keys to try. # @param encrypted_node [REXML::Element] The Encrypted element - # @param private_keys [Array] The Service provider private key + # @param private_keys [Array] The Service provider private key # @return [String] The decrypted data def self.decrypt_multi(encrypted_node, private_keys) raise ArgumentError.new('private_keys must be specified') if !private_keys || private_keys.empty? @@ -288,7 +301,7 @@ def self.decrypt_multi(encrypted_node, private_keys) # Obtains the decrypted string from an Encrypted node element in XML # @param encrypted_node [REXML::Element] The Encrypted element - # @param private_key [OpenSSL::PKey::RSA] The Service provider private key + # @param private_key [OpenSSL::PKey::PKey] The Service provider private key # @return [String] The decrypted data def self.decrypt_data(encrypted_node, private_key) encrypt_data = REXML::XPath.first( @@ -314,7 +327,7 @@ def self.decrypt_data(encrypted_node, private_key) # Obtains the symmetric key from the EncryptedData element # @param encrypt_data [REXML::Element] The EncryptedData element - # @param private_key [OpenSSL::PKey::RSA] The Service provider private key + # @param private_key [OpenSSL::PKey::PKey] The Service provider private key # @return [String] The symmetric key def self.retrieve_symmetric_key(encrypt_data, private_key) encrypted_key = REXML::XPath.first( diff --git a/lib/xml_security.rb b/lib/xml_security.rb index 1b1b3228..f2670d84 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -23,73 +23,134 @@ # Portions Copyrighted 2007 Todd W Saxton. require 'rubygems' -require "rexml/document" -require "rexml/xpath" -require "openssl" +require 'rexml/document' +require 'rexml/xpath' +require 'openssl' require 'nokogiri' -require "digest/sha1" -require "digest/sha2" -require "onelogin/ruby-saml/utils" -require "onelogin/ruby-saml/error_handling" +require 'digest/sha1' +require 'digest/sha2' +require 'onelogin/ruby-saml/utils' +require 'onelogin/ruby-saml/error_handling' module XMLSecurity - class BaseDocument < REXML::Document - REXML::Document::entity_expansion_limit = 0 - - C14N = "http://www.w3.org/2001/10/xml-exc-c14n#" - DSIG = "http://www.w3.org/2000/09/xmldsig#" - NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | - Nokogiri::XML::ParseOptions::NONET + module Crypto + extend self + + C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#' + DSIG = 'http://www.w3.org/2000/09/xmldsig#' + RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' + RSA_SHA224 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224' + RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' + RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' + DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1' + DSA_SHA256 = 'http://www.w3.org/2009/xmldsig11#dsa-sha256' + ECDSA_SHA1 = 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1' + ECDSA_SHA224 = 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224' + ECDSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256' + ECDSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384' + ECDSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512' + SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1' + SHA224 = 'http://www.w3.org/2001/04/xmldsig-more#sha224' + SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256' + SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384' + SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512' + ENVELOPED_SIG = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' def canon_algorithm(element) - algorithm = element - if algorithm.is_a?(REXML::Element) - algorithm = element.attribute('Algorithm').value - end - - case algorithm - when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" - Nokogiri::XML::XML_C14N_1_0 - when "http://www.w3.org/2006/12/xml-c14n11", - "http://www.w3.org/2006/12/xml-c14n11#WithComments" - Nokogiri::XML::XML_C14N_1_1 - else - Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 + case get_algorithm_attr(element) + when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", + "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + Nokogiri::XML::XML_C14N_1_0 + when "http://www.w3.org/2006/12/xml-c14n11", + "http://www.w3.org/2006/12/xml-c14n11#WithComments" + Nokogiri::XML::XML_C14N_1_1 + else + Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 end end - def algorithm(element) - algorithm = element - if algorithm.is_a?(REXML::Element) - algorithm = element.attribute("Algorithm").value - end + def signature_algorithm(element) + alg = get_algorithm_attr(element) + + match_data = alg && (alg.downcase.match(/(?:\A|#)(rsa|dsa|ecdsa)-(sha\d+)\z/i) || {}) # TODO: Use &. operator + key_alg = match_data[1] + hash_alg = match_data[2] - algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i + key = case key_alg + when 'rsa' then OpenSSL::PKey::RSA + when 'dsa' then OpenSSL::PKey::DSA + when 'ecdsa' then OpenSSL::PKey::EC + else + # TODO: raise ArgumentError.new("Invalid key algorithm: #{alg}") + OpenSSL::PKey::RSA + end - case algorithm - when 256 then OpenSSL::Digest::SHA256 - when 384 then OpenSSL::Digest::SHA384 - when 512 then OpenSSL::Digest::SHA512 + [key, hash_algorithm(hash_alg)] + end + + def hash_algorithm(element) + alg = get_algorithm_attr(element) + puts alg.inspect + hash_alg = alg && (alg.downcase.match(/(?:\A|[#-])(sha\d+)\z/i) || {})[1] # TODO: Use &. operator + + case hash_alg + when 'sha1' then OpenSSL::Digest::SHA1 + when 'sha224' then OpenSSL::Digest::SHA224 + when 'sha256' then OpenSSL::Digest::SHA256 + when 'sha384' then OpenSSL::Digest::SHA384 + when 'sha512' then OpenSSL::Digest::SHA512 else + # TODO: raise ArgumentError.new("Invalid hash algorithm: #{hash_alg}") OpenSSL::Digest::SHA1 end end + private + + def get_algorithm_attr(element) + if element.is_a?(REXML::Element) + element.attribute('Algorithm').value + elsif element + element + end + end + end + + class BaseDocument < REXML::Document + REXML::Document::entity_expansion_limit = 0 + + # @deprecated Constants moved to Crypto module + C14N = XMLSecurity::Crypto::C14N + DSIG = XMLSecurity::Crypto::DSIG + + NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | + Nokogiri::XML::ParseOptions::NONET end class Document < BaseDocument - RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" - RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" - SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1" - SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256' - SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384" - SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512' - ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" - INC_PREFIX_LIST = "#default samlp saml ds xs xsi md" + INC_PREFIX_LIST = '#default samlp saml ds xs xsi md' + + # @deprecated Constants moved to Crypto module + RSA_SHA1 = XMLSecurity::Crypto::RSA_SHA1 + RSA_SHA224 = XMLSecurity::Crypto::RSA_SHA224 + RSA_SHA256 = XMLSecurity::Crypto::RSA_SHA256 + RSA_SHA384 = XMLSecurity::Crypto::RSA_SHA384 + RSA_SHA512 = XMLSecurity::Crypto::RSA_SHA512 + DSA_SHA1 = XMLSecurity::Crypto::DSA_SHA1 + DSA_SHA256 = XMLSecurity::Crypto::DSA_SHA256 + ECDSA_SHA1 = XMLSecurity::Crypto::ECDSA_SHA1 + ECDSA_SHA224 = XMLSecurity::Crypto::ECDSA_SHA224 + ECDSA_SHA256 = XMLSecurity::Crypto::ECDSA_SHA256 + ECDSA_SHA384 = XMLSecurity::Crypto::ECDSA_SHA384 + ECDSA_SHA512 = XMLSecurity::Crypto::ECDSA_SHA512 + SHA1 = XMLSecurity::Crypto::SHA1 + SHA224 = XMLSecurity::Crypto::SHA224 + SHA256 = XMLSecurity::Crypto::SHA256 + SHA384 = XMLSecurity::Crypto::SHA384 + SHA512 = XMLSecurity::Crypto::SHA512 + ENVELOPED_SIG = XMLSecurity::Crypto::ENVELOPED_SIG attr_writer :uuid @@ -114,14 +175,14 @@ def uuid # # # - def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1) + def sign_document(private_key, certificate, signature_method = XMLSecurity::Crypto::RSA_SHA1, digest_method = XMLSecurity::Crypto::SHA1) noko = Nokogiri::XML(self.to_s) do |config| config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS end - signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG) + signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', XMLSecurity::Crypto::DSIG) signed_info_element = signature_element.add_element("ds:SignedInfo") - signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N}) + signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => XMLSecurity::Crypto::C14N}) signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method}) # Add Reference @@ -129,30 +190,30 @@ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_ # Add Transforms transforms_element = reference_element.add_element("ds:Transforms") - transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG}) - c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N}) - c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST}) + transforms_element.add_element("ds:Transform", {"Algorithm" => XMLSecurity::Crypto::ENVELOPED_SIG}) + c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => XMLSecurity::Crypto::C14N}) + c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => XMLSecurity::Crypto::C14N, "PrefixList" => INC_PREFIX_LIST}) digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method}) inclusive_namespaces = INC_PREFIX_LIST.split(" ") - canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces) - reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element)) + canon_doc = noko.canonicalize(XMLSecurity::Crypto.canon_algorithm(XMLSecurity::Crypto::C14N), inclusive_namespaces) + reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, XMLSecurity::Crypto.hash_algorithm(digest_method_element)) # add SignatureValue noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config| config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS end - noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG) - canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N)) + noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => XMLSecurity::Crypto::DSIG) + canon_string = noko_signed_info_element.canonicalize(XMLSecurity::Crypto.canon_algorithm(XMLSecurity::Crypto::C14N)) - signature = compute_signature(private_key, algorithm(signature_method).new, canon_string) + signature = compute_signature(private_key, XMLSecurity::Crypto.hash_algorithm(signature_method).new, canon_string) signature_element.add_element("ds:SignatureValue").text = signature # add KeyInfo - key_info_element = signature_element.add_element("ds:KeyInfo") - x509_element = key_info_element.add_element("ds:X509Data") - x509_cert_element = x509_element.add_element("ds:X509Certificate") + key_info_element = signature_element.add_element("ds:KeyInfo") + x509_element = key_info_element.add_element("ds:X509Data") + x509_cert_element = x509_element.add_element("ds:X509Certificate") if certificate.is_a?(String) certificate = OpenSSL::X509::Certificate.new(certificate) end @@ -169,17 +230,16 @@ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_ end end - protected + private - def compute_signature(private_key, signature_algorithm, document) - Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "") + def compute_signature(private_key, signature_hash_algorithm, document) + Base64.encode64(private_key.sign(signature_hash_algorithm, document)).gsub(/\n/, "") end def compute_digest(document, digest_algorithm) digest = digest_algorithm.digest(document) Base64.encode64(digest).strip end - end class SignedDocument < BaseDocument @@ -201,7 +261,7 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {}) cert_element = REXML::XPath.first( self, "//ds:X509Certificate", - { "ds"=>DSIG } + { "ds"=>XMLSecurity::Crypto::DSIG } ) if cert_element @@ -214,7 +274,7 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {}) end if options[:fingerprint_alg] - fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new + fingerprint_alg = XMLSecurity::Crypto.hash_algorithm(options[:fingerprint_alg]).new else fingerprint_alg = OpenSSL::Digest.new('SHA1') end @@ -243,7 +303,7 @@ def validate_document_with_cert(idp_cert, soft = true) cert_element = REXML::XPath.first( self, "//ds:X509Certificate", - { "ds"=>DSIG } + { "ds"=>XMLSecurity::Crypto::DSIG } ) if cert_element @@ -278,34 +338,34 @@ def validate_signature(base64_cert, soft = true) sig_element = REXML::XPath.first( @working_copy, "//ds:Signature", - {"ds"=>DSIG} + {"ds"=>XMLSecurity::Crypto::DSIG} ) # signature method sig_alg_value = REXML::XPath.first( sig_element, "./ds:SignedInfo/ds:SignatureMethod", - {"ds"=>DSIG} + {"ds"=>XMLSecurity::Crypto::DSIG} ) - signature_algorithm = algorithm(sig_alg_value) + signature_key_algorithm, signature_hash_algorithm = XMLSecurity::Crypto.signature_algorithm(sig_alg_value) # get signature base64_signature = REXML::XPath.first( sig_element, "./ds:SignatureValue", - {"ds" => DSIG} + {"ds" => XMLSecurity::Crypto::DSIG} ) signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature)) # canonicalization method - canon_algorithm = canon_algorithm REXML::XPath.first( + canon_algorithm = XMLSecurity::Crypto.canon_algorithm(REXML::XPath.first( sig_element, './ds:SignedInfo/ds:CanonicalizationMethod', - 'ds' => DSIG - ) + 'ds' => XMLSecurity::Crypto::DSIG + )) - noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG) - noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG) + noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => XMLSecurity::Crypto::DSIG) + noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => XMLSecurity::Crypto::DSIG) canon_string = noko_signed_info_element.canonicalize(canon_algorithm) noko_sig_element.remove @@ -314,30 +374,30 @@ def validate_signature(base64_cert, soft = true) inclusive_namespaces = extract_inclusive_namespaces # check digests - ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG}) + ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>XMLSecurity::Crypto::DSIG}) hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) - canon_algorithm = canon_algorithm REXML::XPath.first( + canon_algorithm = XMLSecurity::Crypto.canon_algorithm(REXML::XPath.first( ref, '//ds:CanonicalizationMethod', - { "ds" => DSIG } - ) + { "ds" => XMLSecurity::Crypto::DSIG } + )) canon_algorithm = process_transforms(ref, canon_algorithm) canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) - digest_algorithm = algorithm(REXML::XPath.first( + digest_algorithm = XMLSecurity::Crypto.hash_algorithm(REXML::XPath.first( ref, "//ds:DigestMethod", - { "ds" => DSIG } + { "ds" => XMLSecurity::Crypto::DSIG } )) hash = digest_algorithm.digest(canon_hashed_element) encoded_digest_value = REXML::XPath.first( ref, "//ds:DigestValue", - { "ds" => DSIG } + { "ds" => XMLSecurity::Crypto::DSIG } ) digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value)) @@ -349,8 +409,16 @@ def validate_signature(base64_cert, soft = true) cert_text = Base64.decode64(base64_cert) cert = OpenSSL::X509::Certificate.new(cert_text) + # check correct public key type + public_key = cert.public_key + unless public_key.is_a?(signature_key_algorithm) + expected = signature_key_algorithm.class.name.split('::').last + actual = public_key.class.name.split('::').last + return append_error("Incorrect public key type (expected: #{expected}, was: #{actual})", soft) + end + # verify signature - unless cert.public_key.verify(signature_algorithm.new, signature, canon_string) + unless public_key.verify(signature_hash_algorithm.new, signature, canon_string) return append_error("Key validation error", soft) end @@ -363,24 +431,12 @@ def process_transforms(ref, canon_algorithm) transforms = REXML::XPath.match( ref, "//ds:Transforms/ds:Transform", - { "ds" => DSIG } + { "ds" => XMLSecurity::Crypto::DSIG } ) transforms.each do |transform_element| - if transform_element.attributes && transform_element.attributes["Algorithm"] - algorithm = transform_element.attributes["Algorithm"] - case algorithm - when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" - canon_algorithm = Nokogiri::XML::XML_C14N_1_0 - when "http://www.w3.org/2006/12/xml-c14n11", - "http://www.w3.org/2006/12/xml-c14n11#WithComments" - canon_algorithm = Nokogiri::XML::XML_C14N_1_1 - when "http://www.w3.org/2001/10/xml-exc-c14n#", - "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" - canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 - end - end + next unless transform_element.attributes && transform_element.attributes["Algorithm"] + canon_algorithm = XMLSecurity::Crypto.canon_algorithm(transform_element) end canon_algorithm @@ -394,7 +450,7 @@ def extract_signed_element_id reference_element = REXML::XPath.first( self, "//ds:Signature/ds:SignedInfo/ds:Reference", - {"ds"=>DSIG} + { "ds" => XMLSecurity::Crypto::DSIG } ) return nil if reference_element.nil? @@ -407,7 +463,7 @@ def extract_inclusive_namespaces element = REXML::XPath.first( self, "//ec:InclusiveNamespaces", - { "ec" => C14N } + { "ec" => XMLSecurity::Crypto::C14N } ) if element prefix_list = element.attributes.get_attribute("PrefixList").value @@ -416,6 +472,5 @@ def extract_inclusive_namespaces nil end end - end end diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index cb6d1f4d..b3e677ea 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -163,8 +163,8 @@ def initialize; end } }) assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint - assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method] - assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method] + assert_equal XMLSecurity::Document::SHA256, settings.get_sp_digest_method + assert_equal XMLSecurity::Document::RSA_SHA256, settings.get_sp_signature_method end it "merges results into given settings object" do @@ -176,8 +176,8 @@ def initialize; end OneLogin::RubySaml::IdpMetadataParser.new.parse(idp_metadata_descriptor, :settings => settings) assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint - assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method] - assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method] + assert_equal XMLSecurity::Document::SHA256, settings.get_sp_digest_method + assert_equal XMLSecurity::Document::RSA_SHA256, settings.get_sp_signature_method end end diff --git a/test/logoutrequest_test.rb b/test/logoutrequest_test.rb index 08a15976..34b66f87 100644 --- a/test/logoutrequest_test.rb +++ b/test/logoutrequest_test.rb @@ -273,7 +273,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -289,7 +289,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA256 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -305,7 +305,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA384 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -321,7 +321,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA512 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -348,7 +348,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -409,7 +409,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end diff --git a/test/logoutresponse_test.rb b/test/logoutresponse_test.rb index 7cc79991..e6daf6ca 100644 --- a/test/logoutresponse_test.rb +++ b/test/logoutresponse_test.rb @@ -314,7 +314,7 @@ class RubySamlTest < Minitest::Test refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") # Re-create the Logoutresponse based on these modified parameters, @@ -350,7 +350,7 @@ class RubySamlTest < Minitest::Test refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") # Re-create the Logoutresponse based on these modified parameters, diff --git a/test/request_test.rb b/test/request_test.rb index 76e8a848..cc2640bf 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -339,7 +339,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -355,7 +355,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA256 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -382,7 +382,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -474,7 +474,7 @@ class RequestTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) diff --git a/test/settings_test.rb b/test/settings_test.rb index 8e7cb9f6..dfca8476 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -124,8 +124,8 @@ class SettingsTest < Minitest::Test new_settings = OneLogin::RubySaml::Settings.new assert_equal new_settings.security[:authn_requests_signed], false assert_equal new_settings.security[:embed_sign], false - assert_equal new_settings.security[:digest_method], XMLSecurity::Document::SHA1 - assert_equal new_settings.security[:signature_method], XMLSecurity::Document::RSA_SHA1 + assert_equal new_settings.get_sp_digest_method, XMLSecurity::Document::SHA1 + assert_equal new_settings.get_sp_signature_method, XMLSecurity::Document::RSA_SHA1 end it "overrides only provided security attributes passing a second parameter" do @@ -397,7 +397,7 @@ class SettingsTest < Minitest::Test it "returns the private key when it is valid" do @settings.private_key = ruby_saml_key_text - assert @settings.get_sp_key.kind_of? OpenSSL::PKey::RSA + assert @settings.get_sp_key.kind_of?(OpenSSL::PKey::RSA) end it "raises when the private key is not valid" do diff --git a/test/slo_logoutrequest_test.rb b/test/slo_logoutrequest_test.rb index 0415f20f..20dd5b8c 100644 --- a/test/slo_logoutrequest_test.rb +++ b/test/slo_logoutrequest_test.rb @@ -401,7 +401,7 @@ class RubySamlTest < Minitest::Test refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") # Construct SloLogoutrequest and ask it to validate the signature. @@ -436,7 +436,7 @@ class RubySamlTest < Minitest::Test refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") # Construct SloLogoutrequest and ask it to validate the signature. @@ -468,14 +468,12 @@ class RubySamlTest < Minitest::Test # send is based on this base64 request. params = { 'SAMLRequest' => downcased_escape(base64_request), - 'SigAlg' => downcased_escape(settings.security[:signature_method]), + 'SigAlg' => downcased_escape(settings.get_sp_signature_method), } # Assemble query string. query = "SAMLRequest=#{params['SAMLRequest']}&SigAlg=#{params['SigAlg']}" # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm( - settings.security[:signature_method] - ) + sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) params['Signature'] = downcased_escape(Base64.encode64(signature).gsub(/\n/, "")) diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index 7c9669f0..45cc4fff 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -249,7 +249,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -268,7 +268,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA256 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -287,7 +287,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA384 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -306,7 +306,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA512 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -333,7 +333,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end @@ -402,7 +402,7 @@ class SloLogoutresponseTest < Minitest::Test query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = XMLSecurity::Crypto.hash_algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end diff --git a/test/xml_security_test.rb b/test/xml_security_test.rb index b8ec7837..84c3445a 100644 --- a/test/xml_security_test.rb +++ b/test/xml_security_test.rb @@ -88,48 +88,48 @@ class XmlSecurityTest < Minitest::Test describe "#canon_algorithm" do it "C14N_EXCLUSIVE_1_0" do canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#") - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#WithComments") - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("other") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#WithComments") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("other") end it "C14N_1_0" do canon_algorithm = Nokogiri::XML::XML_C14N_1_0 - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315") - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") end it "XML_C14N_1_1" do canon_algorithm = Nokogiri::XML::XML_C14N_1_1 - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11") - assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11#WithComments") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11") + assert_equal canon_algorithm, XMLSecurity::Crypto.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11#WithComments") end end describe "#algorithm" do it "SHA1" do alg = OpenSSL::Digest::SHA1 - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1") - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#sha1") - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("other") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2000/09/xmldsig#sha1") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("other") end it "SHA256" do alg = OpenSSL::Digest::SHA256 - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha256") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#sha256") end it "SHA384" do alg = OpenSSL::Digest::SHA384 - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha384") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#sha384") end it "SHA512" do alg = OpenSSL::Digest::SHA512 - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512") - assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha512") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512") + assert_equal alg, XMLSecurity::Crypto.hash_algorithm("http://www.w3.org/2001/04/xmldsig-more#sha512") end end @@ -299,15 +299,19 @@ class XmlSecurityTest < Minitest::Test end describe "StarfieldTMS" do - let (:response) { OneLogin::RubySaml::Response.new(fixture(:starfield_response)) } + let(:response) { OneLogin::RubySaml::Response.new(fixture(:starfield_response)) } before do response.settings = OneLogin::RubySaml::Settings.new(:idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D") end it "be able to validate a good response" do - Timecop.freeze Time.parse('2012-11-28 17:55:00 UTC') do + Timecop.freeze(Time.parse('2012-11-28 17:55:00 UTC')) do response.stubs(:validate_subject_confirmation).returns(true) + response.is_valid?(true) + puts response.errors + puts response.document + assert response.is_valid? end end From 19d62b3a3442443cd1b50ae53c7e08e7c8013d0a Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:57:41 +0900 Subject: [PATCH 2/2] Fix tests --- lib/xml_security.rb | 15 +++++++-------- test/xml_security_test.rb | 4 ---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/xml_security.rb b/lib/xml_security.rb index f2670d84..13ef2a10 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -58,16 +58,16 @@ module Crypto SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512' ENVELOPED_SIG = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' - def canon_algorithm(element) + def canon_algorithm(element, default = true) case get_algorithm_attr(element) - when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + when %r{\Ahttp://www\.w3\.org/TR/2001/REC-xml-c14n-20010315#?(?:WithComments)?\z}i Nokogiri::XML::XML_C14N_1_0 - when "http://www.w3.org/2006/12/xml-c14n11", - "http://www.w3.org/2006/12/xml-c14n11#WithComments" + when %r{\Ahttp://www\.w3\.org/2006/12/xml-c14n11#?(?:WithComments)?\z}i Nokogiri::XML::XML_C14N_1_1 - else + when %r{\Ahttp://www\.w3\.org/2001/10/xml-exc-c14n#?(?:WithComments)?\z}i Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 + else + Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 if default end end @@ -92,7 +92,6 @@ def signature_algorithm(element) def hash_algorithm(element) alg = get_algorithm_attr(element) - puts alg.inspect hash_alg = alg && (alg.downcase.match(/(?:\A|[#-])(sha\d+)\z/i) || {})[1] # TODO: Use &. operator case hash_alg @@ -436,7 +435,7 @@ def process_transforms(ref, canon_algorithm) transforms.each do |transform_element| next unless transform_element.attributes && transform_element.attributes["Algorithm"] - canon_algorithm = XMLSecurity::Crypto.canon_algorithm(transform_element) + canon_algorithm = XMLSecurity::Crypto.canon_algorithm(transform_element, false) end canon_algorithm diff --git a/test/xml_security_test.rb b/test/xml_security_test.rb index 84c3445a..edca3e3b 100644 --- a/test/xml_security_test.rb +++ b/test/xml_security_test.rb @@ -308,10 +308,6 @@ class XmlSecurityTest < Minitest::Test it "be able to validate a good response" do Timecop.freeze(Time.parse('2012-11-28 17:55:00 UTC')) do response.stubs(:validate_subject_confirmation).returns(true) - response.is_valid?(true) - puts response.errors - puts response.document - assert response.is_valid? end end