I have been wanting HTTPS on the small web services I run for friends. The commercial CA option — VeriSign and the handful of competitors — costs serious money for a basic certificate and requires verification that does not really make sense for small private services anyway.
The alternative is to be your own CA. The certificates produced will not be trusted by browsers without manual intervention, but for a small number of users on services I control, that is fine. The setup is half a day's work with OpenSSL — the new open source replacement for SSLeay — and produces something I have used for the last week with no friction.
This post is the procedure.
The conceptual model
A Certificate Authority is, mechanically, a key pair plus a discipline. The key pair signs other certificates. The discipline is the part that determines what those signatures mean.
For a commercial CA — the kind whose root certificates are pre-installed in browsers — the discipline includes verifying that whoever asks for a certificate actually controls the domain. The commercial CA charges for the work of verification. The browsers trust the CA's root because the discipline has been audited.
For a personal CA, you are the discipline. You verify your own domains because they are yours. The signatures are exactly as trustworthy as your own operational discipline. The browsers do not pre-trust your root, but you can install your root in your browser, and any service signed by your CA is then trusted automatically.
This is the model. It is small in code and not much larger in concept.
The procedure
First, generate the CA's key pair. The CA key is the most important thing in this whole exercise; it must be protected accordingly.
openssl genrsa -des3 -out ca.key 2048
This produces a 2048-bit RSA key, encrypted with a passphrase. The passphrase is mandatory — you will need to type it every time you sign a new certificate, but the alternative is leaving the CA key vulnerable to anyone who reads the file. Pick a strong passphrase. Write it on a piece of paper that lives in a desk drawer.
Second, generate the CA's self-signed root certificate.
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
This prompts for the various distinguished-name fields — country, organisation, common name. The common name should describe the CA, not any specific service. "Peter Bassill Personal CA" or similar. The 10-year validity is appropriate for a root certificate that you will not be re-rolling soon.
Third, generate a key for the service you want to certify.
openssl genrsa -out service.key 2048
This is the service's own key. It does not need a passphrase — the service will be reading it on startup and a passphrase here would mean manual intervention every time the service restarts. The file should be readable only by the user the service runs as.
Fourth, generate a Certificate Signing Request (CSR) for the service.
openssl req -new -key service.key -out service.csr
The distinguished name for the service certificate must include the common name matching the hostname browsers will use to reach it. If your service is at notes.example.com, the common name field is notes.example.com literally. Browsers compare the common name against the URL hostname; mismatches cause warnings.
Fifth, sign the CSR with the CA key.
openssl x509 -req -in service.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out service.crt -days 365
This produces service.crt, the signed certificate. Validity 1 year is a good default — short enough to limit damage if the service key is compromised, long enough not to be a chore to renew.
The -CAcreateserial option creates a serial-number file. Each certificate signed by the CA has a unique serial number. The CA file ca.srl is updated to track the next available number.
Sixth, install the service certificate and key on the web server. For Apache with mod_ssl, the relevant directives:
SSLEngine on
SSLCertificateFile /etc/apache/ssl/service.crt
SSLCertificateKeyFile /etc/apache/ssl/service.key
With Listen 443 and an appropriate VirtualHost block.
Seventh, install the CA's root certificate (ca.crt) into every browser that will use the service. In Netscape, this is via the security preferences — Certificates → Authorities → Import. In Internet Explorer, via the security tab. Both browsers prompt before importing.
Note what the import does: it tells the browser to trust any certificate signed by this CA. If your CA key is compromised, an attacker can produce certificates for any domain that your users will trust. This is the blast radius of a personal CA.
Where to be careful
A few things that are easy to get slightly wrong.
The CA key is gold. It must live on a machine that is locked down, off the network, ideally air-gapped. Any compromise of the CA key requires re-issuing all certificates, re-installing the new CA root in every browser, and revoking trust in the old root. Plan accordingly. My own CA key lives on a machine that boots from a CD-ROM and has no network connection.
Certificate revocation is annoying. If a service certificate is compromised, you want to revoke it. The revocation mechanism is a Certificate Revocation List (CRL) that you publish. Most browsers do not check CRLs by default. So in practice, if you have to revoke, you also have to re-issue and re-distribute manually. For small numbers of users this is fine; for larger user bases this is a real cost.
The validity period of the service certificate matters. Too long, and a compromised key remains useful to attackers for a long time. Too short, and you spend a lot of time renewing. One year is a good compromise for low-stakes services; six months for higher-stakes ones; longer than two years is, in my view, sloppy.
Browsers will warn the first time. Even after you import the CA root, there are often quirks where a fresh user sees a warning and has to confirm. Document the procedure for your users. Accept that they will, at some point, click "yes" to a warning they should not have. This is the deeper problem with the trust model and is not solvable at the personal-CA level.
What this is good for
- Internal services accessible only to a known small user base.
- Test environments that need to look like production.
- Tools where you, the operator, are the only user.
- Hobby services where the cost of a commercial certificate is unjustified.
What this is not good for: any service where you do not control which browsers will visit. Public-facing commercial services need a commercial certificate, full stop. The personal CA is for cases where you can install the root in every browser that matters.
A note on the broader trust model
The browser trust model — "trust this list of CAs because we audited them" — is, at the moment, the only model anyone has shipped at scale. It has problems. The CA model assumes the CAs are trustworthy and that their operational discipline holds. Both have failed in cases. There are alternatives — the SSH model of trusting the host directly, PGP's web of trust — that distribute the trust more broadly but are also less convenient.
For the next several years I expect the CA model to dominate, with growing scepticism about it. By 2010 or so I would expect to see real alternatives reach maturity. For now, a personal CA is the simplest way to get HTTPS on services where the user base is small enough to install your root once and then forget.
Half a day. A handful of files. Years of useful service.