Posts in Category: Posts

r509 (Ruby Certificate Authority API) v0.9 Released

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.

What is r509?

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.

Feature Highlights

A very incomplete list of the extensive changes made from 0.8.1 to 0.9.

  • Feature: Elliptic curve (ECDSA) support! You can now generate ECDSA keys and sign certificates within the normal r509 framework. This support depends on your system having EC support enabled in OpenSSL. Red Hat distributions (Fedora, RHEL, CentOS, etc) have this disabled by default. For more details check out the CSR+key generation and private key generation docs.
  • Feature: Rewrote all extension handling to directly parse the ASN.1 structures.
  • Feature: Added support in the signer and config files for more complex certificate policy data, as well as inhibit any policy, policy constraints, and name constraints extensions.
  • Feature: Support for multiple general name types in extensions. See GeneralName.
  • Feature: Automatically generated getter/setter methods added for all registered OIDs on R509::Subject. (See docs for details)
  • Bug Fix: The r509 command line script now properly self-signs with the specified message digest in interactive mode rather than always using SHA1.
  • Bug Fix: The r509 command line scripts now load using /usr/bin/env to work properly in rvm-based environments.
  • Refactor: The R509::CRL module has been refactored. R509::CRL::Administrator has changed significantly and R509::Crl::Parser has been renamed to R509::CRL::SignedList and gained new methods.
  • Refactor: Class names have been altered to be fully capitalized if they are an acronym.
  • Support Change: Due to limitations in the elliptic curve bindings Ruby 1.8.7 is no longer a supported r509 platform. Please upgrade to 1.9.3+!

Installing/Contributing

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

Ruby CA Tutorial

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!

Future Release Plans

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.

Two Essential rvmrc Lines

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"

The (Potential) Danger of Trivial Code Examples

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

SQL Example

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”.

Better SQL Example

$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.

Network Socket Example

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.

Better Network Socket Example

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

Populating Views Example

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.

HMAC Example

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.

Better HMAC Example

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.

Best HMAC Example

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.

Encryption Example

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).

Better Encryption Example

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…

Best Encryption Example

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?

Password-Based Encryption Example

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…)

Better Password-Based Encryption Example

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!)

Best Password-Based Encryption Example

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

Some Other Possible Examples

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!

  • NSURLConnection synchronously or doing significant processing in the main thread
  • Authentication cookies
  • Password storage (plaintext, simple hashes, global salt)
  • Temporary file creation
  • Reading a large file (past integer limits or just huge memory consumption and poor performance)
  1. It is not my intent to single out any particular language. Since each language has such a distinct ecosystem of docs/examples each language possesses strengths and weaknesses with regard to the examples I’ve provided
  2. Quiet in the back iptables aficionados.
  3. The current Ruby documentation around OpenSSL::Cipher is actually quite thorough about the pitfalls of the various things I discuss here, but when Googling for something like “ruby aes encryption” you can easily be steered in a dangerous direction
  4. The wisdom of using CBC is itself questionable, but let’s stick to outright mistakes for now
  5. I couldn’t resist.

Ruby Elliptic Curve (EC) Certificates

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.

Examples

Here’s how you’d create a CSR with an ECDSA private key. (You can also look at the R509::Csr object docs)

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, Postfix, and Fedora 17

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!

Caveats

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.

Install & Configure

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

Testing

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:
dkim-signed-email
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!

Building a CA (r509 Howto)

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.

Why? What’s this for?

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.

Are there any caveats?

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.

Setting Up Your Environment

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.

Getting the Gems

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

Setting Up The Data Store

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.

Create a Root Certificate

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:

  • CSR Bit Strength – This defaults to 2048. You can hit enter or type a different # here (like 4096) if you’d prefer
  • Message Digest – Defaults to sha1. Hit enter or type another digest (e.g. sha256)
  • C – This stands for country. Use a two letter ISO country code.
  • ST – This is state or province. You can type whatever you’d like here.
  • L – This is locality (city/town). You can type whatever you’d like here.
  • O – This is organization. Again, whatever you’d like.
  • OU – This is organizational unit. This is frequently left blank in certificates, but you can optionally enter something here (like “IT” for example)
  • CN – This is the common name. In the case of root certificates it is essentially the “name” of the certificate. You can choose whatever you’d like, but I recommend something like “MyCompany Root CA”
  • SAN Domains – Leave this blank and hit enter
  • Self-signed cert duration in days – use 3650 (or higher if you’d like greater than 10 year validity)

After these steps the script will write a certificate and private key to the files specified.

Set up the yaml configuration

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.

Adding the r509-ca-http rackup file

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

Running the 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:

  • http://localhost:9292/test/certificate/issue
  • http://localhost:9292/test/certificate/revoke
  • http://localhost:9292/test/certificate/unrevoke

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:

  • CA – enter r509_howto_ca5
  • Profile – enter server6
  • Validity Period – enter 31536000 (this is 1 year in seconds)
  • Fill out, C, ST, L, O, and CN with the data you want.
  • Leave SAN domains and emailAddress blank.
  • Paste a CSR in the CSR textarea. You can quickly generate a CSR by using the 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.

Revocation

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.

  • Enter r509_howto_ca for CA
  • For serial, you’ll want the serial number of your previously issued certificate. Run r509-parse cert.pem to get some details about your cert, then copy the value marked “Serial (Decimal)” and paste it into the serial field
  • Ignore the reason field for now.7

Now 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.

CRL Generation

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.

OCSP Responses

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!

Testing OCSP Responses

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:

  • Create a new csr and key using r509 --keyout localhost.key --subject "/CN=localhost/O=r509 test"
  • Head to http://localhost:9292/test/certificate/issue and issue the certificate, but be sure you set the CN to localhost
  • Save the resulting certificate to a file named localhost.cer

You 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.

Quick Start

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.

Github Project Pages

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

  1. You’ll want to secure this for any serious use obviously.
  2. Relative brevity. If you’re down here in the footnotes you know this is not a brief howto.
  3. This is an article unto itself. Suffice it to say, you must protect that key if you intend to use your CA for any serious purpose. The compromise of that key to third parties renders your entire CA hierarchy useless for identity purposes. Public CAs use HSMs and various multi-factor physical access controls to limit the risk of this occurring.
  4. With the obvious exception of the public key
  5. This is the name of the config of the certificate authority we wish to issue a certificate from. You can see it in the config.yaml file
  6. This is the issuance profile we want to use with the CA we’ve selected. You can have many issuance profiles, each corresponding to a certificate type you want to allow
  7. Reason codes are optional. The r509 documentation has a list of reason codes.
  8. Well, they’re supposed to at least.
  9. In a production environment you’d want this to be a separate server. Your CA should never be directly accessible on the web!
  10. We’re using Firefox because it checks OCSP by default, is tolerant of OCSP responders that resolve into the RFC 1918 address space, and is relatively easy to add root certificates to