class Ciam::XMLSecurity::SignedDocument

Constants

C14N
DSIG

Attributes

noko_sig_element[RW]
sig_element[RW]
signed_element_id[RW]

Public Class Methods

new(response) click to toggle source
Calls superclass method
# File lib/ciam/xml_security.rb, line 43
def initialize(response)
  super(response)
  extract_signed_element_id
end

Public Instance Methods

validate(idp_cert_fingerprint, soft = true) click to toggle source
# File lib/ciam/xml_security.rb, line 48
def validate(idp_cert_fingerprint, soft = true)
  # get cert from response
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
  base64_cert  = cert_element.text
  cert_text    = Base64.decode64(base64_cert)
  cert         = OpenSSL::X509::Certificate.new(cert_text)

  # check cert matches registered idp cert
  fingerprint = Digest::SHA2.hexdigest(cert.to_der)

  if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
    return soft ? false : (raise Ciam::Saml::ValidationError.new("Fingerprint mismatch"))
  end

  validate_doc(base64_cert, soft)
end
validate_doc(base64_cert, soft = true) click to toggle source
# File lib/ciam/xml_security.rb, line 65
def validate_doc(base64_cert, soft = true)
  # validate references
  # check for inclusive namespaces
  inclusive_namespaces = extract_inclusive_namespaces

  document = Nokogiri.parse(self.to_s)

  # store and remove signature node
  self.sig_element ||= begin
    element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>DSIG})
    element.remove
  end


  # verify signature
  signed_info_element     = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
  self.noko_sig_element ||= document.at_xpath('//ds:Signature', 'ds' => DSIG)
  noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
  canon_algorithm = canon_algorithm REXML::XPath.first(sig_element, '//ds:CanonicalizationMethod')
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
  noko_sig_element.remove

  # check digests
  REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
    uri                           = ref.attributes.get_attribute("URI").value

    hashed_element                = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
    canon_algorithm               = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod')
    canon_hashed_element          = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces).gsub('&','&')

    digest_algorithm              = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))

    hash                          = digest_algorithm.digest(canon_hashed_element)
    digest_value                  = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)

    unless digests_match?(hash, digest_value)
      return soft ? false : (raise Ciam::Saml::ValidationError.new("Digest mismatch"))
    end
  end

  base64_signature        = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
  signature               = Base64.decode64(base64_signature)

  # get certificate object
  cert_text               = Base64.decode64(base64_cert)
  cert                    = OpenSSL::X509::Certificate.new(cert_text)

  # signature method
  signature_algorithm     = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))

  unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
    return soft ? false : (raise Ciam::Saml::ValidationError.new("Key validation error"))
  end

  return true
end

Private Instance Methods

algorithm(element) click to toggle source
# File lib/ciam/xml_security.rb, line 143
def algorithm(element)
  algorithm = element.attribute("Algorithm").value if element
  algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
  case algorithm
  when 256 then OpenSSL::Digest::SHA256
  when 384 then OpenSSL::Digest::SHA384
  when 512 then OpenSSL::Digest::SHA512
  else
    OpenSSL::Digest::SHA1
  end
end
canon_algorithm(element) click to toggle source
# File lib/ciam/xml_security.rb, line 133
def canon_algorithm(element)
  algorithm = element.attribute('Algorithm').value if element
  case algorithm
    when "http://www.w3.org/2001/10/xml-exc-c14n#"         then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
    when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
    when "http://www.w3.org/2006/12/xml-c14n11"            then Nokogiri::XML::XML_C14N_1_1
    else                                                        Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
  end
end
digests_match?(hash, digest_value) click to toggle source
# File lib/ciam/xml_security.rb, line 124
def digests_match?(hash, digest_value)
  hash == digest_value
end
extract_inclusive_namespaces() click to toggle source
# File lib/ciam/xml_security.rb, line 155
def extract_inclusive_namespaces
  if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
    prefix_list = element.attributes.get_attribute("PrefixList").value
    prefix_list.split(" ")
  else
    []
  end
end
extract_signed_element_id() click to toggle source
# File lib/ciam/xml_security.rb, line 128
def extract_signed_element_id
  reference_element       = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
  self.signed_element_id  = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
end