diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index da9714b0..a742d593 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-07-08 10:27:10 UTC using RuboCop version 1.64.1. +# on 2024-07-09 11:29:15 UTC using RuboCop version 1.64.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -20,7 +20,7 @@ Layout/EmptyLineAfterGuardClause: - 'lib/ruby_saml/slo_logoutrequest.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' -# Offense count: 9 +# Offense count: 6 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only @@ -32,7 +32,6 @@ Layout/EmptyLinesAroundClassBody: - 'lib/ruby_saml/logoutresponse.rb' - 'lib/ruby_saml/metadata.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' - - 'lib/xml_security.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -40,7 +39,7 @@ Layout/EmptyLinesAroundMethodBody: Exclude: - 'lib/ruby_saml/slo_logoutrequest.rb' -# Offense count: 12 +# Offense count: 11 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines @@ -57,14 +56,6 @@ Layout/EmptyLinesAroundModuleBody: - 'lib/ruby_saml/slo_logoutrequest.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' - -# Offense count: 1 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: native, lf, crlf -Layout/EndOfLine: - Exclude: - - 'lib/ruby_saml.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -81,7 +72,7 @@ Layout/ExtraSpacing: Layout/FirstArgumentIndentation: Exclude: - 'lib/ruby_saml/response.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 5 # This cop supports safe autocorrection (--autocorrect). @@ -105,7 +96,7 @@ Layout/SpaceAfterComma: Exclude: - 'lib/ruby_saml/response.rb' - 'lib/ruby_saml/settings.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 12 # This cop supports safe autocorrection (--autocorrect). @@ -130,7 +121,8 @@ Layout/SpaceAroundOperators: Exclude: - 'lib/ruby_saml/response.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/document.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 5 # This cop supports safe autocorrection (--autocorrect). @@ -154,15 +146,8 @@ Layout/SpaceInsideHashLiteralBraces: - 'lib/ruby_saml/response.rb' - 'lib/ruby_saml/settings.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' - - 'lib/xml_security.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: final_newline, final_blank_line -Layout/TrailingEmptyLines: - Exclude: - - 'lib/ruby_saml.rb' + - 'lib/ruby_saml/xml/document.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 2 Lint/NoReturnInBeginEndBlocks: @@ -185,12 +170,11 @@ Lint/UnreachableLoop: Exclude: - 'lib/ruby_saml/saml_message.rb' -# Offense count: 3 +# Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - - 'lib/ruby_saml/logging.rb' - 'lib/ruby_saml/slo_logoutrequest.rb' # Offense count: 42 @@ -308,7 +292,7 @@ Performance/StringReplacement: - 'lib/ruby_saml/metadata.rb' - 'lib/ruby_saml/saml_message.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/document.rb' # Offense count: 54 # This cop supports safe autocorrection (--autocorrect). @@ -361,7 +345,7 @@ Style/ConditionalAssignment: - 'lib/ruby_saml/logoutresponse.rb' - 'lib/ruby_saml/response.rb' - 'lib/ruby_saml/slo_logoutrequest.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 6 # Configuration parameters: AllowedConstants. @@ -372,7 +356,9 @@ Style/Documentation: - 'lib/ruby_saml/error_handling.rb' - 'lib/ruby_saml/idp_metadata_parser.rb' - 'lib/ruby_saml/logging.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/base_document.rb' + - 'lib/ruby_saml/xml/document.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). @@ -416,7 +402,17 @@ Style/IfUnlessModifier: - 'lib/ruby_saml/slo_logoutrequest.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/base_document.rb' + - 'lib/ruby_saml/xml/document.rb' + - 'lib/ruby_saml/xml/signed_document.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, Autocorrect. +# SupportedStyles: module_function, extend_self, forbidden +Style/ModuleFunction: + Exclude: + - 'lib/ruby_saml/logging.rb' # Offense count: 15 # Configuration parameters: AllowedMethods. @@ -431,7 +427,7 @@ Style/OptionalBooleanParameter: - 'lib/ruby_saml/settings.rb' - 'lib/ruby_saml/slo_logoutrequest.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -445,7 +441,7 @@ Style/RedundantRegexpArgument: Exclude: - 'lib/ruby_saml/saml_message.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/document.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -473,7 +469,7 @@ Style/StringConcatenation: - 'lib/ruby_saml/saml_message.rb' - 'lib/ruby_saml/slo_logoutrequest.rb' -# Offense count: 440 +# Offense count: 351 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -492,7 +488,7 @@ Style/StringLiterals: - 'lib/ruby_saml/slo_logoutrequest.rb' - 'lib/ruby_saml/slo_logoutresponse.rb' - 'lib/ruby_saml/utils.rb' - - 'lib/xml_security.rb' + - 'lib/ruby_saml/xml/signed_document.rb' # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -510,7 +506,7 @@ Style/SymbolArray: Exclude: - 'lib/ruby_saml/settings.rb' -# Offense count: 94 +# Offense count: 95 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4cc61c..1772f569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Create namespace alias `OneLogin = Object` for backward compatibility, to be removed in version `2.1.0`. * [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Change directly structure from `lib/onelogin/ruby-saml` to `lib/ruby_saml`. * [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Move schema files from `lib/onelogin/schemas` to `lib/ruby_saml/schemas`. +* [#692](https://github.com/SAML-Toolkits/ruby-saml/pull/692) Remove `XMLSecurity` namespace and replace with `RubySaml::XML`. * [#686](https://github.com/SAML-Toolkits/ruby-saml/pull/686) Use SHA-256 as the default hashing algorithm everywhere instead of SHA-1, including signatures, fingerprints, and digests. * [#690](https://github.com/SAML-Toolkits/ruby-saml/pull/690) Remove deprecated `settings.security[:embed_sign]` parameter. diff --git a/LICENSE b/LICENSE index c141165e..8f4b441d 100644 --- a/LICENSE +++ b/LICENSE @@ -21,4 +21,3 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 9aed3671..f5767ede 100644 --- a/README.md +++ b/README.md @@ -411,7 +411,7 @@ but it can be done as follows: * Provide the XML to the parse method if the signature was validated ```ruby -require "xml_security" +require "ruby_saml/xml" require "ruby_saml/utils" require "ruby_saml/idp_metadata_parser" @@ -431,7 +431,7 @@ get.basic_auth uri.user, uri.password if uri.user response = http.request(get) xml = response.body errors = [] -doc = XMLSecurity::SignedDocument.new(xml, errors) +doc = RubySaml::XML::SignedDocument.new(xml, errors) cert_str = "" cert = RubySaml::Utils.format_cert("cert_str") metadata_sign_cert = OpenSSL::X509::Certificate.new(cert) @@ -634,8 +634,8 @@ to specify different certificates for each function. You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above): ```ruby - settings.security[:digest_method] = XMLSecurity::Document::SHA1 - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:digest_method] = RubySaml::XML::Document::SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 ``` #### Signing SP Metadata @@ -979,3 +979,14 @@ end # Output XML with custom metadata MyMetadata.new.generate(settings) ``` + +## Attribution + +Portions of the code in `RubySaml::XML` namespace is adapted from earlier work +copyrighted by either Oracle and/or Todd W. Saxton. The original code was distributed +under the Common Development and Distribution License (CDDL) 1.0. This code is planned to +be written entirely in future versions. + +## License + +RubySaml is made available under the MIT License. Refer to [LICENSE](LICENSE). diff --git a/UPGRADING.md b/UPGRADING.md index 1a9033d0..465381ea 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -10,14 +10,27 @@ Before attempting to upgrade to `2.0.0`: - Upgrade your project to minimum Ruby 3.0, JRuby 9.4, or TruffleRuby 22. - Upgrade RubySaml to `1.17.x`. Note that RubySaml `1.17.x` is compatible with up to Ruby 3.3. -### Root namespace changed to RubySaml +### Root "OneLogin" namespace changed to "RubySaml" -RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. This will require you -to search your codebase for the string `OneLogin::` and remove it as appropriate. Aside from this namespace change, +RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. +Please remove `OneLogin::` and `onelogin/` everywhere in your codebase. Aside from this namespace change, the class names themselves have intentionally been kept the same. -For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work. -This alias will be removed in RubySaml version `2.1.0`. +Note that the project folder structure has also been updated accordingly. Notably, the directory +`lib/onelogin/schemas` is now `lib/ruby_saml/schemas`. + +For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work +as before. This alias will be removed in RubySaml version `2.1.0`. + +### Root "XMLSecurity" namespace changed to "RubySaml::XML" + +RubySaml version `2.0.0` changes the namespace `RubySaml::XML::` to `RubySaml::XML::`. Please search your +codebase for `RubySaml::XML::` and replace it as appropriate. In addition, you must replace direct usage of +`require 'xml_security'` with `require 'ruby_saml/xml'`. + +For backward compatibility, the alias `XMLSecurity = RubySaml::XML` has been set, so `RubySaml::XML::` will still work +as before. In addition, a shim file has been added so that `require 'xml_security'` continues to work. +These aliases will be removed in RubySaml version `2.1.0`. ### Security: Change default hashing algorithm to SHA-256 (was SHA-1) @@ -32,9 +45,9 @@ To preserve the old insecure SHA-1 behavior *(not recommended)*, you may set `Ru ```ruby # Preserve RubySaml 1.x insecure SHA-1 behavior settings = RubySaml::Settings.new -settings.idp_cert_fingerprint_algorithm = XMLSecurity::Document::SHA1 -settings.security[:digest_method] = XMLSecurity::Document::SHA1 -settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 +settings.idp_cert_fingerprint_algorithm = RubySaml::XML::Document::SHA1 +settings.security[:digest_method] = RubySaml::XML::Document::SHA1 +settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 ``` ### Removal of embed_sign Setting @@ -128,7 +141,7 @@ The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is v # In this example `query_params` is assumed to contain decoded query parameters, # and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP. settings = { - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false } options = { diff --git a/lib/ruby_saml.rb b/lib/ruby_saml.rb index 01410c9a..b9c7544f 100644 --- a/lib/ruby_saml.rb +++ b/lib/ruby_saml.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'ruby_saml/logging' +require 'ruby_saml/xml' require 'ruby_saml/saml_message' require 'ruby_saml/authrequest' require 'ruby_saml/logoutrequest' @@ -18,5 +19,5 @@ require 'ruby_saml/utils' require 'ruby_saml/version' -# @deprecated This alias will be removed in version 2.1.0 +# @deprecated This alias adds compatibility with v1.x and will be removed in v2.1.0 OneLogin = Object diff --git a/lib/ruby_saml/authrequest.rb b/lib/ruby_saml/authrequest.rb index 539b8add..73b9fd19 100644 --- a/lib/ruby_saml/authrequest.rb +++ b/lib/ruby_saml/authrequest.rb @@ -84,7 +84,7 @@ def create_params(settings, params={}) relay_state: relay_state, sig_alg: params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method]) signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -108,7 +108,7 @@ def create_authentication_xml_doc(settings) def create_xml_document(settings) time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") - request_doc = XMLSecurity::Document.new + request_doc = RubySaml::XML::Document.new request_doc.uuid = uuid root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } diff --git a/lib/ruby_saml/idp_metadata_parser.rb b/lib/ruby_saml/idp_metadata_parser.rb index 270137fa..9f99b021 100644 --- a/lib/ruby_saml/idp_metadata_parser.rb +++ b/lib/ruby_saml/idp_metadata_parser.rb @@ -376,13 +376,13 @@ def certificates # @return [String|nil] the fingerpint of the X509Certificate if it exists # - def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA256) + def fingerprint(certificate, fingerprint_algorithm = RubySaml::XML::Document::SHA256) @fingerprint ||= begin return unless certificate cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate)) - fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new + fingerprint_alg = RubySaml::XML::BaseDocument.new.algorithm(fingerprint_algorithm).new fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":") end end diff --git a/lib/ruby_saml/logging.rb b/lib/ruby_saml/logging.rb index 7045a93c..b5dc4aeb 100644 --- a/lib/ruby_saml/logging.rb +++ b/lib/ruby_saml/logging.rb @@ -2,32 +2,33 @@ require 'logger' -# Simplistic log class when we're running in Rails module RubySaml - class Logging + module Logging + extend self + DEFAULT_LOGGER = ::Logger.new($stdout) - def self.logger + attr_writer :logger + + def logger @logger ||= begin logger = Rails.logger if defined?(::Rails) && Rails.respond_to?(:logger) - logger ||= DEFAULT_LOGGER + logger || DEFAULT_LOGGER end end - class << self - attr_writer :logger + %i[error warn debug info].each do |level| + define_method(level) do |message| + logger.send(level, message) if enabled? + end end - def self.debug(message) - return if ENV['ruby-saml/testing'] - - logger.debug(message) + def deprecate(message) + warn("[DEPRECATION] RubySaml: #{message}") end - def self.info(message) - return if ENV['ruby-saml/testing'] - - logger.info(message) + def enabled? + !ENV['ruby-saml/testing'] end end end diff --git a/lib/ruby_saml/logoutrequest.rb b/lib/ruby_saml/logoutrequest.rb index d808c054..f1b6e1d3 100644 --- a/lib/ruby_saml/logoutrequest.rb +++ b/lib/ruby_saml/logoutrequest.rb @@ -81,7 +81,7 @@ def create_params(settings, params={}) relay_state: relay_state, sig_alg: params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method]) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -105,7 +105,7 @@ def create_logout_request_xml_doc(settings) def create_xml_document(settings) time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") - request_doc = XMLSecurity::Document.new + request_doc = RubySaml::XML::Document.new request_doc.uuid = uuid root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } diff --git a/lib/ruby_saml/logoutresponse.rb b/lib/ruby_saml/logoutresponse.rb index 7c885239..ae92a1e5 100644 --- a/lib/ruby_saml/logoutresponse.rb +++ b/lib/ruby_saml/logoutresponse.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -require "xml_security" +require "ruby_saml/xml" require "ruby_saml/saml_message" - require "time" # Only supports SAML 2.0 @@ -45,7 +44,7 @@ def initialize(response, settings = nil, options = {}) @options = options @response = decode_raw_saml(response, settings) - @document = XMLSecurity::SignedDocument.new(@response) + @document = RubySaml::XML::SignedDocument.new(@response) super() end diff --git a/lib/ruby_saml/metadata.rb b/lib/ruby_saml/metadata.rb index a979aa3d..b76624d1 100644 --- a/lib/ruby_saml/metadata.rb +++ b/lib/ruby_saml/metadata.rb @@ -21,7 +21,7 @@ class Metadata # @return [String] XML Metadata of the Service Provider # def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil) - meta_doc = XMLSecurity::Document.new + meta_doc = RubySaml::XML::Document.new add_xml_declaration(meta_doc) root = add_root_element(meta_doc, settings, valid_until, cache_duration) sp_sso = add_sp_sso_element(root, settings) diff --git a/lib/ruby_saml/response.rb b/lib/ruby_saml/response.rb index 774df663..a12007ea 100644 --- a/lib/ruby_saml/response.rb +++ b/lib/ruby_saml/response.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "xml_security" +require "ruby_saml/xml" require "ruby_saml/attributes" require "time" @@ -65,7 +65,7 @@ def initialize(response, options = {}) end @response = decode_raw_saml(response, settings) - @document = XMLSecurity::SignedDocument.new(@response, @errors) + @document = RubySaml::XML::SignedDocument.new(@response, @errors) if assertion_encrypted? @decrypted_document = generate_decrypted_document @@ -951,7 +951,7 @@ def xpath_from_signed_assertion(subelt=nil) end # Generates the decrypted_document - # @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted + # @return [RubySaml::XML::SignedDocument] The SAML Response with the assertion decrypted # def generate_decrypted_document if settings.nil? || settings.get_sp_decryption_keys.empty? @@ -964,8 +964,8 @@ def generate_decrypted_document end # Obtains a SAML Response with the EncryptedAssertion element decrypted - # @param document_copy [XMLSecurity::SignedDocument] A copy of the original SAML Response with the encrypted assertion - # @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted + # @param document_copy [RubySaml::XML::SignedDocument] A copy of the original SAML Response with the encrypted assertion + # @return [RubySaml::XML::SignedDocument] The SAML Response with the assertion decrypted # def decrypt_assertion_from_document(document_copy) response_node = REXML::XPath.first( @@ -980,7 +980,7 @@ def decrypt_assertion_from_document(document_copy) ) response_node.add(decrypt_assertion(encrypted_assertion_node)) encrypted_assertion_node.remove - XMLSecurity::SignedDocument.new(response_node.to_s) + RubySaml::XML::SignedDocument.new(response_node.to_s) end # Decrypts an EncryptedAssertion element diff --git a/lib/ruby_saml/saml_message.rb b/lib/ruby_saml/saml_message.rb index dd8b2571..9e29f896 100644 --- a/lib/ruby_saml/saml_message.rb +++ b/lib/ruby_saml/saml_message.rb @@ -67,7 +67,7 @@ def id(document) def valid_saml?(document, soft = true) begin xml = Nokogiri::XML(document.to_s) do |config| - config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS + config.options = RubySaml::XML::BaseDocument::NOKOGIRI_OPTIONS end rescue StandardError => error return false if soft diff --git a/lib/ruby_saml/settings.rb b/lib/ruby_saml/settings.rb index a1a1dde4..06a3b9f9 100644 --- a/lib/ruby_saml/settings.rb +++ b/lib/ruby_saml/settings.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "xml_security" +require "ruby_saml/xml" require "ruby_saml/attribute_service" require "ruby_saml/utils" require "ruby_saml/validation_error" @@ -173,7 +173,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 = RubySaml::XML::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":") end end @@ -273,7 +273,7 @@ def get_binding(value) DEFAULTS = { assertion_consumer_service_binding: Utils::BINDINGS[:post], single_logout_service_binding: Utils::BINDINGS[:redirect], - idp_cert_fingerprint_algorithm: XMLSecurity::Document::SHA256, + idp_cert_fingerprint_algorithm: RubySaml::XML::Document::SHA256, compress_request: true, compress_response: true, message_max_bytesize: 250_000, @@ -287,8 +287,8 @@ def get_binding(value) want_assertions_encrypted: false, want_name_id: false, metadata_signed: false, - digest_method: XMLSecurity::Document::SHA256, - signature_method: XMLSecurity::Document::RSA_SHA256, + digest_method: RubySaml::XML::Document::SHA256, + signature_method: RubySaml::XML::Document::RSA_SHA256, check_idp_cert_expiration: false, check_sp_cert_expiration: false, strict_audience_validation: false, diff --git a/lib/ruby_saml/slo_logoutresponse.rb b/lib/ruby_saml/slo_logoutresponse.rb index d2337816..5dd0ab6d 100644 --- a/lib/ruby_saml/slo_logoutresponse.rb +++ b/lib/ruby_saml/slo_logoutresponse.rb @@ -90,7 +90,7 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}, relay_state: relay_state, sig_alg: params['SigAlg'] ) - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method]) signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -117,7 +117,7 @@ def create_logout_response_xml_doc(settings, request_id = nil, logout_message = def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil) time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') - response_doc = XMLSecurity::Document.new + response_doc = RubySaml::XML::Document.new response_doc.uuid = uuid destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url diff --git a/lib/ruby_saml/utils.rb b/lib/ruby_saml/utils.rb index 29810b53..a0440ec3 100644 --- a/lib/ruby_saml/utils.rb +++ b/lib/ruby_saml/utils.rb @@ -229,7 +229,7 @@ def self.escape_request_param(param, lowercase_url_encoding) # def self.verify_signature(params) cert, sig_alg, signature, query_string = %i[cert sig_alg signature query_string].map { |k| params[k]} - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg) + signature_algorithm = RubySaml::XML::BaseDocument.new.algorithm(sig_alg) cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string) end diff --git a/lib/ruby_saml/xml.rb b/lib/ruby_saml/xml.rb new file mode 100644 index 00000000..b6ab1b62 --- /dev/null +++ b/lib/ruby_saml/xml.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'ruby_saml/xml/base_document' +require 'ruby_saml/xml/document' +require 'ruby_saml/xml/signed_document' + +# @deprecated This alias adds compatibility with v1.x and will be removed in v2.1.0 +XMLSecurity = RubySaml::XML diff --git a/lib/ruby_saml/xml/base_document.rb b/lib/ruby_saml/xml/base_document.rb new file mode 100644 index 00000000..a2918eab --- /dev/null +++ b/lib/ruby_saml/xml/base_document.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rexml/document' +require 'rexml/xpath' +require 'nokogiri' +require 'openssl' +require 'digest/sha1' +require 'digest/sha2' + +module RubySaml + module XML + 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 + + 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 + end + end + + def algorithm(element) + algorithm = element + if algorithm.is_a?(REXML::Element) + algorithm = element.attribute('Algorithm').value + end + + algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && ::Regexp.last_match(2).to_i + + case algorithm + when 1 then OpenSSL::Digest::SHA1 + when 384 then OpenSSL::Digest::SHA384 + when 512 then OpenSSL::Digest::SHA512 + else + OpenSSL::Digest::SHA256 + end + end + end + end +end diff --git a/lib/ruby_saml/xml/document.rb b/lib/ruby_saml/xml/document.rb new file mode 100644 index 00000000..a37ab09f --- /dev/null +++ b/lib/ruby_saml/xml/document.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'ruby_saml/xml/base_document' + +module RubySaml + module XML + 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' + + attr_writer :uuid + + def uuid + @uuid ||= document.root&.attributes&.[]('ID') + end + + # + # + # + # + # + # + # + # + # + # etc. + # + # + # + # + # + def sign_document(private_key, certificate, signature_method = RSA_SHA256, digest_method = SHA256) + noko = Nokogiri::XML(to_s) do |config| + config.options = RubySaml::XML::BaseDocument::NOKOGIRI_OPTIONS + end + + signature_element = REXML::Element.new('ds:Signature').add_namespace('ds', 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:SignatureMethod', {'Algorithm'=>signature_method}) + + # Add Reference + reference_element = signed_info_element.add_element('ds:Reference', {'URI' => "##{uuid}"}) + + # 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}) + + 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)) + + # add SignatureValue + noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config| + config.options = RubySaml::XML::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)) + + signature = compute_signature(private_key, 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') + if certificate.is_a?(String) + certificate = OpenSSL::X509::Certificate.new(certificate) + end + x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, '') + + # add the signature + issuer_element = elements['//saml:Issuer'] + if issuer_element + root.insert_after(issuer_element, signature_element) + elsif (first_child = root.children[0]) + root.insert_before(first_child, signature_element) + else + root.add_element(signature_element) + end + end + + protected + + def compute_signature(private_key, signature_algorithm, document) + Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, '') + end + + def compute_digest(document, digest_algorithm) + digest = digest_algorithm.digest(document) + Base64.encode64(digest).strip + end + end + end +end diff --git a/lib/ruby_saml/xml/signed_document.rb b/lib/ruby_saml/xml/signed_document.rb new file mode 100644 index 00000000..444a062a --- /dev/null +++ b/lib/ruby_saml/xml/signed_document.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +require 'ruby_saml/xml/base_document' +require 'ruby_saml/error_handling' +require 'ruby_saml/utils' + +module RubySaml + module XML + class SignedDocument < BaseDocument + include RubySaml::ErrorHandling + + attr_writer :signed_element_id + + def initialize(response, errors = []) + super(response) + @errors = errors + end + + def signed_element_id + @signed_element_id ||= extract_signed_element_id + end + + def validate_document(idp_cert_fingerprint, soft = true, options = {}) + # get cert from response + cert_element = REXML::XPath.first( + self, + "//ds:X509Certificate", + { "ds"=>DSIG } + ) + + if cert_element + base64_cert = RubySaml::Utils.element_text(cert_element) + cert_text = Base64.decode64(base64_cert) + begin + cert = OpenSSL::X509::Certificate.new(cert_text) + rescue OpenSSL::X509::CertificateError => _e + return append_error('Document Certificate Error', soft) + end + + if options[:fingerprint_alg] + fingerprint_alg = RubySaml::XML::BaseDocument.new.algorithm(options[:fingerprint_alg]).new + else + fingerprint_alg = OpenSSL::Digest.new('SHA256') + end + fingerprint = fingerprint_alg.hexdigest(cert.to_der) + + # check cert matches registered idp cert + if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,'').downcase + return append_error('Fingerprint mismatch', soft) + end + elsif options[:cert] + base64_cert = Base64.encode64(options[:cert].to_pem) + elsif soft + return false + else + return append_error('Certificate element missing in response (ds:X509Certificate) and not cert provided at settings', soft) + end + validate_signature(base64_cert, soft) + end + + def validate_document_with_cert(idp_cert, soft = true) + # get cert from response + cert_element = REXML::XPath.first( + self, + '//ds:X509Certificate', + { 'ds'=>DSIG } + ) + + if cert_element + base64_cert = RubySaml::Utils.element_text(cert_element) + cert_text = Base64.decode64(base64_cert) + begin + cert = OpenSSL::X509::Certificate.new(cert_text) + rescue OpenSSL::X509::CertificateError => _e + return append_error('Document Certificate Error', soft) + end + + # check saml response cert matches provided idp cert + if idp_cert.to_pem != cert.to_pem + return append_error('Certificate of the Signature element does not match provided certificate', soft) + end + else + base64_cert = Base64.encode64(idp_cert.to_pem) + end + validate_signature(base64_cert, true) + end + + def validate_signature(base64_cert, soft = true) + document = Nokogiri::XML(to_s) do |config| + config.options = RubySaml::XML::BaseDocument::NOKOGIRI_OPTIONS + end + + # create a rexml document + @working_copy ||= REXML::Document.new(to_s).root + + # get signature node + sig_element = REXML::XPath.first( + @working_copy, + '//ds:Signature', + {'ds'=>DSIG} + ) + + # signature method + sig_alg_value = REXML::XPath.first( + sig_element, + './ds:SignedInfo/ds:SignatureMethod', + {'ds'=>DSIG} + ) + signature_algorithm = algorithm(sig_alg_value) + + # get signature + base64_signature = REXML::XPath.first( + sig_element, + './ds:SignatureValue', + {'ds' => DSIG} + ) + signature = Base64.decode64(RubySaml::Utils.element_text(base64_signature)) + + # canonicalization method + canon_algorithm = canon_algorithm REXML::XPath.first( + sig_element, + './ds:SignedInfo/ds:CanonicalizationMethod', + 'ds' => DSIG + ) + + noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG) + noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG) + + canon_string = noko_signed_info_element.canonicalize(canon_algorithm) + noko_sig_element.remove + + # get inclusive namespaces + inclusive_namespaces = extract_inclusive_namespaces + + # check digests + ref = REXML::XPath.first(sig_element, '//ds:Reference', {'ds'=>DSIG}) + + hashed_element = document.at_xpath('//*[@ID=$id]', nil, { 'id' => extract_signed_element_id }) + + canon_algorithm = canon_algorithm REXML::XPath.first( + ref, + '//ds:CanonicalizationMethod', + { 'ds' => DSIG } + ) + + canon_algorithm = process_transforms(ref, canon_algorithm) + + canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) + + digest_algorithm = algorithm(REXML::XPath.first( + ref, + '//ds:DigestMethod', + { 'ds' => DSIG } + )) + hash = digest_algorithm.digest(canon_hashed_element) + encoded_digest_value = REXML::XPath.first( + ref, + '//ds:DigestValue', + { 'ds' => DSIG } + ) + digest_value = Base64.decode64(RubySaml::Utils.element_text(encoded_digest_value)) + + unless digests_match?(hash, digest_value) + return append_error('Digest mismatch', soft) + end + + # get certificate object + cert_text = Base64.decode64(base64_cert) + cert = OpenSSL::X509::Certificate.new(cert_text) + + # verify signature + unless cert.public_key.verify(signature_algorithm.new, signature, canon_string) + return append_error('Key validation error', soft) + end + + true + end + + private + + def process_transforms(ref, canon_algorithm) + transforms = REXML::XPath.match( + ref, + '//ds:Transforms/ds:Transform', + { 'ds' => DSIG } + ) + + transforms.each do |transform_element| + next unless 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 + + canon_algorithm + end + + def digests_match?(hash, digest_value) + hash == digest_value + end + + def extract_signed_element_id + reference_element = REXML::XPath.first( + self, + '//ds:Signature/ds:SignedInfo/ds:Reference', + {'ds'=>DSIG} + ) + + return nil if reference_element.nil? + + sei = reference_element.attribute('URI').value[1..] + sei.nil? ? reference_element.parent.parent.parent.attribute('ID').value : sei + end + + def extract_inclusive_namespaces + element = REXML::XPath.first( + self, + '//ec:InclusiveNamespaces', + { 'ec' => C14N } + ) + return unless element + + prefix_list = element.attributes.get_attribute('PrefixList').value + prefix_list.split + end + end + end +end diff --git a/lib/xml_security.rb b/lib/xml_security.rb index fb791d42..4d51c1da 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -1,414 +1,8 @@ # frozen_string_literal: true -# The contents of this file are subject to the terms -# of the Common Development and Distribution License -# (the License). You may not use this file except in -# compliance with the License. -# -# You can obtain a copy of the License at -# https://opensso.dev.java.net/public/CDDLv1.0.html or -# opensso/legal/CDDLv1.0.txt -# See the License for the specific language governing -# permission and limitations under the License. -# -# When distributing Covered Code, include this CDDL -# Header Notice in each file and include the License file -# at opensso/legal/CDDLv1.0.txt. -# If applicable, add the following below the CDDL Header, -# with the fields enclosed by brackets [] replaced by -# your own identifying information: -# "Portions Copyrighted [year] [name of copyright owner]" -# -# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $ -# -# Copyright 2007 Sun Microsystems Inc. All Rights Reserved -# Portions Copyrighted 2007 Todd W Saxton. +require 'ruby_saml/logging' +RubySaml::Logging.deprecate 'Using `require "xml_security"` is deprecated and will be removed ' \ + 'in RubySaml 2.1.0. Please use `require "ruby_saml/xml"` instead.' -require 'rubygems' -require "rexml/document" -require "rexml/xpath" -require "openssl" -require 'nokogiri' -require "digest/sha1" -require "digest/sha2" -require "ruby_saml/utils" -require "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 - - 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 - end - end - - def algorithm(element) - algorithm = element - if algorithm.is_a?(REXML::Element) - algorithm = element.attribute("Algorithm").value - end - - algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && ::Regexp.last_match(2).to_i - - case algorithm - when 1 then OpenSSL::Digest::SHA1 - when 384 then OpenSSL::Digest::SHA384 - when 512 then OpenSSL::Digest::SHA512 - else - OpenSSL::Digest::SHA256 - end - end - - 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" - - attr_writer :uuid - - def uuid - @uuid ||= document.root&.attributes&.[]('ID') - end - - # - # - # - # - # - # - # - # - # - # etc. - # - # - # - # - # - def sign_document(private_key, certificate, signature_method = RSA_SHA256, digest_method = SHA256) - noko = Nokogiri::XML(to_s) do |config| - config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS - end - - signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', 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:SignatureMethod", {"Algorithm"=>signature_method}) - - # Add Reference - reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"}) - - # 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}) - - 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)) - - # 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)) - - signature = compute_signature(private_key, 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") - if certificate.is_a?(String) - certificate = OpenSSL::X509::Certificate.new(certificate) - end - x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "") - - # add the signature - issuer_element = elements["//saml:Issuer"] - if issuer_element - root.insert_after(issuer_element, signature_element) - elsif (first_child = root.children[0]) - root.insert_before(first_child, signature_element) - else - root.add_element(signature_element) - end - end - - protected - - def compute_signature(private_key, signature_algorithm, document) - Base64.encode64(private_key.sign(signature_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 - include RubySaml::ErrorHandling - - attr_writer :signed_element_id - - def initialize(response, errors = []) - super(response) - @errors = errors - end - - def signed_element_id - @signed_element_id ||= extract_signed_element_id - end - - def validate_document(idp_cert_fingerprint, soft = true, options = {}) - # get cert from response - cert_element = REXML::XPath.first( - self, - "//ds:X509Certificate", - { "ds"=>DSIG } - ) - - if cert_element - base64_cert = RubySaml::Utils.element_text(cert_element) - cert_text = Base64.decode64(base64_cert) - begin - cert = OpenSSL::X509::Certificate.new(cert_text) - rescue OpenSSL::X509::CertificateError => _e - return append_error("Document Certificate Error", soft) - end - - if options[:fingerprint_alg] - fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new - else - fingerprint_alg = OpenSSL::Digest.new('SHA256') - end - fingerprint = fingerprint_alg.hexdigest(cert.to_der) - - # check cert matches registered idp cert - if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase - return append_error("Fingerprint mismatch", soft) - end - elsif options[:cert] - base64_cert = Base64.encode64(options[:cert].to_pem) - elsif soft - return false - else - return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft) - end - validate_signature(base64_cert, soft) - end - - def validate_document_with_cert(idp_cert, soft = true) - # get cert from response - cert_element = REXML::XPath.first( - self, - "//ds:X509Certificate", - { "ds"=>DSIG } - ) - - if cert_element - base64_cert = RubySaml::Utils.element_text(cert_element) - cert_text = Base64.decode64(base64_cert) - begin - cert = OpenSSL::X509::Certificate.new(cert_text) - rescue OpenSSL::X509::CertificateError => _e - return append_error("Document Certificate Error", soft) - end - - # check saml response cert matches provided idp cert - if idp_cert.to_pem != cert.to_pem - return append_error("Certificate of the Signature element does not match provided certificate", soft) - end - else - base64_cert = Base64.encode64(idp_cert.to_pem) - end - validate_signature(base64_cert, true) - end - - def validate_signature(base64_cert, soft = true) - document = Nokogiri::XML(to_s) do |config| - config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS - end - - # create a rexml document - @working_copy ||= REXML::Document.new(to_s).root - - # get signature node - sig_element = REXML::XPath.first( - @working_copy, - "//ds:Signature", - {"ds"=>DSIG} - ) - - # signature method - sig_alg_value = REXML::XPath.first( - sig_element, - "./ds:SignedInfo/ds:SignatureMethod", - {"ds"=>DSIG} - ) - signature_algorithm = algorithm(sig_alg_value) - - # get signature - base64_signature = REXML::XPath.first( - sig_element, - "./ds:SignatureValue", - {"ds" => DSIG} - ) - signature = Base64.decode64(RubySaml::Utils.element_text(base64_signature)) - - # canonicalization method - canon_algorithm = canon_algorithm REXML::XPath.first( - sig_element, - './ds:SignedInfo/ds:CanonicalizationMethod', - 'ds' => DSIG - ) - - noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG) - noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG) - - canon_string = noko_signed_info_element.canonicalize(canon_algorithm) - noko_sig_element.remove - - # get inclusive namespaces - inclusive_namespaces = extract_inclusive_namespaces - - # check digests - ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG}) - - hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) - - canon_algorithm = canon_algorithm REXML::XPath.first( - ref, - '//ds:CanonicalizationMethod', - { "ds" => DSIG } - ) - - canon_algorithm = process_transforms(ref, canon_algorithm) - - canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) - - digest_algorithm = algorithm(REXML::XPath.first( - ref, - "//ds:DigestMethod", - { "ds" => DSIG } - )) - hash = digest_algorithm.digest(canon_hashed_element) - encoded_digest_value = REXML::XPath.first( - ref, - "//ds:DigestValue", - { "ds" => DSIG } - ) - digest_value = Base64.decode64(RubySaml::Utils.element_text(encoded_digest_value)) - - unless digests_match?(hash, digest_value) - return append_error("Digest mismatch", soft) - end - - # get certificate object - cert_text = Base64.decode64(base64_cert) - cert = OpenSSL::X509::Certificate.new(cert_text) - - # verify signature - unless cert.public_key.verify(signature_algorithm.new, signature, canon_string) - return append_error("Key validation error", soft) - end - - true - end - - private - - def process_transforms(ref, canon_algorithm) - transforms = REXML::XPath.match( - ref, - "//ds:Transforms/ds:Transform", - { "ds" => DSIG } - ) - - transforms.each do |transform_element| - next unless 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 - - canon_algorithm - end - - def digests_match?(hash, digest_value) - hash == digest_value - end - - def extract_signed_element_id - reference_element = REXML::XPath.first( - self, - "//ds:Signature/ds:SignedInfo/ds:Reference", - {"ds"=>DSIG} - ) - - return nil if reference_element.nil? - - sei = reference_element.attribute("URI").value[1..] - sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei - end - - def extract_inclusive_namespaces - element = REXML::XPath.first( - self, - "//ec:InclusiveNamespaces", - { "ec" => C14N } - ) - return unless element - - prefix_list = element.attributes.get_attribute("PrefixList").value - prefix_list.split - end - - end -end +# @deprecated This file adds compatibility with v1.x and will be removed in v2.1.0 +require 'ruby_saml/xml' diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index c5ec8ddc..9eb647d1 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -157,27 +157,27 @@ def initialize; end settings = idp_metadata_parser.parse(idp_metadata, { :settings => { :security => { - :digest_method => XMLSecurity::Document::SHA256, - :signature_method => XMLSecurity::Document::RSA_SHA256 + :digest_method => RubySaml::XML::Document::SHA256, + :signature_method => RubySaml::XML::Document::RSA_SHA256 } } }) assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", 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 RubySaml::XML::Document::SHA256, settings.security[:digest_method] + assert_equal RubySaml::XML::Document::RSA_SHA256, settings.security[:signature_method] end it "merges results into given settings object" do settings = RubySaml::Settings.new(:security => { - :digest_method => XMLSecurity::Document::SHA256, - :signature_method => XMLSecurity::Document::RSA_SHA256 + :digest_method => RubySaml::XML::Document::SHA256, + :signature_method => RubySaml::XML::Document::RSA_SHA256 }) RubySaml::IdpMetadataParser.new.parse(idp_metadata_descriptor, :settings => settings) assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", 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 RubySaml::XML::Document::SHA256, settings.security[:digest_method] + assert_equal RubySaml::XML::Document::RSA_SHA256, settings.security[:signature_method] end end @@ -256,8 +256,8 @@ def initialize; end parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, { :settings => { :security => { - :digest_method => XMLSecurity::Document::SHA256, - :signature_method => XMLSecurity::Document::RSA_SHA256 + :digest_method => RubySaml::XML::Document::SHA256, + :signature_method => RubySaml::XML::Document::RSA_SHA256 } } }) diff --git a/test/logoutrequest_test.rb b/test/logoutrequest_test.rb index 28dc90d6..49528a4b 100644 --- a/test/logoutrequest_test.rb +++ b/test/logoutrequest_test.rb @@ -177,8 +177,8 @@ class RequestTest < Minitest::Test it "create a signed logout request with 256 digest and signature method" do settings.compress_request = false - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - settings.security[:digest_method] = XMLSecurity::Document::SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 + settings.security[:digest_method] = RubySaml::XML::Document::SHA256 params = RubySaml::Logoutrequest.new.create_params(settings) request_xml = Base64.decode64(params["SAMLRequest"]) @@ -189,8 +189,8 @@ class RequestTest < Minitest::Test it "create a signed logout request with 512 digest and signature method RSA_SHA384" do settings.compress_request = false - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 - settings.security[:digest_method] = XMLSecurity::Document::SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA384 + settings.security[:digest_method] = RubySaml::XML::Document::SHA512 params = RubySaml::Logoutrequest.new.create_params(settings) request_xml = Base64.decode64(params["SAMLRequest"]) @@ -261,73 +261,73 @@ class RequestTest < Minitest::Test end it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA256 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA384 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA384 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA512 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA512 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter using the first certificate and key" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.compress_request = false settings.certificate = nil settings.private_key = nil @@ -342,13 +342,13 @@ class RequestTest < Minitest::Test assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 4d5455b2..55f41901 100644 --- a/test/logoutresponse_test.rb +++ b/test/logoutresponse_test.rb @@ -234,7 +234,7 @@ class RubySamlTest < Minitest::Test it "return true when no idp_cert is provided and option :relax_signature_validation is present" do settings.idp_cert = nil - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -245,7 +245,7 @@ class RubySamlTest < Minitest::Test it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do settings.idp_cert = nil - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -254,7 +254,7 @@ class RubySamlTest < Minitest::Test end it "return true when valid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -263,7 +263,7 @@ class RubySamlTest < Minitest::Test end it "return true when valid RSA_SHA256 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -272,7 +272,7 @@ class RubySamlTest < Minitest::Test end it "return false when invalid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params['RelayState'] = 'http://invalid.example.com' options = {} options[:get_params] = params @@ -281,7 +281,7 @@ class RubySamlTest < Minitest::Test end it "raise when invalid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false params['RelayState'] = 'http://invalid.example.com' options = {} @@ -293,7 +293,7 @@ class RubySamlTest < Minitest::Test end it "raise when get_params encoding differs from what this library generates" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false options = {} options[:get_params] = params @@ -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 = RubySaml::XML::BaseDocument.new.algorithm(settings.security[: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, @@ -329,7 +329,7 @@ class RubySamlTest < Minitest::Test end it "return true even if raw_get_params encoding differs from what this library generates" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false options = {} options[:get_params] = params @@ -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 = RubySaml::XML::BaseDocument.new.algorithm(settings.security[: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, @@ -373,7 +373,7 @@ class RubySamlTest < Minitest::Test before do settings.soft = true settings.idp_slo_service_url = "http://example.com?field=value" - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.security[:logout_responses_signed] = true settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text diff --git a/test/metadata_test.rb b/test/metadata_test.rb index 2b30c399..b97b8b38 100644 --- a/test/metadata_test.rb +++ b/test/metadata_test.rb @@ -340,7 +340,7 @@ class MetadataTest < Minitest::Test assert_match %r[], xml_text assert_match %r[], xml_text - signed_metadata = XMLSecurity::SignedDocument.new(xml_text) + signed_metadata = RubySaml::XML::SignedDocument.new(xml_text) assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") @@ -348,8 +348,8 @@ class MetadataTest < Minitest::Test describe "when digest and signature methods are specified" do before do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - settings.security[:digest_method] = XMLSecurity::Document::SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 + settings.security[:digest_method] = RubySaml::XML::Document::SHA512 end it "creates a signed metadata with specified digest and signature methods" do @@ -357,7 +357,7 @@ class MetadataTest < Minitest::Test assert_match %r[], xml_text assert_match %r[], xml_text - signed_metadata = XMLSecurity::SignedDocument.new(xml_text) + signed_metadata = RubySaml::XML::SignedDocument.new(xml_text) assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") @@ -400,7 +400,7 @@ def add_extras(root, _settings) assert_match %r[], xml_text assert_match %r[], xml_text - signed_metadata = XMLSecurity::SignedDocument.new(xml_text) + signed_metadata = RubySaml::XML::SignedDocument.new(xml_text) assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") diff --git a/test/onelogin_alias_test.rb b/test/onelogin_alias_test.rb index 18ee3dc9..84e0e628 100644 --- a/test/onelogin_alias_test.rb +++ b/test/onelogin_alias_test.rb @@ -132,7 +132,7 @@ def generate_audience_error(expected, actual) end it "be able to parse a document which contains ampersands" do - XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true) + RubySaml::XML::SignedDocument.any_instance.stubs(:digests_match?).returns(true) OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true) ampersands_response = OneLogin::RubySaml::Response.new(ampersands_document) diff --git a/test/request_test.rb b/test/request_test.rb index 9b6226c9..9fec1d85 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -258,8 +258,8 @@ class RequestTest < Minitest::Test end it "create a signed request with 256 digest and signature methods" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - settings.security[:digest_method] = XMLSecurity::Document::SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 + settings.security[:digest_method] = RubySaml::XML::Document::SHA512 params = RubySaml::Authrequest.new.create_params(settings) @@ -327,41 +327,41 @@ class RequestTest < Minitest::Test end it "create a signature parameter with RSA_SHA1 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA256 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA256 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter using the first certificate and key" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.compress_request = false settings.certificate = nil settings.private_key = nil @@ -376,13 +376,13 @@ class RequestTest < Minitest::Test assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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/response_test.rb b/test/response_test.rb index 588c3b61..be9a437e 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -74,7 +74,7 @@ def generate_audience_error(expected, actual) end it "be able to parse a document which contains ampersands" do - XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true) + RubySaml::XML::SignedDocument.any_instance.stubs(:digests_match?).returns(true) RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true) ampersands_response = RubySaml::Response.new(ampersands_document) @@ -382,7 +382,7 @@ def generate_audience_error(expected, actual) no_signature_response.stubs(:validate_subject_confirmation).returns(true) no_signature_response.settings = settings no_signature_response.settings.idp_cert_fingerprint = "3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7" - XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true) + RubySaml::XML::SignedDocument.any_instance.expects(:validate_signature).returns(true) assert no_signature_response.is_valid? end @@ -1377,7 +1377,7 @@ def generate_audience_error(expected, actual) it 'sign an unsigned SAML Response XML and initiate the SAML object with it' do xml = Base64.decode64(fixture("test_sign.xml")) - document = XMLSecurity::Document.new(xml) + document = RubySaml::XML::Document.new(xml) formatted_cert = RubySaml::Utils.format_cert(ruby_saml_cert_text) cert = OpenSSL::X509::Certificate.new(formatted_cert) diff --git a/test/settings_test.rb b/test/settings_test.rb index 1d607a34..865f0588 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -93,13 +93,13 @@ class SettingsTest < Minitest::Test it "does not modify default security settings" do settings = RubySaml::Settings.new settings.security[:authn_requests_signed] = true - settings.security[:digest_method] = XMLSecurity::Document::SHA512 - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 + settings.security[:digest_method] = RubySaml::XML::Document::SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA512 new_settings = RubySaml::Settings.new assert_equal new_settings.security[:authn_requests_signed], false - assert_equal new_settings.security[:digest_method], XMLSecurity::Document::SHA256 - assert_equal new_settings.security[:signature_method], XMLSecurity::Document::RSA_SHA256 + assert_equal new_settings.security[:digest_method], RubySaml::XML::Document::SHA256 + assert_equal new_settings.security[:signature_method], RubySaml::XML::Document::RSA_SHA256 end it "overrides only provided security attributes passing a second parameter" do diff --git a/test/slo_logoutrequest_test.rb b/test/slo_logoutrequest_test.rb index e0e7eb90..d1657c83 100644 --- a/test/slo_logoutrequest_test.rb +++ b/test/slo_logoutrequest_test.rb @@ -59,12 +59,12 @@ class RubySamlTest < Minitest::Test settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text settings.idp_cert = ruby_saml_cert_text - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = {} params['SAMLRequest'] = logout_request_deflated_base64 params['RelayState'] = 'http://invalid.example.com' params['Signature'] = 'invalid_signature' - params['SigAlg'] = XMLSecurity::Document::RSA_SHA1 + params['SigAlg'] = RubySaml::XML::Document::RSA_SHA1 options = {} options[:get_params] = params @@ -307,7 +307,7 @@ class RubySamlTest < Minitest::Test it "return true when no idp_cert is provided and option :relax_signature_validation is present" do settings.idp_cert = nil - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') params['RelayState'] = params[:RelayState] options = {} @@ -320,7 +320,7 @@ class RubySamlTest < Minitest::Test it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do settings.idp_cert = nil - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') params['RelayState'] = params[:RelayState] options = {} @@ -331,7 +331,7 @@ class RubySamlTest < Minitest::Test end it "return true when valid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') params['RelayState'] = params[:RelayState] options = {} @@ -342,7 +342,7 @@ class RubySamlTest < Minitest::Test end it "return true when valid RSA_SHA256 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') options = {} options[:get_params] = params @@ -353,7 +353,7 @@ class RubySamlTest < Minitest::Test end it "return false when invalid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') params['RelayState'] = 'http://invalid.example.com' params[:RelayState] = params['RelayState'] @@ -366,7 +366,7 @@ class RubySamlTest < Minitest::Test end it "raise when invalid RSA_SHA1 Signature" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') params['RelayState'] = 'http://invalid.example.com' @@ -383,7 +383,7 @@ class RubySamlTest < Minitest::Test it "raise when get_params encoding differs from what this library generates" do # Use Logoutrequest only to build the SAMLRequest parameter. - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false params = RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") # Assemble query string. @@ -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 = RubySaml::XML::BaseDocument.new.algorithm(settings.security[: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. @@ -418,7 +418,7 @@ class RubySamlTest < Minitest::Test it "return true even if raw_get_params encoding differs from what this library generates" do # Use Logoutrequest only to build the SAMLRequest parameter. - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.soft = false params = RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") # Assemble query string. @@ -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 = RubySaml::XML::BaseDocument.new.algorithm(settings.security[: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. @@ -455,7 +455,7 @@ class RubySamlTest < Minitest::Test it "handles Azure AD downcased request encoding" do # Use Logoutrequest only to build the SAMLRequest parameter. - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 settings.soft = false # Creating the query manually to tweak it later instead of using @@ -473,7 +473,7 @@ class RubySamlTest < Minitest::Test # Assemble query string. query = "SAMLRequest=#{params['SAMLRequest']}&SigAlg=#{params['SigAlg']}" # Make normalised signature based on our modified params. - sign_algorithm = XMLSecurity::BaseDocument.new.algorithm( + sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm( settings.security[:signature_method] ) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) @@ -506,7 +506,7 @@ class RubySamlTest < Minitest::Test settings.private_key = ruby_saml_key_text settings.idp_cert = nil settings.security[:logout_requests_signed] = true - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 end it "return true when at least a idp_cert is valid" do diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index 17fd13ab..94f995fd 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -152,8 +152,8 @@ class SloLogoutresponseTest < Minitest::Test end it "create a signed logout response with SHA384 digest and signature method RSA_SHA512" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 - settings.security[:digest_method] = XMLSecurity::Document::SHA384 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA512 + settings.security[:digest_method] = RubySaml::XML::Document::SHA384 logout_request.settings = settings params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") @@ -165,8 +165,8 @@ class SloLogoutresponseTest < Minitest::Test end it "create a signed logout response with SHA512 digest and signature method RSA_SHA384" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 - settings.security[:digest_method] = XMLSecurity::Document::SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA384 + settings.security[:digest_method] = RubySaml::XML::Document::SHA512 logout_request.settings = settings params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") @@ -237,82 +237,82 @@ class SloLogoutresponseTest < Minitest::Test end it "create a signature parameter with RSA_SHA1 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA256 /SHA256 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA256 params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA256 query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA384 params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA384 query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA512 params = RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA512 query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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 it "create a signature parameter using the first certificate and key" do - settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1 settings.compress_request = false settings.certificate = nil settings.private_key = nil @@ -327,13 +327,13 @@ class SloLogoutresponseTest < Minitest::Test assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] - assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + assert_equal params['SigAlg'], RubySaml::XML::Document::RSA_SHA1 query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + signature_algorithm = RubySaml::XML::BaseDocument.new.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_alias_test.rb b/test/xml_security_alias_test.rb new file mode 100644 index 00000000..7a7728fc --- /dev/null +++ b/test/xml_security_alias_test.rb @@ -0,0 +1,203 @@ +require_relative 'test_helper' +require 'xml_security' + +class XmlSecurityAliasTest < Minitest::Test + + describe "XmlSecurity alias to XMLSecurity" do + let(:decoded_response) { Base64.decode64(response_document_without_recipient) } + let(:document) { XMLSecurity::SignedDocument.new(decoded_response) } + let(:settings) { RubySaml::Settings.new } + + before do + @base64cert = document.elements["//ds:X509Certificate"].text + end + + it "should run validate without throwing NS related exceptions" do + assert !document.validate_signature(@base64cert, true) + end + + it "should run validate with throwing NS related exceptions" do + assert_raises(RubySaml::ValidationError) do + document.validate_signature(@base64cert, false) + end + end + + it "not raise an error when softly validating the document multiple times" do + 2.times { assert_equal document.validate_signature(@base64cert, true), false } + end + + it "not raise an error when softly validating the document and the X509Certificate is missing" do + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + assert !mod_document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test + end + + it "should raise Fingerprint mismatch" do + exception = assert_raises(RubySaml::ValidationError) do + document.validate_document("no:fi:ng:er:pr:in:t", false) + end + assert_equal("Fingerprint mismatch", exception.message) + assert_includes document.errors, "Fingerprint mismatch" + end + + it "should raise Digest mismatch" do + exception = assert_raises(RubySaml::ValidationError) do + document.validate_signature(@base64cert, false) + end + assert_equal("Digest mismatch", exception.message) + assert_includes document.errors, "Digest mismatch" + end + + it "should raise Key validation error" do + decoded_response.sub!("pJQ7MS/ek4KRRWGmv/H43ReHYMs=", + "b9xsAXLsynugg3Wc1CI3kpWku+0=") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + base64cert = mod_document.elements["//ds:X509Certificate"].text + exception = assert_raises(RubySaml::ValidationError) do + mod_document.validate_signature(base64cert, false) + end + assert_equal("Key validation error", exception.message) + assert_includes mod_document.errors, "Key validation error" + end + + it "correctly obtain the digest method with alternate namespace declaration" do + adfs_document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false)) + base64cert = adfs_document.elements["//X509Certificate"].text + assert adfs_document.validate_signature(base64cert, false) + end + + it "raise validation error when the X509Certificate is missing and no cert provided" do + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + exception = assert_raises(RubySaml::ValidationError) do + mod_document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test + end + assert_equal("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", exception.message) + end + + it "invalidaties when the X509Certificate is missing and the cert is provided but mismatches" do + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + cert = OpenSSL::X509::Certificate.new(ruby_saml_cert) + assert !mod_document.validate_document("a fingerprint", true, :cert => cert) # The fingerprint isn't relevant to this test + end + end + + 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") + end + end + + describe "#algorithm" do + 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::BaseDocument.new.algorithm("other") + end + end + + describe "Fingerprint Algorithms" do + let(:response_fingerprint_test) { RubySaml::Response.new(fixture(:adfs_response_sha1, false)) } + + it "validate using SHA256" do + sha256_fingerprint = "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21" + sha256_fingerprint_downcase = sha256_fingerprint.tr(':', '').downcase + + assert response_fingerprint_test.document.validate_document(sha256_fingerprint) + assert response_fingerprint_test.document.validate_document(sha256_fingerprint, true, fingerprint_alg: XMLSecurity::Document::SHA256) + + assert response_fingerprint_test.document.validate_document(sha256_fingerprint_downcase) + assert response_fingerprint_test.document.validate_document(sha256_fingerprint_downcase, true, fingerprint_alg: XMLSecurity::Document::SHA256) + end + end + + describe "Signature Algorithms" do + it "validate using SHA256" do + document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false)) + assert document.validate_document("3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7") + end + end + + describe "XmlSecurity::SignedDocument" do + + describe "#extract_inclusive_namespaces" do + it "support implicit namespace resolution for exclusive canonicalization" do + response = fixture(:no_signature_ns, false) + document = XMLSecurity::SignedDocument.new(response) + inclusive_namespaces = document.send(:extract_inclusive_namespaces) + + assert_equal %w[ #default saml ds xs xsi ], inclusive_namespaces + end + + it "return nil when inclusive namespace element is missing" do + response = fixture(:no_signature_ns, false) + response.slice! %r{} + + document = XMLSecurity::SignedDocument.new(response) + inclusive_namespaces = document.send(:extract_inclusive_namespaces) + + assert inclusive_namespaces.nil? + end + end + + describe '#validate_document' do + describe 'with valid document' do + describe 'when response has signed message and assertion' do + let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') } + let(:document) { RubySaml::Response.new(document_data).document } + let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } + + it 'is valid' do + assert document.validate_document(fingerprint, true), 'Document should be valid' + end + end + + describe 'when response has signed assertion' do + let(:document_data) { read_response('response_with_signed_assertion_3.xml') } + let(:document) { RubySaml::Response.new(document_data).document } + let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } + + it 'is valid' do + assert document.validate_document(fingerprint, true), 'Document should be valid' + end + end + end + + describe 'signature_wrapping_attack' do + let(:document_data) { read_invalid_response("signature_wrapping_attack.xml.base64") } + let(:document) { RubySaml::Response.new(document_data).document } + let(:fingerprint) { 'afe71c28ef740bc87425be13a2263d37971da1f9' } + + it 'is invalid' do + assert !document.validate_document(fingerprint, true), 'Document should be invalid' + end + end + + describe 'signature wrapping attack - doubled SAML response body' do + let(:document_data) { read_invalid_response("response_with_doubled_signed_assertion.xml") } + let(:document) { RubySaml::Response.new(document_data) } + let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } + + it 'is valid, but the unsigned information is ignored in favour of the signed information' do + assert document.document.validate_document(fingerprint, true), 'Document should be valid' + assert_equal 'someone@example.org', document.name_id, 'Document should expose only signed, valid details' + end + end + + describe 'signature wrapping attack - concealed SAML response body' do + let(:document_data) { read_invalid_response("response_with_concealed_signed_assertion.xml") } + let(:document) { RubySaml::Response.new(document_data) } + let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } + + it 'is valid, but fails to retrieve information' do + assert document.document.validate_document(fingerprint, true), 'Document should be valid' + assert document.name_id.nil?, 'Document should expose only signed, valid details' + end + end + end + end +end diff --git a/test/xml_security_test.rb b/test/xml_test.rb similarity index 82% rename from test/xml_security_test.rb rename to test/xml_test.rb index 59919849..b8a67176 100644 --- a/test/xml_security_test.rb +++ b/test/xml_test.rb @@ -1,14 +1,12 @@ require_relative 'test_helper' -require 'xml_security' +require 'ruby_saml/xml' -class XmlSecurityTest < Minitest::Test - include XMLSecurity - - describe "XmlSecurity" do +class XmlTest < Minitest::Test + describe "RubySaml::XML" do let(:decoded_response) { Base64.decode64(response_document_without_recipient) } - let(:document) { XMLSecurity::SignedDocument.new(decoded_response) } - let(:settings) { RubySaml::Settings.new() } + let(:document) { RubySaml::XML::SignedDocument.new(decoded_response) } + let(:settings) { RubySaml::Settings.new } before do @base64cert = document.elements["//ds:X509Certificate"].text @@ -30,7 +28,7 @@ class XmlSecurityTest < Minitest::Test it "not raise an error when softly validating the document and the X509Certificate is missing" do decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") - mod_document = XMLSecurity::SignedDocument.new(decoded_response) + mod_document = RubySaml::XML::SignedDocument.new(decoded_response) assert !mod_document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test end @@ -53,7 +51,7 @@ class XmlSecurityTest < Minitest::Test it "should raise Key validation error" do decoded_response.sub!("pJQ7MS/ek4KRRWGmv/H43ReHYMs=", "b9xsAXLsynugg3Wc1CI3kpWku+0=") - mod_document = XMLSecurity::SignedDocument.new(decoded_response) + mod_document = RubySaml::XML::SignedDocument.new(decoded_response) base64cert = mod_document.elements["//ds:X509Certificate"].text exception = assert_raises(RubySaml::ValidationError) do mod_document.validate_signature(base64cert, false) @@ -63,14 +61,14 @@ class XmlSecurityTest < Minitest::Test end it "correctly obtain the digest method with alternate namespace declaration" do - adfs_document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false)) + adfs_document = RubySaml::XML::SignedDocument.new(fixture(:adfs_response_xmlns, false)) base64cert = adfs_document.elements["//X509Certificate"].text assert adfs_document.validate_signature(base64cert, false) end it "raise validation error when the X509Certificate is missing and no cert provided" do decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") - mod_document = XMLSecurity::SignedDocument.new(decoded_response) + mod_document = RubySaml::XML::SignedDocument.new(decoded_response) exception = assert_raises(RubySaml::ValidationError) do mod_document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test end @@ -79,7 +77,7 @@ class XmlSecurityTest < Minitest::Test it "invalidaties when the X509Certificate is missing and the cert is provided but mismatches" do decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") - mod_document = XMLSecurity::SignedDocument.new(decoded_response) + mod_document = RubySaml::XML::SignedDocument.new(decoded_response) cert = OpenSSL::X509::Certificate.new(ruby_saml_cert) assert !mod_document.validate_document("a fingerprint", true, :cert => cert) # The fingerprint isn't relevant to this test end @@ -88,48 +86,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, RubySaml::XML::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#") + assert_equal canon_algorithm, RubySaml::XML::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#WithComments") + assert_equal canon_algorithm, RubySaml::XML::BaseDocument.new.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, RubySaml::XML::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315") + assert_equal canon_algorithm, RubySaml::XML::BaseDocument.new.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, RubySaml::XML::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11") + assert_equal canon_algorithm, RubySaml::XML::BaseDocument.new.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, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1") + assert_equal alg, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#sha1") 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::BaseDocument.new.algorithm("other") + assert_equal alg, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") + assert_equal alg, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha256") + assert_equal alg, RubySaml::XML::BaseDocument.new.algorithm("other") 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, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") + assert_equal alg, RubySaml::XML::BaseDocument.new.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, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512") + assert_equal alg, RubySaml::XML::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha512") end end @@ -140,8 +138,8 @@ class XmlSecurityTest < Minitest::Test sha1_fingerprint = "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72" sha1_fingerprint_downcase = sha1_fingerprint.tr(':', '').downcase - assert response_fingerprint_test.document.validate_document(sha1_fingerprint, true, fingerprint_alg: XMLSecurity::Document::SHA1) - assert response_fingerprint_test.document.validate_document(sha1_fingerprint_downcase, true, fingerprint_alg: XMLSecurity::Document::SHA1) + assert response_fingerprint_test.document.validate_document(sha1_fingerprint, true, fingerprint_alg: RubySaml::XML::Document::SHA1) + assert response_fingerprint_test.document.validate_document(sha1_fingerprint_downcase, true, fingerprint_alg: RubySaml::XML::Document::SHA1) end it "validate using SHA256" do @@ -149,46 +147,46 @@ class XmlSecurityTest < Minitest::Test sha256_fingerprint_downcase = sha256_fingerprint.tr(':', '').downcase assert response_fingerprint_test.document.validate_document(sha256_fingerprint) - assert response_fingerprint_test.document.validate_document(sha256_fingerprint, true, fingerprint_alg: XMLSecurity::Document::SHA256) + assert response_fingerprint_test.document.validate_document(sha256_fingerprint, true, fingerprint_alg: RubySaml::XML::Document::SHA256) assert response_fingerprint_test.document.validate_document(sha256_fingerprint_downcase) - assert response_fingerprint_test.document.validate_document(sha256_fingerprint_downcase, true, fingerprint_alg: XMLSecurity::Document::SHA256) + assert response_fingerprint_test.document.validate_document(sha256_fingerprint_downcase, true, fingerprint_alg: RubySaml::XML::Document::SHA256) end it "validate using SHA384" do sha384_fingerprint = "98:FE:17:90:31:E7:68:18:8A:65:4D:DA:F5:76:E2:09:97:BE:8B:E3:7E:AA:8D:63:64:7C:0C:38:23:9A:AC:A2:EC:CE:48:A6:74:4D:E0:4C:50:80:40:B4:8D:55:14:14" assert !response_fingerprint_test.document.validate_document(sha384_fingerprint) - assert response_fingerprint_test.document.validate_document(sha384_fingerprint, true, fingerprint_alg: XMLSecurity::Document::SHA384) + assert response_fingerprint_test.document.validate_document(sha384_fingerprint, true, fingerprint_alg: RubySaml::XML::Document::SHA384) end it "validate using SHA512" do sha512_fingerprint = "5A:AE:BA:D0:BA:9D:1E:25:05:01:1E:1A:C9:E9:FF:DB:ED:FA:6E:F7:52:EB:45:49:BD:DB:06:D8:A3:7E:CC:63:3A:04:A2:DD:DF:EE:61:05:D9:58:95:2A:77:17:30:4B:EB:4A:9F:48:4A:44:1C:D0:9E:0B:1E:04:77:FD:A3:D2" assert !response_fingerprint_test.document.validate_document(sha512_fingerprint) - assert response_fingerprint_test.document.validate_document(sha512_fingerprint, true, fingerprint_alg: XMLSecurity::Document::SHA512) + assert response_fingerprint_test.document.validate_document(sha512_fingerprint, true, fingerprint_alg: RubySaml::XML::Document::SHA512) end end describe "Signature Algorithms" do it "validate using SHA1" do - document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false)) + document = RubySaml::XML::SignedDocument.new(fixture(:adfs_response_sha1, false)) assert document.validate_document("C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21") end it "validate using SHA256" do - document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false)) + document = RubySaml::XML::SignedDocument.new(fixture(:adfs_response_sha256, false)) assert document.validate_document("3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7") end it "validate using SHA384" do - document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false)) + document = RubySaml::XML::SignedDocument.new(fixture(:adfs_response_sha384, false)) assert document.validate_document("C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21") end it "validate using SHA512" do - document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false)) + document = RubySaml::XML::SignedDocument.new(fixture(:adfs_response_sha512, false)) assert document.validate_document("C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21") end end @@ -198,7 +196,7 @@ class XmlSecurityTest < Minitest::Test describe "#extract_inclusive_namespaces" do it "support explicit namespace resolution for exclusive canonicalization" do response = fixture(:open_saml_response, false) - document = XMLSecurity::SignedDocument.new(response) + document = RubySaml::XML::SignedDocument.new(response) inclusive_namespaces = document.send(:extract_inclusive_namespaces) assert_equal %w[ xs ], inclusive_namespaces @@ -206,7 +204,7 @@ class XmlSecurityTest < Minitest::Test it "support implicit namespace resolution for exclusive canonicalization" do response = fixture(:no_signature_ns, false) - document = XMLSecurity::SignedDocument.new(response) + document = RubySaml::XML::SignedDocument.new(response) inclusive_namespaces = document.send(:extract_inclusive_namespaces) assert_equal %w[ #default saml ds xs xsi ], inclusive_namespaces @@ -228,14 +226,14 @@ class XmlSecurityTest < Minitest::Test response = fixture(:no_signature_ns, false) response.slice! %r{} - document = XMLSecurity::SignedDocument.new(response) + document = RubySaml::XML::SignedDocument.new(response) inclusive_namespaces = document.send(:extract_inclusive_namespaces) assert inclusive_namespaces.nil? end end - describe "XMLSecurity::DSIG" do + describe "RubySaml::XML::DSIG" do before do settings.idp_sso_service_url = "https://idp.example.com/sso" settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" @@ -249,13 +247,13 @@ class XmlSecurityTest < Minitest::Test request = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) request.sign_document(ruby_saml_key, ruby_saml_cert) # verify our signature - signed_doc = XMLSecurity::SignedDocument.new(request.to_s) + signed_doc = RubySaml::XML::SignedDocument.new(request.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) request2 = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) request2.sign_document(ruby_saml_key, ruby_saml_cert_text) # verify our signature - signed_doc2 = XMLSecurity::SignedDocument.new(request2.to_s) + signed_doc2 = RubySaml::XML::SignedDocument.new(request2.to_s) assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) end @@ -264,7 +262,7 @@ class XmlSecurityTest < Minitest::Test request.sign_document(ruby_saml_key, ruby_saml_cert_text) # verify our signature - signed_doc = XMLSecurity::SignedDocument.new(request.to_s) + signed_doc = RubySaml::XML::SignedDocument.new(request.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) end @@ -272,13 +270,13 @@ class XmlSecurityTest < Minitest::Test logout_request = RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) logout_request.sign_document(ruby_saml_key, ruby_saml_cert) # verify our signature - signed_doc = XMLSecurity::SignedDocument.new(logout_request.to_s) + signed_doc = RubySaml::XML::SignedDocument.new(logout_request.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) logout_request2 = RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) logout_request2.sign_document(ruby_saml_key, ruby_saml_cert_text) # verify our signature - signed_doc2 = XMLSecurity::SignedDocument.new(logout_request2.to_s) + signed_doc2 = RubySaml::XML::SignedDocument.new(logout_request2.to_s) signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) end @@ -287,13 +285,13 @@ class XmlSecurityTest < Minitest::Test logout_response = RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message") logout_response.sign_document(ruby_saml_key, ruby_saml_cert) # verify our signature - signed_doc = XMLSecurity::SignedDocument.new(logout_response.to_s) + signed_doc = RubySaml::XML::SignedDocument.new(logout_response.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) logout_response2 = RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message") logout_response2.sign_document(ruby_saml_key, ruby_saml_cert_text) # verify our signature - signed_doc2 = XMLSecurity::SignedDocument.new(logout_response2.to_s) + signed_doc2 = RubySaml::XML::SignedDocument.new(logout_response2.to_s) signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) end