Stop Paying For SSL Certificates You Don't Need
By- July 11, 2013
Securing your stack with a SSL/TLS doesn't have to be costly or time consuming. There are two sides to TLS: Public Key Infrastructure (PKI) and Public Key/Asymmetric Cryptography. Hopefully your public network data is already encrypted with SSL/TLS (leveraging Public Key Cryptography), but you can also leverage a PKI to easily gain additional security wins for your internal network traffic.
SSL/TLS is most frequently used for encrypting data on the wire. You want to prevent the person next to you at Starbucks from being able to see your password as you submit a login form on the WiFi. It is used to verify the identity of a web server -- peer verification. You want to know that data going to and from www.tinfoilsecurity.com actually is coming from our servers, and not from someone else's. Early on in the TLS handshake your browser (and any underlying library like OpenSSL) does most of the heavy lifting for you: checking that the certificate's CommonName or SubjectAltNames matches the hostname and verifying the chain of trust. However, a tragically rarely used feature of SSL/TLS is that the client also has the opportunity to present a certificate during the handshake and the server can use it to verify the client's identity as well.
Why doesn't every browser present a certificate to every SSL website identifying the visitor as me? It certainly would make logins simple, but the rub comes with trust -- any certificate could identify the user as Ben Sedat. Public Key Infrastructure tries to solve this, and if both the website and client trust an authority to sign the certificates and keep track of them then you can establish a trusted link and make it a lot harder to forge fake things into the system.
Websites do this by requesting a certificate from a set of known Certificate Authorities. Browsers and operating systems keep lists of valid CAs and alert you when the certificate's chain of trust isn't valid. These CAs charge for their services, usually anywhere from $10 to $100 a year. Both you and your website's visitor trust the CA and everything works well, at least until it doesn't. What isn't widely publicized is that you can easily set up your own CA.
This CA isn't going to be trusted by your website visitors, but your infrastructure can trust it as long as you keep it safe. All too often we see companies and services disabling SSL entirely. They think that they need an expensive certificate, signed by a well-known CA. In the case of a website with clients that aren't under your control, that may be necessary. However, if you have more direct control over the client (like if you're your own API client) you can establish who you want to trust. Many libraries, like the Ruby hooks around OpenSSL and SSL connections allow you to specify which certificate authorities you want to trust in addition to the system defaults.
There isn't a perfect security system but you can easily add hurdles to delay and frustrate an attacker. SSL encryption of internet traffic is one such hurdle, and enforcing client/server identities on top creates one more hoop an attacker has to jump through to be successful. A client certificate isn't the be-all-end-all either -- it can be combined with usernames/passwords, two-factor authentication on another device, or all sorts of other methods to harden a system even more.
Go Forth and Make Your Own CA
OpenSSL is the de-facto standard command line utility for dealing with certificates. At Tinfoil we also really like XCA (http://sourceforge.net/projects/xca/); it's open-source and provides a (mostly) usable UI over the somewhat arcane OpenSSL invocations. Creating a CA and some certificates is easy in XCA, and once you're done you can have pieces of your application authenticating each other and keeping the communication secure.
Creating a certificate authority
First secure your XCA database (and private keys) with good password(s). Keep these safe, and don't put it anywhere digital! This is a critical layer of security for your CA.
Generate your CA's private key
Choose a nice long key here. There's some debate over key sizes regarding performance but it's worth it to go big here unless you're constrained by an embedded device. 1024-bit is considered too small, with 2048-bit being better currently but eventually breakable. 4096-bit may take a while to generate the key but it's worth it. It's also worth noting that RSA keys are more performant while encrypting, while DSA is faster for signing requests. ECDSA uses elliptic curve, which allows for smaller keys but is more computationally intensive.
The equivalent openssl incantations (for newer OpenSSL versions it may be easier to use the
openssl genrsa -des3 -out blogCA-key.pem <bytes>
openssl dsaparam -out blogCA-params.pem
openssl gendsa -des3 -out blogCA-key.pem blogCA-params.pem
openssl ecparam -out blogCA-params.pem
openssl ecparam -genkey -in blogCA-params.pem -out blogCA-key.pem
openssl ec -des3 -in blogCA-key.pem -out blogCA-key.pem
Generate your CA's public certificate
Choose the CA template and Apply All of the extensions. This flags your new certificate as a Certificate Authority, which some clients care about when evaluating the chain of trust. Also give it a commonName, essentially an identifier for your certificate, and choose an end date fairly far in the future.
Via the command line:
openssl req -new -x509 -days 730 -key blogCA-key.pem -out blogCA-cert.pem
Generate a server certificate
You can generate the private key, request, and certificate for a server application all within XCA. For bonus points, you can generate the private key and request on your server/clients directly, transfer just the request over, sign it with the CA and transfer the certificate back to the server. This prevents the private key from ever going on your network, although secure tunnels like SSH can make this process safer.
Create the private keys via CLI:
openssl req -newkey rsa:<bytes> -keyout server1-key.pem -out server1.csr
openssl req -newkey dsa:blogCA-cert.pem -keyout server1-key.pem -out server1.csr
openssl req -newkey ec:blogCA-cert.pem -keyout server1-key.pem -out server1.csr
Signing the request via CLI:
openssl x509 -CA blogCA-cert.pem -CAkey blogCA-key.pem -CAcreateserial \ -days 365 -req -in server1.csr -out server1-cert.pem
You can repeat this process for your servers, and/or your clients. Now you have all the credential pieces to have pieces of your infrastructure reveal themselves and verify each other during communication.
Validating the trust
Now it's time to use the certificates in your infrastructure. Standard peer verification from the client's perspective checks against the requested hostname, but in (cloud) infrastructure that could be a frequently changing target. One option is to use a load balancer behind a CNAME. Another option when you are your own client is to check the common or subject names in the provided certificate and validate against those and your certificate authority.
There can be some gotchas in the minefield of secure SSL communication, especially as we're using the commonName as a service identifier rather than just matching the hostname. Some examples below perform peer verification from the client's perspective with that in mind. Feel free to send along any equivalent ones in other languages and I'd be happy to credit and include them.Sample ruby client code, with sockets:
require 'socket' require 'openssl' context = OpenSSL::SSL::SSLContext.new context.verify_mode = OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT store = OpenSSL::X509::Store.new store.add_file 'blogCA-cert.pem' context.cert_store = store context.verify_callback = proc do |preverify, ssl_context| raise OpenSSL::SSL::SSLError.new unless preverify && ssl_context.error == 0 end socket = TCPSocket.new('service.example.com', 8080) ssl_connection = OpenSSL::SSL::SSLSocket.new(socket, context) ssl_connection.connect ssl_connection.post_connection_check('server1') # Check the CommonName or AltSubjectNames # SSL Connection Ready for Use Now
require 'net/http' require 'openssl' store = OpenSSL::X509::Store.new store.add_file 'blogCA-cert.pem' Net::HTTP.start('service.example.com', 8080, :use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http| # Our own verification, as net/http uses hostname validation. Sadly, the flag for this (https://github.com/ruby/ruby/blob/trunk/lib/net/http.rb#L658) is unchecked raise 'Possible MITM' unless OpenSSL::SSL.verify_certificate_identity(http.peer_cert, 'server1') raise 'Bad trust chain' unless store.verify(http.peer_cert) # SSL Connection Ready for Use Now end
It's worth taking a moment to go over some basic CA security. Like a real CA, you need to keep your CA's passphrase and private key secret. I recommend taking a page from Bitcoin wallet private key protection and put the private key on some cold storage, like a VM or flash drive that you can keep entirely off the internet.
Expiration dates are the other gotcha of this type of security. Generating new keys and certificates should be easy now, so you can use a shorter certificate lifespan before you have to rotate them. If you use a calendaring solution it's worth it to put in a reminder a few weeks before the certificate expires so you remember to renew. Don't get caught unawares.
That's the basics of creating and using your own CA! For extra credit, you can post your CA's certificate revocation list someplace and check against that as well. Certificate Authorities are fairly easy to set up and provide extra hurdles of identification if someone has intruded on your network and wants to man-in-the-middle your network traffic.