Be Your Own CA
A certificate authority, stripped of
ceremony, is a key, a certificate that says
CA:true, and bookkeeping: what it has
signed, and which serial number comes next.
openssl ca is the bookkeeping program. It
is not glamorous — it keeps a text-file
database and flatly refuses to work without
one — but what it does is precisely what
every CA does, with the audits and lawyers
left as an exercise.
The lab builds two CAs on purpose. A root
that signs almost nothing and could, in a
realer world, live offline; and an
intermediate under it that does the
daily work of signing servers. Two levels is
how the public system is built, and it is
what makes chapter 11's "incomplete chain"
failure possible to demonstrate honestly.
examples/lab-setup.sh runs everything in
this chapter end to end; what follows is the
walk-through.
The configuration
openssl ca takes nearly all its orders
from a config file — examples/ca.cnf,
which defines both CAs. One section per CA:
[ CA_root ]
dir = ./root
database = $dir/index.txt
serial = $dir/serial
new_certs_dir = $dir/newcerts
private_key = $dir/private/root.key.pem
certificate = $dir/root.cert.pem
default_md = sha256
default_days = 365
policy = policy_any
unique_subject = no
Directories for its files, paths to its key
and certificate, the hash to sign with, and a
policy — which name fields a request
must have. policy_any here demands only a
common name; public CAs run policies with
lawyers attached. unique_subject = no
permits signing two certificates for the
same name, which the lab needs (a server
certificate and its expired twin).
Plus the extension sections the signatures
will stamp — the root's (CA:true), the
intermediate's (CA:true, pathlen:0 — it may
sign servers but no further CAs), and the
server's (its SAN list, CA:false):
[ v3_server ]
basicConstraints = CA:false
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
subjectAltName = DNS:server.example.test, IP:127.0.0.1
The bookkeeping files are made by hand,
once: an empty index.txt (the database)
and a serial file holding the first serial
number, 1000. If openssl ca fails with
a complaint about the database or serial,
this paragraph is why.
The root
A key, then a certificate it signs for
itself, wearing the v3_root_ca extensions:
$ openssl req -new -x509 -key root/private/root.key.pem -out root/root.cert.pem -days 3650 -subj "/CN=Mere Mortals Root CA/O=Example Lab" -config ca.cnf -extensions v3_root_ca
$ openssl x509 -in root/root.cert.pem -noout -subject -issuer -dates
subject=CN = Mere Mortals Root CA, O = Example Lab
issuer=CN = Mere Mortals Root CA, O = Example Lab
notBefore=Oct 18 17:39:03 2026 GMT
notAfter=Oct 15 17:39:03 2036 GMT
Ten years for the root — roots are long-lived by design, since replacing one means redistributing trust itself.
The intermediate
Key, CSR, and then the root at work via
openssl ca -name CA_root (the -name
selects which CA section signs; without it
the config's default — the intermediate —
signs):
$ openssl ca -config ca.cnf -name CA_root -extensions v3_inter_ca -in inter.csr.pem -out inter/inter.cert.pem -batch -notext
Using configuration from ca.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'Mere Mortals Intermediate CA'
organizationName :ASN.1 12:'Example Lab'
Certificate is to be certified until Oct 18 17:39:09 2027 GMT (365 days)
Write out database with 1 new entries
Database updated
Read that output once and openssl ca holds
no mysteries: it checked the CSR's signature,
showed the name it is about to certify,
asked for confirmation (-batch answered
yes), and updated its database — the
root/index.txt file now holds one line
recording serial 1000, the dates, and the
subject. That line is the CA's memory; lose
it and the CA forgets what it signed.
$ openssl x509 -in inter/inter.cert.pem -noout -subject -issuer
subject=CN = Mere Mortals Intermediate CA, O = Example Lab
issuer=CN = Mere Mortals Root CA, O = Example Lab
The server certificate, and the judge
Chapter 6's CSR, signed by the intermediate
with the v3_server extensions:
$ openssl ca -config ca.cnf -extensions v3_server -in server.csr.pem -out server.cert.pem -batch -notext
Then the moment the whole structure is for.
openssl verify walks the chain — here it
needs the root as the trust anchor
(-CAfile) and the intermediate offered as
an untrusted helper (-untrusted): the
chain's middle is evidence, not belief.
$ openssl verify -CAfile root/root.cert.pem -untrusted inter/inter.cert.pem server.cert.pem
server.cert.pem: OK
OK is the smallest complete sentence in
PKI: the signatures check, the dates check,
the chain reaches the root you named. Every
failure in chapter 11 is this command, or an
s_client handshake, producing something
else — and now you own a lab where the good
case passes, which is what makes the bad
cases legible.
Also built by the setup script, waiting for
their chapters: expired.cert.pem — the same
CSR signed with -startdate 20240101000000Z
-enddate 20250101000000Z, openssl ca's way
of minting history; wronghost.cert.pem —
valid, signed, and for other.example.test;
and the bundle files chapter 10 explains.