Skip to content

Client SSL authentication

Ruben Stranders edited this page Feb 9, 2015 · 1 revision

Using the nginx_pass_ssl_client_cert parameter, nginx can be configured to support SSL client authentication. This allows clients to authenticate themselves by with an SSL certificate that has been signed by your own SSL certificate authority.

  • Read this page for general information about SSL mutual authentication.
  • See here for instructions on how to create your own SSL certificate authority.
  • See here for details on the nginx_pass_ssl_client_cert parameter.

Instructions.

  1. Put the code below in lib/ssl_authentication/ssl_authentication.rb
  2. Add the line require 'ssl_authentication/ssl_authentication' to environment.rb, above the initialization of the Rails application.
  3. In the controller where you wish to allow authentication using a client SSL certificate, add the line authenticates_from_ssl_certificate ROOT_CA_FILE, where ROOT_CA_FILE contains the root authority X509 certificate(s) that you trust. Client SSL certificates signed by this root authority are granted access.
module SSLAuthentication
  module ControllerAdditions
    def self.included(base)
      base.extend ClassMethods
    end

    def authenticate_from_ssl_certificate(ssl_ca_files)
      # This performs mutual authentication with the certificate supplied by the client when establishing the
      # SSL connection
      certificate = load_certificate_from_headers

      if certificate.nil?
        Rails.logger.warn('[SSLAuthentication failed] No client certificate supplied')
        head :unauthorized # certificate wasn't supplied or was of incorrect format
        return
      end

      cert_store = OpenSSL::X509::Store.new
      ssl_ca_files.each { |f| cert_store.add_file f } # load the certificates and/or authorities that we trust

      if cert_store.verify(certificate) # verify that the certificate was signed by the root CA
        Rails.logger.info("[SSLAuthentication success] Trusted certificate supplied #{certificate.public_key}")
      else
        Rails.logger.warn("[SSLAuthentication failed] Untrusted certificate supplied #{certificate.public_key}")
        head :unauthorized
      end
    end

    def load_certificate_from_headers
      raw_certificate = request.headers['X-Client-Cert'] ||
          (request.headers['rack.session'] && request.headers['rack.session']['X-Client-Cert'])

      return nil unless raw_certificate.is_a?(String)

      # This is a hack. Due to a problem with nginx, the certificate has to be sent as a single line string.
      # Here  we reconstruct the certificate if it does not start with "-----BEGIN CERTIFICATE-----"
      certificate_content = if raw_certificate.starts_with?('-----BEGIN CERTIFICATE-----')
                              raw_certificate.gsub("\t", "") # certificate passed by nginx as request header
                            else
                              certificate_body = raw_certificate.scan(/.{1,64}/m).join("\n")
                              "-----BEGIN CERTIFICATE-----\n#{certificate_body}\n-----END CERTIFICATE-----"
                            end

      OpenSSL::X509::Certificate.new(certificate_content)
    end

    module ClassMethods
      def authenticates_from_ssl_certificate(*ca_files)
        before_filter { authenticate_from_ssl_certificate ca_files }
      end
    end
  end
end

if defined? ActionController::Base
  ActionController::Base.class_eval do
    include SSLAuthentication::ControllerAdditions
  end
end
Clone this wiki locally