Collective Idea

Collective Idea Logo

Steve Richert

Inter-Service Authentication with SSL

By Steve Richert on December 17, 2012 in authentication and ssl

At Collective Idea, we love building web services. Oftentimes we also build the client applications that consume those services.

One of the major challenges with a service-oriented architecture is authenticating communication between the client and the service as well as between services.

Oh, You Mean OAuth!

OAuth is a good candidate for this type of authentication. It provides flexibility in terms of scoped authorization levels, securely signed requests, token expiration and all sorts of other goodies. And these things are great, especially for public-facing applications, where anybody could register through your OAuth provider.

But if you’re only dealing with a small number of controlled, trusted clients, implementing an OAuth provider for each service might be a bit much.

Then What?

Every request to the services needs to identify its client application and more importantly, it needs to be verified. What we need is a tried and tested way to communicate trust.

Enter: SSL

We usually see SSL in the form of an “https://” URL in our browser, and a little lock icon to make us feel all warm and fuzzy. That feeling of security comes from the fact that our browser has identified the website and we can trust that they actually are who they say they are.

This sounds awfully close to what we’re looking for. If an SSL certificate can verify the recipient of a request, we should be able to reverse the process to verify the sender of a request.

SSL Authentication

To set up our SSL authentication, each client application needs its own SSL certificate. So first thing’s first: we need a private key with which to sign all of these SSL certificates… one key to rule them all. Signing all of the client certificates with the same private key will allow us to verify their authenticity later.

openssl genrsa -out master.key 1024

Now that we have our master key, we can start generating SSL certificates. Each certificate will require a new CSR (certificate signing request). CSR generation will prompt for information about the certificate and we’ll take that opportunity to identify the client application that this certificate belongs to.

# Enter "web-client" for "Organizational Unit Name."
# The remaining attributes aren't important.
openssl req -new -key master.key -out web-client.csr

With the CSR in hand, generating a signed certificate is easy.

openssl x509 -req -in web-client.csr -signkey master.key -out web-client.crt

This certificate is given to the client application and every request originating from the client will include the contents of that certificate in a custom header.

Meanwhile, in the service… when a request is received we’ll need to assert that the certificate provided is authentic. We do this by verifying that the certificate is signed by the master key. The best part is that the service doesn’t even need the private, master key to do that verification. It can use a public key derived from the master key instead.

openssl rsa -in master.key -pubout > master.pub

Every service gets this same public, master key and uses it to verify all incoming requests.

Finally, Ruby

Assuming your service is written in Rails, here’s how that verification might be implemented:

class ApplicationController < ActionController::Base
  before_filter :require_authentication

  private

  def require_authentication
    unless current_certificate.verify(public_key)
      head :forbidden
    end
  end

  def public_key
    @public_key ||= OpenSSL::PKey::RSA.new(ENV['AUTH_PUBLIC_KEY'])
  end

  def current_certificate
    @current_certificate ||= OpenSSL::X509::Certificate.new(request.headers['X-SSL-Auth'])
  end

  def current_client
    current_certificate.issuer.to_a.assoc('OU')[1]
  end
end

The public key used for verification is kept in the server’s environment at ENV['AUTH_PUBLIC_KEY'] and the current request’s certificate is pulled from the headers.

The certificate responds to the verify method which accepts the public key and simply returns true if the certificate is authentic or false otherwise.

You’ll notice the current_client method as well, which extracts the organizational unit from the certificate that we set during the CSR generation. This identifies the client application making the request. With that information, the service can implement any sort of access controls that may (or may not) be required.

In the Wild

At Collective Idea, we think SSL certificate authentication is a cool idea and in some cases, a great fit. It’s also comforting to know that Square uses this same approach for authentication between its own services.

In fact, this post was inspired by a talk by Square’s Chris Hunt at RubyConf 2012. His talk omits some of the details of certificate generation and verification but it’s a fantastic resource for good service-oriented architecture.

I’m personally new to SSL-based security so please leave a comment for how this might be improved or expanded upon. Thank you!

By Steve Richert on December 17, 2012 in authentication and ssl

10 Comments

  1. John Bachir

    John Bachir December 17, 2012 http://jjb.cc

    Interesting, thanks for writing this up.

    You don’t show how the client code is configured to send the cert with its requests.

    Also — how do you feel this approach compares to using “normal” server-side ssl + basic auth with a huge password? In both cases there is a secret that can be stolen and used by a malicious party just as easily (client cert vs. client password) so AFAIK the cert approach is not more secure. But maybe I’m missing something.

  2. Steve Richert

    Steve Richert December 17, 2012 http://collectiveidea.com

    John, with basic authentication, the service must be aware of which usernames and passwords are valid. One advantage of SSL certificates is that an (effectively) unlimited number of valid certificates can be issued to trusted clients with no code changes to the services.

    But you’re right to say that the advantage is not in the certificate’s security as much as its verifiability.

  3. Michel Barbosa

    Michel Barbosa December 17, 2012

    Hi Steve,

    If you plan on using certificates to do the authentication, you might as well opt for Client-Side Authentication straight in the webserver. You could configure the Apache/Nginx webserver to only accept certain client-side certificates  which have been issued by a given Certificate Authority by utilizing a so-called Certificate Chain. In that way, you don’t need any Ruby verification code in your application layer. Instead, you shift the verification to the webserver and require that client applications provide a private key and associated certificate when authenticating. You don’t need custom headers for that

    Cheers,

  4. Steve Richert

    Steve Richert December 17, 2012 http://collectiveidea.com

    Michel, that sounds pretty great. I’m no good at web server administration so the concept of sending a client-side certificate with the request is news to me. To clarify, the client would need to send the private key as well? Seems odd.

  5. Michel Barbosa

    Michel Barbosa December 17, 2012

    Well, actually, the client application’s private key is used to setup a shared secret as part of the so-called SSL handshake between the client and the server. The client application’s private key is never transmitted to the webserver in plaintext, it’s just used to establish a shared secret after which data to and from the webserver is encrypted for the duration of the SSL session (which can be configured server side as well).

    If you rely on Client-Side authentication in the webservers AND you make sure to issue unique certificates for each client application AND you make sure that the client applications securely store their private key, you can instruct the webserver to forward the client-side certificate to the application layer through the request headers. In that case, you can use the client-side X509 certificate as a lookup key inside your Rails application for identiying a certain application. Alternatively, you can issue certificates which already contain details of the certificate owner, such as the name or FQDN of a certain client application.

    EIther way, by using client-side authentication in the webserver, you don’t need any custom Ruby certificate validation :)

    Cheers,

    Michel  

  6. Ben

    Ben December 18, 2012

    @Michel,

    Thanks for you post on Client-side authentication. Do you know of any good step-by-step tutorials for implementing this? Preferably nginx-centric? Thanks, ~Ben

  7. Carson Reinke

    Carson Reinke December 18, 2012

    First one that came up: http://blog.nategood.com/client-side-certificate-authentication-in-ngi

  8. Kaleem Ullah

    Kaleem Ullah January 10, 2013

    Great post!
    Thanks.

  9. Luis Cipriani

    Luis Cipriani February 06, 2013 http://talleye.com

    Nice article, but is there a way of using a client public key on the servers instead of using the master.pub in all servers to verify the certificates?

    I’m thinking in something similar to what SSH does to authenticate or encrypt/decrypt the data. 

    This would enforce the clients to register themselves if they want to use that service. Insstead of doing this programmatically using the OU. Thanks

  10. Steve Richert

    Steve Richert February 08, 2013 http://collectiveidea.com

    Luis, yes you could certainly do something like that. The difference is that in the scenario outlined above, we don’t want to enable open registration with the service. We want all connections to the service to be previously and actively authorized by the service.

Post a Comment

Contact Us

Find us on Google Maps
Collective Idea
44 East 8th Street, Suite 410
Holland, Michigan 49423 USA 42.790334-86.105251

Follow us on the Interwebs

We are currently available for medium and long term projects. Please get in touch if we can be of service.