Class: XMLSecurity::SignedDocument

Inherits:
REXML::Document
  • Object
show all
Defined in:
lib/xml_security.rb

Constant Summary collapse

C14N =
"http://www.w3.org/2001/10/xml-exc-c14n#"
DSIG =
"http://www.w3.org/2000/09/xmldsig#"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(response) ⇒ SignedDocument

Returns a new instance of SignedDocument.

[View source]

42
43
44
45
# File 'lib/xml_security.rb', line 42

def initialize(response)
  super(response)
  extract_signed_element_id
end

Instance Attribute Details

#noko_sig_elementObject

Returns the value of attribute noko_sig_element.


40
41
42
# File 'lib/xml_security.rb', line 40

def noko_sig_element
  @noko_sig_element
end

#sig_elementObject

Returns the value of attribute sig_element.


40
41
42
# File 'lib/xml_security.rb', line 40

def sig_element
  @sig_element
end

#signed_element_idObject

Returns the value of attribute signed_element_id.


40
41
42
# File 'lib/xml_security.rb', line 40

def signed_element_id
  @signed_element_id
end

Instance Method Details

#validate(idp_cert_fingerprint, soft = true) ⇒ Object

[View source]

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/xml_security.rb', line 47

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::SHA1.hexdigest(cert.to_der)

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

  validate_doc(base64_cert, soft)
end

#validate_doc(base64_cert, soft = true) ⇒ Object

[View source]

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/xml_security.rb', line 64

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 FederazioneTrentina::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 FederazioneTrentina::Saml::ValidationError.new("Key validation error"))
  end

  return true
end