This post has been updated to be compatible with the r509 0.9 API and configuration file format.
I’ve covered how to create a self-signed certificate authority using OpenSSL in the past, but today we’re going to go step by step through the process of building a fully functional CA using r509 and its ancillary projects. At the end you’ll have a CA capable of issuing and revoking X.509v3 certificates via an HTTP interface1, publishing CRLs, and responding to OCSP requests.
Certificate authorities are useful in any number of environments where you want to establish the identity of a server (or client). Most commonly this takes the form of TLS handshakes on port 443 that are then used to transmit HTTP traffic (HTTPS). This howto aims to help you set up a basic certificate authority that you can use to issue certificates to consumers in your own environment for whatever specific use case you have.
Of course! For brevity2 and simplicity this document does several things that are not best practice for running your own CA. We will create a self-signed root, but won’t create a subordinate root to issue certs off of. We will use the root itself to sign OCSP responses rather than creating an OCSP signing delegate. We won’t talk at all about security around your root certificate private key3.
r509 is a ruby project, so you’ll want to have ruby. At this time I’d recommend the latest ruby (1.9.3-p286), but r509 is tested against 1.8.7, 1.9.3, ree, ruby-head, and rubinius 2.0 (in 1.9 mode). JRuby is not supported because r509 uses the OpenSSL bindings, which are unavailable there.
Once you have a functional ruby install, you’ll need to install some gems. We’ll start with r509 (which is available via rubygems.org).
gem install r509
Next, we’ll need r509-ca-http, a RESTful interface for issuing certificates.
gem install r509-ca-http
We’ll also want r509-middleware-validity to write validity data for our issued/revoked certs to a data store (in this case, redis). This gem is optional, but without it you will not be able to serve OCSP responses.
gem install r509-middleware-validity
Finally, r509-middleware-certwriter will write issued certificates to the filesystem for archival purposes. This gem is optional. You may choose to store your issued certificates in whatever way you wish.
gem install r509-middleware-certwriter
If we’re going to write to redis, we’ll need it installed. You can download the source, grab it from your Linux distribution’s package manager, or use a tool like homebrew if you’re on OS X (just type brew install redis). Once installed go ahead and start it up (either directly via redis-server or init.d) and leave it running. You can obviously skip this step if you’ve chosen to not use r509-middleware-validity.
Now that we have the gems (and their dependencies) installed we can set up a system to issue certificates. First let’s create a new directory called r509-howto-ca and cd into it (we’ll be working within this directory from now on). r509 comes with a utility (cleverly called “r509″). Simply type r509 --out root.cer --keyout root.key in the shell and it will enter an interactive mode that will let us generate the root certificate quickly and easily. You’ll need to choose the following:
After these steps the script will write a certificate and private key to the files specified.
r509 is largely configured through yaml files. Here’s an example configuration we’re going to customize for your needs.
--- certificate_authorities: r509_howto_ca: ca_cert: cert: root.cer key: root.key ocsp_start_skew_seconds: 3600 ocsp_validity_hours: 168 crl_list: r509_howto_ca_list.txt crl_number: r509_howto_ca_crlnumber.txt crl_validity_hours: 168 default_md: SHA1 profiles: server: basic_constraints: :ca: false ocsp_location: - http://ocsp.myca.com:9291 cdp_location: - http://crl.domain.com/r509_howto_ca.crl key_usage: - digitalSignature - keyEncipherment extended_key_usage: - serverAuth certificate_policies: [] default_md: SHA1 allowed_mds: - SHA256 - SHA512 - SHA1 subject_item_policy: CN: required O: required OU: optional ST: required C: required L: required certwriter: path: /absolute/path/to/wherever/you/want/the/certs |
Phew, there’s a lot in there (and this is a simple example!). You can check out the r509 config specification to learn more, but for now copy this config exactly and write it to a file named config.yaml in the same directory as your root+key.
r509-middleware-certwriter: Skip this if you are not using this gem. If you are, update the path parameter at the bottom to point to the absolute path where you want it to write certificates. This directory must exist.
r509-middleware-validity: Skip this if you are not using this gem. If you are, start up redis using the command redis-server or via init.d. You’ll want to leave this running for the remainder of the tutorial.
You’ll also need to create empty files named r509_howto_ca_crlnumber.txt and r509_howto_ca_list.txt in the same directory. You can do this via touch r509_howto_ca_crlnumber.txt r509_howto_ca_list.txt.
r509-ca-http is a gem that contains a sinatra server for performing simple certificate issuance. To start up the server you’ll need a rackup file. Write the following into a file named config.ru in the same directory as everything else.
require 'r509' require 'dependo' require 'logger' Dependo::Registry[:log] = Logger.new(STDOUT) begin gem 'r509-middleware-validity' require 'r509/middleware/validity' use R509::Middleware::Validity Dependo::Registry[:log].info "Using r509 middleware validity" rescue Gem::LoadError end begin gem 'r509-middleware-certwriter' require 'r509/middleware/certwriter' use R509::Middleware::Certwriter Dependo::Registry[:log].info "Using r509 middleware certwriter" rescue Gem::LoadError end config_data = File.read("config.yaml") Dependo::Registry[:config_pool] = R509::Config::CAConfigPool.from_yaml("certificate_authorities", config_data) require 'r509/certificateauthority/http/server' Dependo::Registry[:config_pool].all.each do |config| Dependo::Registry[:log].info "Config: " Dependo::Registry[:log].info "CA Cert:"+config.ca_cert.subject.to_s Dependo::Registry[:log].info "OCSP Cert (may be the same as above):"+config.ocsp_cert.subject.to_s Dependo::Registry[:log].info "OCSP Validity Hours: "+config.ocsp_validity_hours.to_s Dependo::Registry[:log].info "CRL Validity Hours: "+config.crl_validity_hours.to_s Dependo::Registry[:log].info "\n" end server = R509::CertificateAuthority::HTTP::Server run server |
We’ve got everything in place, so just type rackup -p 9292, hit enter, and you should see output something like this (although your details will vary):
I, [2012-11-02T11:22:43.202323 #33028] INFO -- : Config: I, [2012-11-02T11:22:43.202470 #33028] INFO -- : CA Cert:/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 CA Howto Root I, [2012-11-02T11:22:43.202559 #33028] INFO -- : OCSP Cert (may be the same as above):/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 CA Howto Root I, [2012-11-02T11:22:43.202611 #33028] INFO -- : OCSP Validity Hours: 168 I, [2012-11-02T11:22:43.202647 #33028] INFO -- : CRL Validity Hours: 168 I, [2012-11-02T11:22:43.202691 #33028] INFO -- : [2012-11-02 11:22:43] INFO WEBrick 1.3.1 [2012-11-02 11:22:43] INFO ruby 1.9.3 (2012-10-12) [x86_64-darwin12.2.0] [2012-11-02 11:22:43] INFO WEBrick::HTTPServer#start: pid=33028 port=9292 |
The server is now up and running on port 9292! The following test interfaces are available:
These interfaces are meant solely for testing. In a production environment please look at the r509-ca-http API.
Since we’re testing let’s go ahead and open a web browser and go to http://localhost:9292/test/certificate/issue
To issue a certificate off our root we’re going to need to fill in quite a bit of data. For security reasons r509-ca-http overwrites the data4 from a certificate signing request (CSR) with the data you specify. Let’s fill out the fields:
r509_howto_ca5server631536000 (this is 1 year in seconds)r509 script again, but be sure you leave “Self-signed cert duration in days” blank. You must include the —–BEGIN CERTIFICATE REQUEST—– and —–END CERTIFICATE REQUEST—– lines.Now click “gimme” and you’ll get your certificate! You should copy the cert and write it to a file named cert.pem.
Note: If you’re using the r509-middleware-validity gem or r509-middleware-certwriter you will also have a record of this issuance in redis or the cert will be written to the filesystem for archival purposes.
We’ve issued a certificate now, but what if we want to revoke it? Head to http://localhost:9292/test/certificate/revoke
There are 3 fields on this page. CA, serial, and reason.
r509_howto_ca for CAr509-parse cert.pem to get some details about your cert, then copy the value marked “Serial (Decimal)” and paste it into the serial fieldNow hit revoke and you’ll be given a PEM encoded X.509 CRL! You can also look in the r509_howto_ca_list.txt we created earlier to see that the serial is listed as revoked.
So we have a certificate and we revoked it, but how would a client who reaches a server using the certificate know that it was revoked? SSL certificates offer two methods for providing revocation information to relying parties. Let’s talk about Certificate Revocation Lists (CRLs) first.
Whenever a client receives a certificate it performs numerous checks8 to ascertain the acceptability of the certificate. One of those is looking within the certificate for a CRL Distribution Point (CDP). If found, the client fetches the CRL from the URL provided, validates it, and checks to see if the certificate in question is listed in the CRL. In our config.yaml above we had a line for cdp_location: - http://crl.domain.com/r509_howto_ca.crl. If you want your certificates to be able to check CRL you’ll need to change this to a domain name that you can control.
Once you’ve made that change you’ll need to set up something to copy CRLs obtained from http://localhost:9292/1/crl/r509_howto_ca/generate to a web accessible area9.
CRLs are nice to have, but the more modern solution for certificate revocation information is the Online Certificate Status Protocol (OCSP) (RFC 2560 and 5019). To use r509′s OCSP responder capabilities you will need to be using r509-middleware-validity, as well as installing one more gem. Time to gem install r509-ocsp-responder.
We are going to use the root to sign responses for our OCSP responder. This is not considered best practice for a variety of reasons, but it will simplify our setup. We’ll cover OCSP signing delegates in another post. For now, let’s set up the responder’s config yaml. The text below should be placed in a file named config.yaml, but not in the same directory as your CA. Let’s create an ocsp directory and place it in there. You’ll also want to copy the root cert and key from the r509_howto_ca directory into the ocsp directory (or you can change the paths below).
copy_nonce: true cache_headers: true max_cache_age: 60 certificate_authorities: { r509_howto_ca: { ca_cert: { cert: "root.cer", key: "root.key" } } } |
The first 3 params are described in the responder documentation and the certificate_authorities node is just the single CA we want to respond for at this time.
Next we’ll need the rackup file (written to config.ru just like last time, but in the same ocsp directory as the new config.yaml you just created)
require "r509" require "dependo" require 'r509/ocsp/responder/server' Dependo::Registry[:log] = Logger.new(STDOUT) require "r509/validity/redis" require 'redis' begin gem "hiredis" Dependo::Registry[:log].warn "Loading redis with hiredis driver" redis = Redis.new(:driver => :hiredis) rescue Gem::LoadError Dependo::Registry[:log].warn "Loading redis with standard ruby driver" redis = Redis.new end Dependo::Registry[:validity_checker] = R509::Validity::Redis::Checker.new(redis) R509::OCSP::Responder::OCSPConfig.load_config R509::OCSP::Responder::OCSPConfig.print_config responder = R509::OCSP::Responder::Server run responder |
If you want higher performance lookups you can optionally install the hiredis gem.
We can now start up the responder! rackup -p 9291
W, [2012-11-06T16:24:12.816341 #57647] WARN -- : Loading redis with standard ruby driver W, [2012-11-06T16:24:12.818524 #57647] WARN -- : Config loaded W, [2012-11-06T16:24:12.818575 #57647] WARN -- : Copy Nonce: true W, [2012-11-06T16:24:12.818624 #57647] WARN -- : Cache Headers: true W, [2012-11-06T16:24:12.818672 #57647] WARN -- : Max Cache Age: 60 W, [2012-11-06T16:24:12.818710 #57647] WARN -- : Config: W, [2012-11-06T16:24:12.818824 #57647] WARN -- : CA Cert:/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 CA Howto Root W, [2012-11-06T16:24:12.818915 #57647] WARN -- : OCSP Cert (may be the same as above):/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 CA Howto Root W, [2012-11-06T16:24:12.818965 #57647] WARN -- : OCSP Validity Hours: 168 W, [2012-11-06T16:24:12.819011 #57647] WARN -- : [2012-11-06 16:24:12] INFO WEBrick 1.3.1 [2012-11-06 16:24:12] INFO ruby 1.9.3 (2012-10-12) [x86_64-darwin12.2.0] [2012-11-06 16:24:12] INFO WEBrick::HTTPServer#start: pid=57647 port=9291 |
Awesome! If we were running a full CA here we’d want to update our r509-ca-http config yaml to have the correct OCSP URI, but the config from earlier is perfect for testing. Speaking of testing…let’s test this thing out!
We need to issue a new certificate using the test interface. First create a new directory called test_server and cd into it, then do the following:
r509 --keyout localhost.key --subject "/CN=localhost/O=r509 test"localhost.cerYou should now have two files (localhost.cer and localhost.key) in your test_server directory. Inside that directory create a third file named server.rb with the following data in it:
require 'webrick/https' require 'r509' cert = R509::Cert.load_from_file('localhost.cer').cert key = R509::PrivateKey.load_from_file('localhost.key').key s = WEBrick::HTTPServer.new( :Port => 8443, :DocumentRoot => Dir::pwd, :SSLEnable => true, :SSLCertificate => cert, :SSLPrivateKey => key ) trap("INT"){ s.shutdown } s.start |
You should also add this line to your /etc/hosts file:
127.0.0.1 ocsp.myca.com
Finally, we need to set up a web browser to trust our new root CA. Let’s use Firefox10. Open the preferences, click the advanced tab, then encryption, then click the view certificates button. You now have a list of certificates in various categories. Click the authorities tab at the top, then click “Import…” and choose the root CA we created (root.cer). Do not select localhost.cer. Mark the certificate as trusted for websites and then exit out of the preferences.
We’re now ready to test! Make sure your redis server is running (it should have been running when you issued the certificate! If not, issue another one) along with r509-ocsp-responder (rackup -p 9291 in the ocsp directory), then type ruby server.rb inside the test_server directory we just created above. Now open Firefox and navigate to https://localhost:8443 . If all goes well you should see it load the page with no problems and some log lines indicating the OCSP request occurred should show up in the ocsp responder output! Congratulations, you now have a functioning CA.
You can also download a zip file of the configurations and directory setup required to get started. Grab it and then check out the README.txt to get started. You’ll still need to generate your own certs, but this will give you all the config.ru and config.yaml files already in place.
r509 consists of quite a few gems. Here are all their Github project pages. Fork, contribute, complain (maybe not that last one)!
r509
r509-ocsp-responder
r509-ca-http
r509-middleware-validity
r509-middleware-certwriter
r509-validity-redis
Great write up, tutorial works a charm. I’d like to take this further, configuring the OCSP responder to respond as a delegate for another CA. It seems like this is possible, the config appears to support defining OCSP cert, but it’s not clear how the responder should get its list of revoked certs… for the toy CA in the tutorial, the responder appears to get this data from redis. What’s the intended way to populate that for another CA ?