r509 v0.9 is out! This version contains huge new features, bug fixes, and quite a bit of refactoring of the API in preparation for 1.0.
r509 is a Ruby gem built using OpenSSL that is designed to ease management of a public key infrastructure. The r509 API facilitates easy creation of CSRs, signing (and parsing!) of certificates, revocation (CRL/OCSP), and much more. Together with projects like r509-ocsp-responder and r509-ca-http it is intended to be a complete (RFC 3280/5280) certificate authority for use in production environments.
A very incomplete list of the extensive changes made from 0.8.1 to 0.9.
Just gem install r509 (and any of the other r509 gems you use) and you’re ready to go! sha1sums for each gem are below if you wish to verify you have an unaltered gem.
If you’d like to contribute, just click the gem name to view the source on GitHub! Contribution and feedback is always welcome.
| Gem Name (with GitHub Link) | Version | sha1sum |
|---|---|---|
| r509 | 0.9 | 84606ed495d7cf70d7b968c9bd3487f2cb236d49 |
| r509-ocsp-responder | 0.3.2 | 8724fdcfbb7ddb54c9b9b3cd833ddb60f63e627b |
| r509-ca-http | 0.2 | a3f1095e529d1cc387934c289f3e4a9778e3d606 |
| r509-middleware-validity | 0.2.1 | 46025f7e4e9f2ff13c735462764a14751adc9812 |
| r509-middleware-certwriter | 0.2.1 | 89d4ca24c3e0191a9c696cc4b367bbf3805128d4 |
| r509-validity-redis | 0.4.1 | 3d8c168a8d12efe706c4cc1d0aa34e34357bf5e8 |
I’ve updated my how-to on building a CA with r509 to support the new version as well, so if you’re just getting started with r509 check it out!
r509 is largely feature complete. The intent is to release 1.0 in a few months when I’m comfortable with long-term support for the existing API. Releases will conform to the principles of semantic versioning.
If you use rvm on OS X and compile rubies more often than “almost never” you should really have a ~/.rvmrc file that contains these two lines.
cpus=`sysctl -n hw.ncpu` rvm_make_flags="-j$cpus" |
As programmers there are two things we appreciate most when attempting to accomplish something: documentation and examples. Unfortunately, for some classes of problems trivial examples take shortcuts that, if implemented in production code, cause performance problems and security flaws. Let’s take a tour.1
Here’s how to do a SQL query in PHP (3 ways!)
$username = $_REQUEST['username']; mysql_connect($server,$user,$password,$db); mysql_query("SELECT * FROM my_table where username=$username"); //but that's deprecated as of PHP5.5...let's do it with mysqli $dbconn = mysqli_connect($server,$user,$password,$db); mysqli_query($dbconn,"SELECT * FROM my_table where username=$username"); //or maybe we want PDO $pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password'); $statement = $pdo->query("SELECT * FROM my_table where username=$username"); |
Done! You know how to build SQL queries from query parameters now! Or…maybe not. Obviously each of these examples is potentially vulnerable to SQL injection. There are quite a few ways to solve this, but sample code rarely mentions that this code is utterly unsafe for production use because that would be “needlessly complicated”.
$username = $_REQUEST['username']; //deprecated as of 5.5 mysql_connect($server,$user,$password,$db); $safe_query = sprintf("SELECT * FROM my_table where username='%s'",mysql_real_escape_string($username)); mysql_query($safe_query); $dbconn = mysqli_connect($server,$user,$password,$db); $safe_query = sprintf("SELECT * FROM my_table where username='%s'",mysqli_real_escape_string($username)); mysqli_query($dbconn,"SELECT * FROM my_table where username=$username"); $pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password'); $statement = $pdo->prepare("SELECT * FROM my_table where username=:username"); $statement->execute(array(':username' => $username)); |
There is definitely a higher cognitive load associated with figuring out what these snippets do, but it is not high enough to justify the problems caused by ignoring it. The people who most need training about the danger inherent in these methods are the ones who are Googling for examples.
So you need to open a TCP socket? Here’s one way in Ruby.
require 'socket' server = TCPServer.open(10000) loop { client = server.accept client.puts "At the sound of the tone the current timestamp will be #{Time.now.to_i}" client.puts "Tone!" client.close } |
Go ahead, telnet to it and you’ll see the message. Pretty cool right? Except you’re bound to every interface on your machine.
require 'socket' server = TCPServer.open("localhost",10000) loop { client = server.accept client.puts "At the sound of the tone the current timestamp will be #{Time.now.to_i}" client.puts "Tone!" client.close } |
There, now we’re binding to just a single interface. Many examples of this sort of trivial TCP server online neglect to mention that binding to all interfaces will implicitly make your server available to anyone with network access to your machine.2
Many popular MVC frameworks in Ruby share instance variables from their controllers into the views. Let’s build a trivial example.
class SomeController def method @my_data = "Hello World!" end end |
<!--our view--> <p><%= @my_data %></p> |
Pretty slick! Unless your MVC doesn’t auto-escape your instance variables (Rails 3+ does this by default, calm down). If it does not, then if your variable comes from user input you have a potential XSS vector! A better example depends on the available HTML sanitization methods that the framework you choose to use supplies, so this is perhaps an example of where the security of apparently generic code is in fact highly dependent on the framework it runs within.
Totally trivial: HMAC-SHA256 signatures in Ruby.
require 'openssl' key = "key" data = "data to sign" digest = OpenSSL::Digest::SHA256.new puts OpenSSL::HMAC.hexdigest(digest,key,data) |
Simple. Well, as long as you ignore RFC 2104 at least. If you look deeper (and why would you? The code above works great!) you’ll find the following:
The definition of HMAC requires a cryptographic hash function, which
we denote by H, and a secret key K. We assume H to be a cryptographic
hash function where data is hashed by iterating a basic compression
function on blocks of data. We denote by B the byte-length of such
blocks (B=64 for all the above mentioned examples of hash functions),
and by L the byte-length of hash outputs (L=16 for MD5, L=20 for
SHA-1). The authentication key K can be of any length up to B, the
block length of the hash function. Applications that use keys longer
than B bytes will first hash the key using H and then use the
resultant L byte string as the actual key to HMAC. In any case the
minimal recommended length for K is L bytes (as the hash output
length).The key for HMAC can be of any length (keys longer than B bytes are
first hashed using H). However, less than L bytes is strongly
discouraged as it would decrease the security strength of the
function. Keys longer than L bytes are acceptable but the extra
length would not significantly increase the function strength. (A
longer key may be advisable if the randomness of the key is
considered weak.)Keys need to be chosen at random (or using a cryptographically strong
pseudo-random generator seeded with a random seed), and periodically
refreshed. (Current attacks do not indicate a specific recommended
frequency for key changes as these attacks are practically
infeasible. However, periodic key refreshment is a fundamental
security practice that helps against potential weaknesses of the
function and keys, and limits the damage of an exposed key.)
Encryption and digital signatures are tricky business and the cryptographic primitives people use to accomplish their goals are just that, primitive. As a programmer, you will likely be required to implement some type of encryption and signing at some point. It is an unfortunate reality that unless you have some training, experience, or are fortunate enough to use great tools you will likely get it wrong. Even something simple like having insufficient key length for your chosen HMAC digest could have serious consequences and no example is going to tell you that.
require 'openssl' digest = OpenSSL::Digest::SHA256.new key = OpenSSL::Random.random_bytes(digest.digest_length) data = "data to sign" puts OpenSSL::HMAC.hexdigest(digest,key,data) |
Pretty simple change, but now the key is decent. In this case I’ve chosen to generate enough random bytes to cover the length of the chosen digest. Of course, you don’t actually want to be generating a new key for every signature so the complexity will be higher since a “real world” implementation would generate the key out of band, save it to disk, then load it as required.
First generate a key with sufficient length and entropy
require 'openssl' digest = OpenSSL::Digest::SHA256.new key = OpenSSL::Random.random_bytes(digest.digest_length) File.open("/some/path",'w') { |f| f.write(key) } |
Now HMAC your data
require 'openssl' digest = OpenSSL::Digest::SHA256.new key = File.read("/some/path") data = "data to sign" puts OpenSSL::HMAC.hexdigest(digest,key,data) |
Of course now this code can’t be executed without making at least some modifications for your own environment. Damn. Moving on.
Here’s a problem that’s more subtle than HMAC. AES 256 (via Ruby OpenSSL::Cipher3) using cipher block chaining (a very common4 block cipher mode). Again we’ll be using Ruby.
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt key = enc.random_key # let's avoid the pitfall from HMAC and generate a good key ct = enc.update("my encrypted text is extremely important") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key pt = dec.update(ct) pt << dec.final puts pt |
There, a basic example of encryption and we’re using a good key. Unfortunately CBC requires two inputs for safe encryption, not just one. We’ve neglected the initialization vector (IV) so OpenSSL sets it to a 16 byte string of null characters (\0). This is very bad (and extremely common).
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt key = enc.random_key # let's avoid the pitfall from HMAC and generate a good key iv = enc.random_iv # we need this too! ct = enc.update("this encrypted text is actually somewhat secure") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key dec.iv= iv pt = dec.update(ct) pt << dec.final puts pt |
Once again the fixed version does not appear significantly more complex than the (admittedly confusing) original example, but upon closer inspection problems appear. The key and IV are both generated on a per execution basis. You do want to do this with an IV, but not with a key…
First generate a key
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt key = enc.random_key # let's avoid the pitfall from HMAC and generate a good key File.open("/some/path",'w') { |f| f.write(key) } |
Now encrypt/decrypt your data
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt key = File.read("/some/path") iv = enc.random_iv ct = enc.update("no one can ever read this") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key dec.iv= iv pt = dec.update(ct) pt << dec.final puts pt |
Once again we’ve had to split our example into two separate bits of code. One is executed a single time and the other is the common case. We do have a per-encryption IV that needs to be saved so you can fully decrypt later, so don’t forget about that! But what if you want to use a password rather than a pile of random bytes?
We’ve learned our lesson from before and we will not be forgetting the IV.
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt key = "Testing1" # a password provided by the user perhaps iv = enc.random_iv # we need this too! ct = enc.update("customer secrets go here!") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key dec.iv= iv pt = dec.update(ct) pt << dec.final puts pt |
No problem. The user is at fault for their own terrible password right? We just gave the block cipher the key, generated a proper IV, and now we’re done. Well, not so fast. Encryption has tricked you yet again. What you really need is PBKDF2, part of RFC 2898. (What, you didn’t know that? Guess that code sample you copied might have been kind of dangerous…)
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt user_password = "Testing1" key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(user_password,"somesalt",1000,32) enc.key= key iv = enc.random_iv ct = enc.update("my first ciphertext") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key dec.iv= iv pt = dec.update(ct) pt << dec.final puts pt |
Well that’s…substantially less understandable. What the hell are all those parameters in pbkdf2_hmac_sha1 anyway? And is it a good idea to use “somesalt” as a fixed string? (It might be okay, or it might be a serious problem depending on what you’re trying to accomplish. Damned nuance!)
Now things are getting complicated… Let’s assume we care about having a random salt (pretty likely since if you’re encrypting with user passwords you’ll find that users are terrible about having distinct passwords!). RFC 2898 recommends we use a salt of minimum length 64-bits (8 bytes). It also recommends a minimum of 1000 iterations of the password derivation function. Of course, the document is from 2000 and computers are much faster now than they used to be…Let’s choose 10000 iterations (somewhat arbitrarily).
require 'openssl' enc = OpenSSL::Cipher::AES256.new(:CBC) enc.encrypt user_password = "Testing1" length = 32 # byte length of an AES-256 key (256/8) iterations = 10000 salt = OpenSSL::Random.random_bytes(8) key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(user_password,salt,iterations,length) enc.key= key iv = enc.random_iv ct = enc.update("secure password-based encryption ahoy") ct << enc.final # and for completeness, here's a decryption dec = OpenSSL::Cipher::AES256.new(:CBC) dec.decrypt dec.key= key dec.iv= iv pt = dec.update(ct) pt << dec.final puts pt |
There, a nice secure way to do this. Of course, now that we’ve gone this far down the rabbit hole we have the issue of how to store per-encryption salts such that you can decrypt the ciphertext at a later time, but that’s an exercise left to the reader.5
I’m getting a bit long-winded so I’ll leave you with some more ideas for dangerous trivial examples. If you’ve got more don’t hesitate to speak up!
I’ve been working on elliptic curve support in a branch of r509, my Ruby project for managing X.509 certificates, and I’m pleased to say that it’s reached the point where it is mostly complete. I’m going to let it bake in the branch for a bit longer and then merge back into master for an eventual v0.9 release. If you need/want to issue EC-based certificates in Ruby then hopefully it will help; it papers over most of the (very serious) issues with the OpenSSL elliptic curve bindings in Ruby with the standard r509 API. Clone the project, check out the documentation, or just download the prebuilt gem.
For those who are using r509 with r509-ca-http please note that you’ll need the current HEAD of the master branch in git to be able to revoke/unrevoke as the ec branch of r509 also contains a significant refactor of the CRL handling code.
Here’s how you’d create a CSR with an ECDSA private key. (You can also look at the
csr = R509::Csr.new( :type => :ec, :subject => [ ['CN','somedomain.com'], ['O','My Org'], ['L','City'], ['ST','State'], ['C','US'] ] ) |
Specify a specific curve (default is secp384r1):
csr = R509::Csr.new( :type => :ec, :curve_name => 'sect283r1', :subject => [ ['CN','somedomain.com'], ['O','My Org'], ['L','City'], ['ST','State'], ['C','US'] ] ) |
You can also use the r509 command line script to generate elliptic curve CSRs or self-signed certificates. Type r509 --help for instructions or you can invoke it in interactive mode:
r509 --type ec |
DKIM, or DomainKeys Identified Mail, is a way to sign emails you send such that other mail servers can verify they came from your domain. This is accomplished by signing the email on the server with an RSA private key and having the receiving server verify the signature using a public key you publish via DNS TXT records. This can be very useful if you’re sending large volumes of mail and want sites like gmail, yahoo, and hotmail to not treat you as spam. So, let’s set it up for Fedora 17!
This guide is geared towards people who need to set up postfix to automatically sign outbound email for a specific domain. Extending it to sign mail for every domain on your server with a single key (or multiple different keys) is outside the scope, but is a trivial extension of the configuration described below.
yum install dkim-milter cd /etc/mail/dkim-milter/keys dkim-genkey -r -d whatever-your-domain-is.com |
At this point you should have a “default.private” and “default.txt” file in your current working directory (which is /etc/mail/dkim-milter/keys). default.txt contains the DNS TXT record you must add to your DNS entries. Here’s what it will look like:
default._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+rSlv01jhqLorJDSHe3hfbqFKMyRUdDKwh3BunLt8gBIsNvgoNfr1ykhjSA8jzgGc1n7zIXkI3RuoLHaXbGwb1kpuaWp+A4pfJz1plriem9qFRuCOYYkASy25UYK+MULYBwc73FoUQwGv7BvefCHNT6cQpOkSHaoJR4tcSL1OjwIDAQAB" ; ----- DKIM default for test |
You’ll also need to set up one more TXT record:
_domainkey IN TXT "t=y; o=~;" |
The t=y line lets consumers know you are in test mode, and o=~ denotes that some (but not all) of your emails will be signed. When you’re done testing be sure to remove t=y (and set o=- if you so desire).
Next let’s rename and chown the private key so dkim-milter can use it.
mv default.private default chown dkim-milter:dkim-milter default |
Now we’ll need to edit /etc/mail/dkim-milter/keys/keylist to add a line like this (substituting your domain):
*@whatever-your-domain-is.com:whatever-your-domain-is.com:/etc/mail/dkim-milter/keys/default |
Finally, add the postfix group to the dkim-milter user in /etc/group and make sure /var/run/dkim-milter is set to 770 permissions.
We’re all set configuring dkim-milter. Let’s start it up and configure it to turn on at boot.
systemctl start dkim-milter.service
systemctl enable dkim-milter.service |
Now that we’ve got dkim-milter set up we need to tell postfix to use it. Add the following lines to the end of your /etc/postfix/main.cf
milter_default_action = accept milter_protocol = 6 smtpd_milters = local:/var/run/dkim-milter/dkim-milter.sock non_smtpd_milters = local:/var/run/dkim-milter/dkim-milter.sock |
Now restart postfix and we’re ready to test
systemctl restart postfix.service |
To test, simply send an email from your server (using the domain you configured) to a gmail address. A valid signature will look like this valid signature from a google.com address:

If you don’t see a signed-by that means you’ve made a mistake in your configuration (or possibly your DNS hasn’t had time to propagate properly). You can see what the error is by looking at the raw email headers. Good luck!
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_location: 'URI:http://ocsp.myca.com:9291', ocsp_start_skew_seconds: 3600, ocsp_validity_hours: 168, cdp_location: 'URI:http://crl.myca.com/r509_howto_ca.crl', crl_list: 'r509_howto_ca_list.txt', crl_number: 'r509_howto_ca_crlnumber.txt', crl_validity_hours: 168, #7 days default_md: 'SHA1', #SHA1, SHA256, SHA512 supported. MD5 too, but you really shouldn't use that unless you have a good reason profiles: { server: { basic_constraints: "CA:FALSE", key_usage: [digitalSignature,keyEncipherment], extended_key_usage: [serverAuth], certificate_policies: [ ], subject_item_policy: { CN: "required", O: "required", OU: "optional", ST: "required", C: "required", L: "required" } } } } } #remove if not using certwriter 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 this line: cdp_location: 'URI: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