Monday, July 25, 2005

SSL keys/certs for VPN use

I've been meaning for a little while to make note of a minimal CA/device certificate setup for use between machines in, e.g., an stunnel or openvpn context where the connections are between devices under shared control.

The setup is simple:

  • A CA key and self-signed cert is generated.

  • The CA does not keep standard CA records, it just issues uniquely numbered certificates.

  • A series of device keys and CA-signed certs are generated.

  • These instructions assume that all commands are performed on the CA machine, so strict secrecy of the device private keys from the CA's standpoint is not be enforced, but can be trivially added by performing the "openssl keygen" and "openssl req" parts of the device's commands on the device and the "openssl x509" part on the "CA" machine.

  • Providing authenticity and integrity (and confidentiality, if device private keys are indeed generated on the CA machine) on the comminucation channels during key/cert initiation is not addressed here, but is certainly required.

So, here's a straightforward script to do the entire exercise with openssl 0.9.7e (per Debian Sarge):

#! /bin/bash

openssl genrsa 1024 >ca.key
openssl req -new -x509 -batch -subj "/CN=My private CA" -days 6000 -key ca.key >ca.cert

openssl genrsa 1024 >dev.key
openssl req -new -batch -subj /CN=dev -key dev.key | openssl x509 -req -days 6000 -CA ca.cert -CAkey ca.key -CAserial <(date +%s) >dev.cert


  • For both certificates, the subject Common Name (CN) string must be no more than 64 bytes in length.

  • openssl provides (at least) two ways to generate signed certificates. One is to use its full-fledged CA command "ca". This requires the maintenance of an index, archival copies of all certificates, etc. These are all appropriate functions for someone who is actually operating as a CA, but excessive for the present purpose which only requires that the device certificates all be signed by the same CA key. It is possible instead to use openssl's x509 command as a "mini-CA"; one need only specify the cert (-CA), the key (-CAkey) and the serial number file (-CAserial). The latter is essentially unavoidable, however for the present purpose strict sequential numbering is not relevant; all that is required is uniqueness to prevent choking by peers that notice that sort of thing. (This comes up in particular if the "devices" include web-browsers.) The unix date will do nicely for this purpose. It turns out that openssl is not terribly offended by the difficulty writing to the pipe's output end. (It emits an error, but copes.)

  • Passing the -x509 option to req tells it to self-sign, which avoids the need to pass a CSR to a seperate invocation of "openssl x509" when creating a CA cert.

  • Note that req also has a -newkey option which would obviate the seperate invocation of genrsa, however while this option allows control over the key type (RSA/DSA) of the generated key and where to write it, it does not allow control of the symmetric algorithm used to protect the private key itself and its hard-wired default is something other than "nothing", meaning that a passphrase must be supplied interactively. (Presumably some/all of this can be handled through the configuration file, but seperating the very simple genrsa command seemed at least as good. The latter also provides a "one command to generate each file" script, which also improves readability.)

  • For Debian Woody users, openssl 0.9.6c does not have -batch and -subj options, so a config file is required. It can be provided (as shown below) through an invisible pipe, no actual on-filesystem tempfiles (which, in turn, need cleaning up) are required.

#! /bin/bash


cat <<-EOF

[ req ]
prompt = no
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
CN = "$cn"


openssl genrsa 1024 >ca.key
openssl req -new -x509 -config <(config "My private CA") -days 6000 -key ca.key >ca.cert

openssl genrsa 1024 >dev.key
openssl req -new -config <(config dev) -key dev.key | openssl x509 -req -days 6000 -CA ca.cert -CAkey ca.key -CAserial <(date +%s ; sleep 5) >dev.cert

  • The woody version of openssl is a little more sensitive about writing to the output end of the -CAserial pipe. There is a race; if the pipe is closed after it tries to write, we see the same warning as in sarge ("text file busy"!), but if the pipe happens to close first, openssl actually aborts. Testing revealed failures 50-70% of the time. Adding a sleep eliminated the failures.