class Saml::Util

Public Class Methods

collect_extra_namespaces(raw_xml) click to toggle source
# File lib/saml/util.rb, line 168
def collect_extra_namespaces(raw_xml)
  doc = Nokogiri::XML(raw_xml, nil, nil, Nokogiri::XML::ParseOptions::STRICT)
  doc.collect_namespaces.each_with_object({}) { |(prefix, path), hash| hash[prefix.gsub('xmlns:', '')] = path }
end
decrypt_assertion(encrypted_assertion, private_key) click to toggle source
# File lib/saml/util.rb, line 98
def decrypt_assertion(encrypted_assertion, private_key)
  encrypted_assertion_xml = encrypted_assertion.is_a?(Saml::Elements::EncryptedAssertion) ?
      encrypted_assertion.to_xml : encrypted_assertion.to_s
  encrypted_document      = Xmlenc::EncryptedDocument.new(encrypted_assertion_xml)

  Saml::Assertion.parse(encrypted_document.decrypt(private_key), single: true)
end
decrypt_encrypted_id(encrypted_id, private_key, fail_silent = false) click to toggle source
# File lib/saml/util.rb, line 142
def decrypt_encrypted_id(encrypted_id, private_key, fail_silent = false)
  encrypted_id_xml   = encrypted_id.is_a?(Saml::Elements::EncryptedID) ?
      encrypted_id.to_xml : encrypted_id.to_s
  encrypted_document = Xmlenc::EncryptedDocument.new(encrypted_id_xml)
  Saml::Elements::EncryptedID.parse(encrypted_document.decrypt(private_key, fail_silent))
end
download_metadata_xml(location) click to toggle source
# File lib/saml/util.rb, line 173
def download_metadata_xml(location)
  uri = URI.parse(location)

  http             = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl     = uri.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  add_cacert_file(http)

  request = Net::HTTP::Get.new(uri.request_uri)

  response = http.request(request)
  if response.code == '200'
    response.body
  else
    fail Saml::Errors::MetadataDownloadFailed, "Cannot download metadata for: #{location}: #{response.body}"
  end
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse,
    Net::HTTPHeaderSyntaxError, Net::ProtocolError => error
  raise Saml::Errors::MetadataDownloadFailed, "Cannot download metadata for: #{location}: #{error.message}"
end
encrypt_assertion(assertion, key_descriptor_or_certificate, include_certificate: false, include_key_retrieval_method: false) click to toggle source
# File lib/saml/util.rb, line 63
def encrypt_assertion(assertion, key_descriptor_or_certificate, include_certificate: false, include_key_retrieval_method: false)
  case key_descriptor_or_certificate
  when OpenSSL::X509::Certificate
    certificate = key_descriptor_or_certificate
    key_name    = nil
  when Saml::Elements::KeyDescriptor
    certificate = key_descriptor_or_certificate.certificate
    key_name    = key_descriptor_or_certificate.key_info.key_name
  else
    fail ArgumentError, "Expecting Certificate or KeyDescriptor got: #{key_descriptor_or_certificate.class}"
  end

  assertion = assertion.to_xml(nil, nil, false) if assertion.is_a?(Assertion) # create xml without instruct

  encrypted_data = Xmlenc::Builder::EncryptedData.new
  encrypted_data.set_encryption_method(algorithm: 'http://www.w3.org/2001/04/xmlenc#aes128-cbc')

  encrypted_key = encrypted_data.encrypt(assertion.to_s)
  encrypted_key.set_encryption_method(algorithm:               'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
                                      digest_method_algorithm: 'http://www.w3.org/2000/09/xmldsig#sha1')
  encrypted_key.key_info = if include_certificate || key_name
    key_info = Saml::Elements::KeyInfo.new(include_certificate ? certificate.to_pem : nil)
    key_info.key_name = key_name
    key_info
  end
  encrypted_key.encrypt(certificate.public_key)

  if include_key_retrieval_method
    encrypted_key.id = '_' + SecureRandom.uuid
    encrypted_data.set_key_retrieval_method (Xmlenc::Builder::RetrievalMethod.new(uri: "##{encrypted_key.id}"))
  end

  Saml::Elements::EncryptedAssertion.new(encrypted_data: encrypted_data, encrypted_keys: encrypted_key)
end
encrypt_element(element, target_element, encrypted_key_data, encrypted_data_options) click to toggle source
# File lib/saml/util.rb, line 106
def encrypt_element(element, target_element, encrypted_key_data, encrypted_data_options)
  key_name = encrypted_data_options.fetch(:key_name, Saml.generate_id)

  element.encrypted_data = Xmlenc::Builder::EncryptedData.new(encrypted_data_options)
  element.encrypted_data.set_encryption_method(algorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc')
  element.encrypted_data.set_key_name key_name

  original_encrypted_key = element.encrypted_data.encrypt(Nokogiri::XML(target_element.to_xml).root.to_xml, encrypted_data_options)

  encrypted_key_data.each do |key_descriptor, key_options = {}|
    encrypted_key_options = key_options.merge(id: Saml.generate_id, data: original_encrypted_key.data)

    encrypted_key = Xmlenc::Builder::EncryptedKey.new(encrypted_key_options)
    encrypted_key.add_data_reference(element.encrypted_data.id)
    encrypted_key.set_key_name(key_descriptor.key_info.key_name)
    encrypted_key.carried_key_name = key_name
    encrypted_key.set_encryption_method(algorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', digest_method_algorithm: 'http://www.w3.org/2000/09/xmldsig#sha1')
    encrypted_key.encrypt(key_descriptor.certificate.public_key)

    element.encrypted_keys ||= []
    element.encrypted_keys << encrypted_key
  end

  element
end
encrypt_encrypted_id(encrypted_id, key_descriptor, key_options = {}) click to toggle source
# File lib/saml/util.rb, line 137
def encrypt_encrypted_id(encrypted_id, key_descriptor, key_options = {})
  encrypted_id.encrypt(key_descriptor, key_options)
  encrypted_id
end
encrypt_name_id(name_id, key_descriptor, key_options = {}) click to toggle source
# File lib/saml/util.rb, line 132
def encrypt_name_id(name_id, key_descriptor, key_options = {})
  encrypted_id = Saml::Elements::EncryptedID.new(name_id: name_id)
  encrypt_encrypted_id(encrypted_id, key_descriptor, key_options)
end
parse_params(url) click to toggle source
# File lib/saml/util.rb, line 4
def parse_params(url)
  query = URI.parse(url).query
  return {} unless query

  params = {}
  query.split(/[&;]/).each do |pairs|
    key, value  = pairs.split('=', 2)
    params[key] = value
  end

  params
end
post(location, message, additional_headers = {}, proxy = {}) click to toggle source
# File lib/saml/util.rb, line 17
def post(location, message, additional_headers = {}, proxy = {})
  uri = URI.parse(location)
  default_proxy_settings = { addr: :ENV, port: nil, user: nil, pass: nil }
  proxy = default_proxy_settings.merge(proxy)

  http             = Net::HTTP.new(uri.host, uri.port, proxy[:addr], proxy[:port], proxy[:user], proxy[:pass])
  http.use_ssl     = uri.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  add_cacert_file(http)
  add_ssl_certificate_and_key(http)

  request      = Net::HTTP::Post.new(uri.request_uri, merged_headers(additional_headers))
  request.body = message

  http.request(request)
end
sign_xml(message, format = :xml, include_nested_prefixlist = false, &block) click to toggle source
# File lib/saml/util.rb, line 35
def sign_xml(message, format = :xml, include_nested_prefixlist = false, &block)
  message.add_signature

  document = Xmldsig::SignedDocument.new(message.send("to_#{format}"))

  if Saml::Config.include_nested_prefixlist || include_nested_prefixlist
    document.signatures.reverse.each_with_object([]) do |signature, nested_prefixlist|
      inclusive_namespaces = signature.signature.at_xpath('descendant::ec:InclusiveNamespaces', Xmldsig::NAMESPACES)

      if inclusive_namespaces
        nested_prefixlist.concat(inclusive_namespaces.get_attribute('PrefixList').to_s.split(' '))

        if signature.unsigned?
          inclusive_namespaces.set_attribute('PrefixList', nested_prefixlist.uniq.join(' '))
        end
      end
    end
  end

  if block_given?
    document.sign(&block)
  else
    document.sign do |data, signature_algorithm|
      message.provider.sign(signature_algorithm, data)
    end
  end
end
verify_xml(message, raw_body) click to toggle source
# File lib/saml/util.rb, line 149
def verify_xml(message, raw_body)
  document = Xmldsig::SignedDocument.new(raw_body)

  signature_valid = document.validate do |signature, data, signature_algorithm|
    node = document.signatures.find { |s| s.signature_value == signature }.signature.at_xpath('descendant::ds:KeyName', Xmldsig::NAMESPACES)
    key_name = node.present? ? node.content : nil

    message.provider.verify(signature_algorithm, signature, data, key_name)
  end

  fail Saml::Errors::SignatureInvalid unless signature_valid

  signed_node = document.signed_nodes.find { |node| node['ID'] == message._id }

  fail Saml::Errors::SignatureMissing unless signed_node

  message.class.parse(signed_node.canonicalize, single: true)
end

Private Class Methods

add_cacert_file(http) click to toggle source
# File lib/saml/util.rb, line 203
def add_cacert_file(http)
  return http unless Saml::Config.http_ca_file.present?
  http.cert_store = OpenSSL::X509::Store.new
  http.cert_store.set_default_paths
  http.cert_store.add_file(Saml::Config.http_ca_file)
  http
end
add_ssl_certificate_and_key(http) click to toggle source
# File lib/saml/util.rb, line 211
def add_ssl_certificate_and_key(http)
  return http unless Saml::Config.ssl_certificate.present?
  return http unless Saml::Config.ssl_private_key.present?
  http.key  = Saml::Config.ssl_private_key
  http.cert = Saml::Config.ssl_certificate
  http
end
merged_headers(headers) click to toggle source
# File lib/saml/util.rb, line 197
def merged_headers(headers)
  { 'Content-Type' => 'text/xml',
    'Cache-Control' => 'no-cache, no-store',
    'Pragma' => 'no-cache' }.merge(headers)
end