LinuxLXXI · TLS and PKICAs and chains
Certificate authorities and chains - the trust model in practice
What you'll learn
- Distinguish root, intermediate, and leaf CAs
- Set up a private CA hierarchy
- Distribute trust stores
- Manage the chain of trust
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
A certificate authority (CA) signs certificates. The trust chain goes from a trusted root CA through intermediate CAs to end-entity certificates. This lesson covers the trust model in practice.
CA hierarchy
A typical CA hierarchy:
Root CA (offline, secure storage)
│
├── Intermediate CA 1 (e.g. web services)
│ ├── server1.example.com
│ ├── server2.example.com
│ └── ...
│
├── Intermediate CA 2 (e.g. internal services)
│ ├── db.example.com
│ └── ...
│
└── Intermediate CA 3 (e.g. dev)
└── ...
The root signs the intermediates. The intermediates sign end-entity certificates. The root is kept offline; the intermediates are online.
Why a hierarchy
A single root CA is a high-value target. If compromised, every certificate is compromised. A hierarchy:
- Limits the blast radius: a compromised intermediate affects only the certificates it signed.
- Allows different policies for different intermediates (e.g. web vs internal).
- Allows rotation: a new intermediate can be issued without revoking the root.
The extension trap
Before the recipe, understand the one mistake that breaks most hand-built PKIs.
openssl x509 -req adds no X.509 v3 extensions. It
copies the subject and the public key from the CSR, signs,
and stops. Extensions requested in the CSR are discarded
unless you ask for them explicitly.
That matters twice:
- A CA certificate without
basicConstraints = critical,CA:TRUEis not a CA. Every chain built through it fails validation witherror 79 ... invalid CA certificate. - A server certificate without a
subjectAltNamecannot be matched to a hostname by any modern client. RFC 9525 removed the Common Name fallback; Chrome dropped it in 2017 and Go has rejected CN-only certificates since 1.15.
So every signing step below passes -extfile. That is not
optional polish. It is the difference between a working
PKI and one that has to be rebuilt.
Set up a private CA hierarchy
Run this on a disposable host or in a scratch directory. It
writes only under ~/ca and the current directory, and it
touches no system trust store.
Root CA
mkdir -p ~/ca/root/{certs,crl,csr,newcerts,private}
chmod 700 ~/ca/root/private
echo 1000 > ~/ca/root/serial
openssl genrsa -out ~/ca/root/private/root-ca.key 4096
openssl req -x509 -new -nodes -key ~/ca/root/private/root-ca.key \
-sha256 -days 7300 -out ~/ca/root/certs/root-ca.crt \
-subj "/C=US/ST=CA/O=MyOrg/CN=MyOrg Root CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash"
openssl req -x509 does honour -addext, so the root gets
its CA extensions inline. The root signs itself, so there
is no CSR to lose them.
Intermediate CA
mkdir -p ~/ca/intermediate/{certs,crl,csr,newcerts,private}
chmod 700 ~/ca/intermediate/private
echo 1000 > ~/ca/intermediate/serial
openssl genrsa -out ~/ca/intermediate/private/intermediate-ca.key 4096
openssl req -new -key ~/ca/intermediate/private/intermediate-ca.key \
-out ~/ca/intermediate/csr/intermediate-ca.csr \
-subj "/C=US/ST=CA/O=MyOrg/CN=MyOrg Intermediate CA"
cat > intermediate-ext.cnf <<'EOF'
basicConstraints = critical,CA:TRUE,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
EOF
openssl x509 -req -in ~/ca/intermediate/csr/intermediate-ca.csr \
-CA ~/ca/root/certs/root-ca.crt -CAkey ~/ca/root/private/root-ca.key \
-CAcreateserial -out ~/ca/intermediate/certs/intermediate-ca.crt \
-days 3650 -sha256 -extfile intermediate-ext.cnf
pathlen:0 says this intermediate may sign end-entity
certificates but no further CAs. That is the constraint you
want: it stops a compromised intermediate from minting its
own sub-CAs.
Note the csr directory in the mkdir list. Without it,
openssl req -new fails with
Can't open ... for writing, No such file or directory and
the rest of the recipe never runs.
End-entity (server) certificate
openssl genrsa -out server.key 2048
chmod 600 server.key
openssl req -new -key server.key -out server.csr \
-subj "/C=US/ST=CA/O=MyOrg/CN=server.example.com" \
-addext "subjectAltName=DNS:server.example.com,DNS:www.example.com"
cat > server-ext.cnf <<'EOF'
subjectAltName = DNS:server.example.com, DNS:www.example.com
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
EOF
openssl x509 -req -in server.csr \
-CA ~/ca/intermediate/certs/intermediate-ca.crt \
-CAkey ~/ca/intermediate/private/intermediate-ca.key \
-CAcreateserial -out server.crt -days 365 -sha256 \
-extfile server-ext.cnf
The SAN appears in both places for a reason. In the CSR it
tells the CA what you are asking for. In server-ext.cnf
it is what the CA actually issues. The CA decides; the CSR
only requests.
Verify before you distribute anything
# The intermediate must actually be a CA
openssl x509 -in ~/ca/intermediate/certs/intermediate-ca.crt \
-noout -ext basicConstraints
# X509v3 Basic Constraints: critical
# CA:TRUE, pathlen:0
# The leaf must actually carry a SAN
openssl x509 -in server.crt -noout -ext subjectAltName
# X509v3 Subject Alternative Name:
# DNS:server.example.com, DNS:www.example.com
# The chain must build and the hostname must match
openssl verify -CAfile ~/ca/root/certs/root-ca.crt \
-untrusted ~/ca/intermediate/certs/intermediate-ca.crt \
-verify_hostname server.example.com -purpose sslserver \
server.crt
# server.crt: OK
All three checks must pass. openssl verify on its own is
a weak test: it accepts a Common Name as a hostname long
after real clients stopped doing so. -verify_hostname
together with the explicit subjectAltName check is what
tells you a Go or Chrome client will accept the
certificate.
The chain for server.crt is: server -> intermediate -> root.
Chain order
The chain file the server presents has a fixed order: the leaf first, then each intermediate in issuing order, and the root omitted.
cat server.crt ~/ca/intermediate/certs/intermediate-ca.crt > server-chain.crt
The root is left out on purpose. The client already has it in its trust store, and a client that does not have it will not start trusting it because the server sent a copy. Sending the root only wastes handshake bytes.
Get the order wrong and some clients still work, because they reorder the certificates themselves. Others do not. That difference is what makes chain-order bugs look intermittent.
Distribute trust stores
Client systems trust the root CA. They receive
root-ca.crt and install it in their trust store:
- Debian / Ubuntu:
/usr/local/share/ca-certificates/, thenupdate-ca-certificates. - RHEL family:
/etc/pki/ca-trust/source/anchors/, thenupdate-ca-trust.
The intermediate is sent along with the end-entity certificate, in the order described above: leaf first, intermediates next, root omitted.
Cross-signing
An intermediate can be cross-signed by multiple root CAs. This allows the intermediate to be trusted by clients that trust any of the root CAs.
For migration or compatibility, cross-signing is a common tool.
Cleanup
If you built the hierarchy above as an exercise, remove it:
rm -rf ~/ca
rm -f server.key server.csr server.crt server-chain.crt \
server-ext.cnf intermediate-ext.cnf
Nothing in the recipe installs a trust anchor, so there is
no system state to undo. If you did add root-ca.crt to a
trust store while experimenting, remove it from
/usr/local/share/ca-certificates/ or
/etc/pki/ca-trust/source/anchors/, then re-run
update-ca-certificates or update-ca-trust. A forgotten
private root in a trust store is a standing compromise: its
key is on a lab machine, and anything it signs is trusted.
Knowledge check
Knowledge check · 5 questions
Q1. What is the role of a root CA in a CA hierarchy?
Q2. Root CAs are typically online and used for daily signing.
Q3. Which of the following are valid in a CA hierarchy? Select all that apply.
Q4. You sign an intermediate CSR with openssl x509 -req and no -extfile. What happens?
Q5. In what order should a server present its certificate chain?
Passing score: 75%. Answers are checked in this browser.