class XMLSecurity::SignedDocument

Attributes

signed_element_id[RW]

Public Class Methods

new(response, errors = []) click to toggle source
Calls superclass method
# File lib/xml_security.rb, line 191
def initialize(response, errors = [])
  super(response)
  @errors = errors
end

Public Instance Methods

validate_document(idp_cert_fingerprint, soft = true, options = {}) click to toggle source
# File lib/xml_security.rb, line 200
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 = cert_element.text
    cert_text = Base64.decode64(base64_cert)
    begin
      cert = OpenSSL::X509::Certificate.new(cert_text)
    rescue OpenSSL::X509::CertificateError => e
      return append_error("Certificate Error", soft)
    end

    if options[:fingerprint_alg]
      fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
    else
      fingerprint_alg = OpenSSL::Digest::SHA1.new
    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
      @errors << "Fingerprint mismatch"
      return append_error("Fingerprint mismatch", soft)
    end
  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
validate_signature(base64_cert, soft = true) click to toggle source
# File lib/xml_security.rb, line 243
def validate_signature(base64_cert, soft = true)

  document = Nokogiri.parse(self.to_s) do |options|
    options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
  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}
  )

  # 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}
  ).text
  signature = Base64.decode64(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})
  uri = ref.attributes.get_attribute("URI").value

  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_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 }
  ).text
  digest_value = Base64.decode64(encoded_digest_value)

  unless digests_match?(hash, digest_value)
    @errors << "Digest mismatch"
    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

  return true
end

Private Instance Methods

digests_match?(hash, digest_value) click to toggle source
# File lib/xml_security.rb, line 336
def digests_match?(hash, digest_value)
  hash == digest_value
end
extract_inclusive_namespaces() click to toggle source
# File lib/xml_security.rb, line 353
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
extract_signed_element_id() click to toggle source
# File lib/xml_security.rb, line 340
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