Class: XMLSecurity::SignedDocument

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

Constant Summary

Constants inherited from BaseDocument

BaseDocument::C14N, BaseDocument::DSIG

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from BaseDocument

#algorithm, #canon_algorithm

Constructor Details

#initialize(response, errors = []) ⇒ SignedDocument

Returns a new instance of SignedDocument.



162
163
164
165
166
# File 'lib/xml_security.rb', line 162

def initialize(response, errors = [])
  super(response)
  @errors = errors
  extract_signed_element_id
end

Instance Attribute Details

#errorsObject

Returns the value of attribute errors.



160
161
162
# File 'lib/xml_security.rb', line 160

def errors
  @errors
end

#signed_element_idObject

Returns the value of attribute signed_element_id.



159
160
161
# File 'lib/xml_security.rb', line 159

def signed_element_id
  @signed_element_id
end

Instance Method Details

#validate_document(idp_cert_fingerprint, soft = true) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/xml_security.rb', line 168

def validate_document(idp_cert_fingerprint, soft = true)
  # get cert from response
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
  unless cert_element
    if soft
      return false
    else
      raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
    end
  end
  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
    @errors << "Fingerprint mismatch"
    return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
  end

  validate_signature(base64_cert, soft)
end

#validate_signature(base64_cert, soft = true) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/xml_security.rb', line 193

def validate_signature(base64_cert, soft = true)
  # validate references

  # check for inclusive namespaces
  inclusive_namespaces = extract_inclusive_namespaces

  document = Nokogiri.parse(self.to_s)

  # create a working copy so we don't modify the original
  @working_copy ||= REXML::Document.new(self.to_s).root

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

  # verify signature
  signed_info_element     = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"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_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
  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', '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)
    digest_value                  = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)

    unless digests_match?(hash, digest_value)
      @errors << "Digest mismatch"
      return soft ? false : (raise OneLogin::RubySaml::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)
    @errors << "Key validation error"
    return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error"))
  end

  return true
end