Secure Communication For Web Applications
Using OpenSSL to lock down Apache, MySQL, Passenger, and Rails
This is the first in a series on securing the environment of a Rails application. It will likely see some revision.
Security is hard; there are a lot of moving parts, any number of interacting systems, and more holes than a properly-aged swiss cheese. Having recently dealt with end-to-end secure communication, I found more questions than answers, and was often left to fumble on my own. That was trying, and I don't want it to happen again for me or anyone else.
We're going to build an app, then secure it all the way through. Some of this will be a remedial overview—if you're looking for something specific, feel free to skip ahead.
What we'll be covering:
- a basic introduction to the moving parts
- secure communication between a (Rails) application and a browser using mod_ssl with apache and nginx
- authenticating a user with a certificate
- secure communication between a application and a couple of databases (MySQL and Postgres)
- secure communication between applications
- securing a filesystem to store data
What we're either glossing over or not covering:
- making the rails app itself impenetrable
- making the system performant
- picking good passwords, or other basics
- the development environment, browsers, and clients will be running on Snow Leopard (10.6)
- the servers will all be running Ubuntu 10.4 LTS
While we're doing this, we'll be building a Rails application to manage certificate signing and issuance.
OpenSSL, TLS/SSL, Certificates, and Trust: 10,000 feet
OpenSSL is an open source toolkit implementing transport layer security (TLS) and its predecessor, the Secure Sockets Layer (SSL). Most of the software we'll be working with gains its strength from OpenSSL to enable secure communications. We'll be using the SSL abbreviation rather than TLS, since it ends up being more common.
Clients and servers can identify themselves through digital certificates (certs), usually public key certificates implementing the X.509 standard. A cert can be created using OpenSSL and used as a self-signed certificate, or a certificate signing request (CSR) can be generated, enabling the cert to be signed by a certificate authority (CA).
What is a certificate authority, what do they do, and why are they needed?
In general, a certificate authority is an entity which issues or signs digital certificates. By issuing or signing a digital certificate, a CA indicates that a certain amount of diligence has been taken to verify the identity of the certificate-holder, and that anyone who trusts the CA may be certain that the certificate-holder is who they claim to be, and that trust should be extended. In this fashion, certificate authorities serve as a trusted third party, and are all but necessary for most PKI methods.
In an SSL/TLS context, a certificate that has been signed by a certificate authority can be verified using the authority's root certificate, a public certificate issued by each authority. Certificate authorities make their roots freely available for exactly this purpose. (You may download Verisign's root certificates from their support pages.)
For instance, when connecting to Bank of America with Google Chrome, I see a little green lock indicating that the SSL Certificate the BoA web server sent to the browser is verified and trusted by one of the 50 or so root certificates installed in Chrome. I can click on the icon and see that the identity has been verified by VeriSign Class 3 Extended Validation SSL CA. Should I not trust my browser, I could download the certificate into my home directory and validate it myself using
# convert the cert to a verifiable format openssl x509 -in ~/www.bankofamerica.com.cer -inform DER -out bofa.pem # check the cert's info openssl x509 -in bofa.pem -text ... # verify the cert against the issuing ca's root (v.pem) openssl verify -verbose -CAfile ca/v.pem bofa.pem bofa.pem: OK
openssl has verified the certificate against Verisign's root cert, so I know the certificate was issued, or at least signed by Verisign. In short, if a certificate is signed by a trusted CA then we have some assurance that the person calling is who they claim.
Incidentally, Verisign has ~50% of the SSL certificate authority market, GoDaddy has ~25%, the rest being scattered among smaller companies. The barrier to entering that market is very high, with annual audits required for new providers.
In most cases, a web server won't require a signed digital certificate from the client in order to establish an SSL connection, as most web applications authenticate users in some other fashion (a username and password, for example). In our app, we will be requiring a client certificate, and putting that to use limiting access and authenticating users, but we'll be signing that client certificate ourselves.
Before we get to that process, let's look at an overview of the SSL handshake, as it applies to client-authenticated connections.
Shaking Hands: Server- and Client-Authentication
We're still at 10,000 feet here, but this overview of handshaking an SSL connection will suffice for our purposes.
A short summary:
- client begins handshake
- client contacts server with connection parameters (including preferred ciphers)
- server responds with its own parameters, a certificate, proposes a cipher, and requests a certificate from the client
- the client sends a certificate, an encrypted pre-master secret, and a signature
- client and server computer a master secret, from which keys are derived
- client and server send cipher change requests, closing out the handshake
- the connection is established, and data may be transferred securely
In slightly more detail:
When a client first contacts a server to establish a connection, the client says hello and sends along information about its state, a random number, type of compression, and the cryptographic algorithms it can support. Among the information sent is the intended key exchange method, either RSA or some flavor of non-anonymous Diffie-Hellman.
The server replies with similar information and sends its certificate, along with a session id in the case of a resumed connection. The server then requests a certificate from the client, allowing mutual authentication.
The client establishes the validity of the server's certificate and responds with its own certificate, a pre-master secret encrypted with the server's public key, and a digital signature verifying that the client is in possession of the private key for the certificate it submitted. The client and server then use the pre-master secret to compute, using the random numbers they've exchanged, a master secret to be used to derive key data.
The client sends an encrypted cipher change request and closes the handshake, indicating that the rest of its exchange will be encrypted, and the server responds in kind. At this point, secure communication is possible.
When our app completes this process successfully, we will know a few things:
- the client's certificate was signed by our own certificate authority
- the client is in possession of the private key for that certificate
- the client is (or is not) authorized to access our application
So let's figure out what we want the app to do.
Our App and Environs
To effectively demonstrate the facets of secure communication, we'll need our app to do the following:
- add a user to the system
- authenticate a user using a client certificate
- accept a file via upload
- store that file securely
- retrieve the file and transmit back to the requesting user securely
We'll set this up to run on apache and nginx, securing each, and on mysql and postgresql, securing those as well. Of course, before we can do any of this, we need a root certificate to verify against, and a way to issue and sign certificates. For that, we build a certificate authority.
In the next post, we'll look at setting up our own certificate authority, enabling us to sign certificates for our client applications and users.