This is the third segment in a series dealing with securing the environment of a rails environment. Previously: an introduction, and setting up a certificate authority.

Today, we’ll be fleshing out the application that lives in our environment, and authenticating a user via their certificate.

Application: Retsyn

We’re dealing with certs, after all.

Our goal: create an authentication system that identifies a user via a certificate. The user submits a certificate as part of the request, we verify it and use its public key to identify the user. We use the public key, since that’s what gets transmitted back on verification to the app by webrick, apache, and nginx.

You can find the source for the app in progress on github.

Retsyn User

We created our certificates and keys in the last post; now we create a bare rails app, and add a User model:

~~{bash} rails generate model user name:string public_key:string ~~

Since we’re using a public key as an identifier, increase the :limit of the public_key before you run the migration. Skipping this step will mean the public keys assigned to the users may be truncated (silently), and won’t resolve when we’re searching for them later. Also, don’t forget the index:

~~{ruby} class CreateUsers < ActiveRecord::Migration def self.up createtable :users do |t| t.string :name, :null => false t.string :publickey, :limit => 2048, :null => false

  t.timestamps
      end
      add_index :users, :public_key
      

end

def self.down drop_table :users end end ~~

We have a User model, now we need a simple user. Take your user.p12 file from above and extract the public key:

~~{bash} openssl pkcs12 -in path/to/user.p12 -out user.pubkey -clcerts -nokeys ~~

The public key block is the only thing we care about at the moment. From the rails console, create a user with the appropriate public key (be sure and include the trailing newline!), and a name that suits your taste. I used “User”. This will provide us with a user to break in our authentication system against.

Aside: SSL Via Webrick?

Yes, it can be done. You’ll need to add the following to the top of your script/rails file:

~~{ruby} require ‘rubygems’ require ‘rails/commands/server’ require ‘rack’ require ‘webrick’ require ‘webrick/https’

module Rails class Server < ::Rack::Server def defaultoptions super.merge({ :Port => 3443, :environment => (ENV[‘RAILSENV’] || “development”).dup, :daemonize => false, :debugger => false, :pid => File.expandpath(“tmp/pids/server.pid”), :config => File.expandpath(“config.ru”), :SSLEnable => true, :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER, :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open(“path/to/your/server/key”).read), :SSLCertificate => OpenSSL::X509::Certificate.new( File.open(“path/to/your/server/cert”).read), :SSLCACertificateFile => ‘path/to/your/ca/cert’, :SSLCertName => [[“CN”, WEBrick::Utils::getservername]] }) end end end ~~

What’s going on here?

We need to instruct Webrick to require SSL connections:

~~{ruby} :SSLEnable => true ~~

Recall from the discussion of the SSL handshaking process that an SSL server may request a certificate from a client attempting to establish an SSL connection. We’ve instructed the Webrick server above to do so with the folowing lines. The first line requires a verification, the second gives Webrick the location of the CA Certificate we created to validate the client certificate against. If the client certificate is present and signed by our CA, it will be passed along by Webrick as a part of the request. If not, Webrick will not populate the request with the client cert.

~~{ruby} :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER :SSLCACertificateFile => ‘path/to/your/ca/cert’, ~~

Finally, we provide a certificate and key (created earlier) for Webrick to send to connecting clients, so they can validate our credentials:

~~{ruby} :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open(“path/to/your/server/key”).read), :SSLCertificate => OpenSSL::X509::Certificate.new( File.open(“path/to/your/server/cert”).read), ~~

The process for configuring apache or nginx is similar, and we’ll address that later.

Authenticating Users

While Webrick will reject invalid certificates, we still want to ensure than any connecting user is present in our application. We will check this by adding a before filter to application_controller.rb:

~~{ruby} before_filter :authenticate

def authenticate head :status => 403 and return unless current_user end

helpermethod :currentuser def currentuser clientcert = request.env[‘SSLCLIENTCERT’] return nil if clientcert.nil? || clientcert.blank? @currentuser ||= User.findbypublickey(client_cert) end ~~

Our web server will add the public key to the request’s environment as 'SSLCLIENTCERT', so that’s what we check against. If either the cert or the user is not present, it will quickly eject them from the system.

Connecting to Retsyn

Start the web server and connect with curl:

~~{bash} curl -kv https://localhost:3443 ~~

The verbose output (the -v flag at work) that walks you through what’s happening.

The handshake: ~~{bash} … * SSLv3, TLS handshake, Client hello (1): * SSLv3, TLS handshake, Server hello (2): * SSLv3, TLS handshake, CERT (11): * SSLv3, TLS handshake, Server key exchange (12): * SSLv3, TLS handshake, Request CERT (13): * SSLv3, TLS handshake, Server finished (14): * SSLv3, TLS handshake, CERT (11): * SSLv3, TLS handshake, Client key exchange (16): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSLv3, TLS change cipher, Client hello (1): * SSLv3, TLS handshake, Finished (20): * SSL connection using DHE-RSA-AES256-SHA … ~~

The request:

~~{bash} GET / HTTP/1.1 User-Agent: [stuff] Host: localhost:3443 Accept: / ~~

And the response:

~~{bash} HTTP/1.1 403 Forbidden Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Cache-Control: no-cache X-Runtime: 0.018457 Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18) OpenSSL/1.0.0d … ~~

Curl is sending nothing when a certificate is being requested, so our authenticate filter is kicking back a 403 response, as it should. If you pass in the user’s cert and key, however, things look a little more rosy:

~~{bash} curl -k –cert certs/user.pem:test –key certs/user.key https://localhost:3443 ~~

Response:

~~{bash} HTTP/1.1 200 OK … ~~

A browser makes a connection the same way curl does. If a certificate isn’t present, the server will return a 403 Forbidden response. To properly authenticate, you’ll need to install the user.p12 file somewhere your browser can get to it. On Mac OS X 10.6.7, this is done through Keychain Access for webkit browsers, or via
Preferences > Advanced > Encryption > View Certificates using Firefox.

Adding Users

We should build a simple management console, as entering users via the console is going to get old pretty quickly. We should also add a validation on the User model ensuring that the submitted public keys are well-formed:

~~{ruby} require ‘openssl’

class User < ActiveRecord::Base validate :publickeyresolves

def publickeyresolves OpenSSL::X509::Certificate.new(self.publickey) rescue OpenSSL::X509::CertificateError => e errors.add(:publickey, “Certificate error: #{e.message}”) end end ~~

Creating a user with a malformed public_key will now generate a reasonable error.

Next

Users and authentication are done! In the next post, we’ll add secure file storage to our application, using both drive- and file-based encryption.