class XMLSecurity::SignedDocument
Attributes
Public Class Methods
Source
# File lib/xml_security.rb, line 216 def initialize(response, errors = []) super(response) @errors = errors reset_elements end
Calls superclass method
Public Instance Methods
Source
# File lib/xml_security.rb, line 314 def cache_referenced_xml(soft, check_malformed_doc = true) reset_elements @processed = true begin nokogiri_document = XMLSecurity::BaseDocument.safe_load_xml(self, check_malformed_doc) rescue StandardError => error @errors << error.message return false if soft raise ValidationError.new("XML load failed: #{error.message}") end # create a rexml document @working_copy ||= REXML::Document.new(self.to_s).root # get signature node sig_element = REXML::XPath.first( @working_copy, "//ds:Signature", {"ds"=>DSIG} ) return if sig_element.nil? # 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} ) return if base64_signature.nil? base64_signature_text = OneLogin::RubySaml::Utils.element_text(base64_signature) @signature = base64_signature_text.nil? ? nil : Base64.decode64(base64_signature_text) # canonicalization method canon_algorithm = canon_algorithm REXML::XPath.first( sig_element, './ds:SignedInfo/ds:CanonicalizationMethod', 'ds' => DSIG ) noko_sig_element = nokogiri_document.at_xpath('//ds:Signature', 'ds' => DSIG) noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG) @cached_signed_info = noko_signed_info_element.canonicalize(canon_algorithm) ### Now get the @referenced_xml to use? rexml_signed_info = REXML::Document.new(@cached_signed_info.to_s).root noko_sig_element.remove # get inclusive namespaces inclusive_namespaces = extract_inclusive_namespaces # check digests @ref = REXML::XPath.first(rexml_signed_info, "./ds:Reference", {"ds"=>DSIG}) return if @ref.nil? reference_nodes = nokogiri_document.xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) hashed_element = reference_nodes[0] return if hashed_element.nil? canon_algorithm = canon_algorithm REXML::XPath.first( rexml_signed_info, './ds:CanonicalizationMethod', { "ds" => DSIG } ) canon_algorithm = process_transforms(@ref, canon_algorithm) @referenced_xml = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) end
Source
# File lib/xml_security.rb, line 235 def referenced_xml @referenced_xml end
Source
# File lib/xml_security.rb, line 222 def reset_elements @referenced_xml = nil @cached_signed_info = nil @signature = nil @signature_algorithm = nil @ref = nil @processed = false end
Source
# File lib/xml_security.rb, line 239 def signed_element_id @signed_element_id ||= extract_signed_element_id end
Source
# File lib/xml_security.rb, line 244 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 = OneLogin::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('SHA1') 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 base64_cert = Base64.encode64(cert.to_der) else if options[:cert] base64_cert = Base64.encode64(options[:cert].to_pem) else if soft return false else return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft) end end end validate_signature(base64_cert, soft) end
Validates the referenced_xml
, which is the signed part of the document
Source
# File lib/xml_security.rb, line 287 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 = OneLogin::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 end encoded_idp_cert = Base64.encode64(idp_cert.to_pem) validate_signature(encoded_idp_cert, true) end
Source
# File lib/xml_security.rb, line 398 def validate_signature(base64_cert, soft = true) if !@processed cache_referenced_xml(soft) end return append_error("No Signature Algorithm Method found", soft) if @signature_algorithm.nil? return append_error("No Signature node found", soft) if @signature.nil? return append_error("No canonized SignedInfo ", soft) if @cached_signed_info.nil? return append_error("No Reference node found", soft) if @ref.nil? return append_error("No referenced XML", soft) if @referenced_xml.nil? # get certificate object cert_text = Base64.decode64(base64_cert) cert = OpenSSL::X509::Certificate.new(cert_text) digest_algorithm = algorithm(REXML::XPath.first( @ref, "./ds:DigestMethod", { "ds" => DSIG } )) hash = digest_algorithm.digest(@referenced_xml) encoded_digest_value = REXML::XPath.first( @ref, "./ds:DigestValue", { "ds" => DSIG } ) encoded_digest_value_text = OneLogin::RubySaml::Utils.element_text(encoded_digest_value) digest_value = encoded_digest_value_text.nil? ? nil : Base64.decode64(encoded_digest_value_text) # Compare the computed "hash" with the "signed" hash unless hash && hash == digest_value return append_error("Digest mismatch", soft) end # verify signature unless cert.public_key.verify(@signature_algorithm.new, @signature, @cached_signed_info) return append_error("Key validation error", soft) end return true end
Private Instance Methods
Source
# File lib/xml_security.rb, line 469 def digests_match?(hash, digest_value) hash == digest_value end
Source
# File lib/xml_security.rb, line 486 def extract_inclusive_namespaces element = REXML::XPath.first( self, "//ec:InclusiveNamespaces", { "ec" => C14N } ) if element prefix_list = element.attributes.get_attribute("PrefixList").value prefix_list.split(" ") else nil end end
Source
# File lib/xml_security.rb, line 473 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..-1] sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei end
Source
# File lib/xml_security.rb, line 442 def process_transforms(ref, canon_algorithm) transforms = REXML::XPath.match( ref, "./ds:Transforms/ds:Transform", { "ds" => DSIG } ) transforms.each do |transform_element| if transform_element.attributes && transform_element.attributes["Algorithm"] algorithm = transform_element.attributes["Algorithm"] case algorithm when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" canon_algorithm = Nokogiri::XML::XML_C14N_1_0 when "http://www.w3.org/2006/12/xml-c14n11", "http://www.w3.org/2006/12/xml-c14n11#WithComments" canon_algorithm = Nokogiri::XML::XML_C14N_1_1 when "http://www.w3.org/2001/10/xml-exc-c14n#", "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 end end end canon_algorithm end