PKI

Public Key Infrastructure — certificate authority hierarchies, chain of trust, and enrollment workflows.

CA Hierarchy

A two-tier hierarchy: offline root CA signs the intermediate; intermediate signs everything else. The root key stays offline (air-gapped or in Vault).

Root CA (offline, 10-year validity)
└── Intermediate CA (online, 3-year validity)
    ├── Server certificates (1-year)
    ├── Client certificates (90-day to 1-year)
    └── Code signing certificates

Root CA — Generate Offline

Generate root CA private key — 4096-bit RSA or ed25519
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out root-ca.key
chmod 600 root-ca.key
Self-sign the root CA certificate — 10-year validity
openssl req -x509 -new -key root-ca.key -sha256 -days 3650 \
    -subj "/C=US/ST=California/O=Domus Digitalis/CN=Domus Root CA" \
    -out root-ca.pem
Verify the root CA
openssl x509 -in root-ca.pem -noout -subject -issuer -dates -serial

Intermediate CA

Generate intermediate key and CSR
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out intermediate-ca.key
openssl req -new -key intermediate-ca.key -sha256 \
    -subj "/C=US/ST=California/O=Domus Digitalis/CN=Domus Intermediate CA" \
    -out intermediate-ca.csr
Create intermediate CA extensions file
cat > intermediate-ext.cnf <<'EOF'
[v3_intermediate_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF

pathlen:0 prevents the intermediate from creating sub-CAs. This is deliberate: two tiers is enough.

Sign the intermediate with the root CA
openssl x509 -req -in intermediate-ca.csr -CA root-ca.pem -CAkey root-ca.key \
    -CAcreateserial -sha256 -days 1095 \
    -extfile intermediate-ext.cnf -extensions v3_intermediate_ca \
    -out intermediate-ca.pem
Build the CA chain file — servers need this
cat intermediate-ca.pem root-ca.pem > ca-chain.pem

CSR Generation

Generate key and CSR in one command — server certificate
openssl req -new -newkey rsa:2048 -nodes \
    -keyout server.key \
    -subj "/C=US/ST=California/O=Domus Digitalis/CN=web.inside.domusdigitalis.dev" \
    -out server.csr
CSR with Subject Alternative Names — required since Chrome 58
openssl req -new -newkey rsa:2048 -nodes \
    -keyout server.key \
    -subj "/CN=web.inside.domusdigitalis.dev" \
    -addext "subjectAltName=DNS:web.inside.domusdigitalis.dev,DNS:web,IP:10.50.1.100" \
    -out server.csr
Inspect a CSR — verify before submitting to CA
openssl req -in server.csr -noout -text | grep -A3 "Subject\|Alternative"

Signing with the Intermediate CA

Create server certificate extensions
cat > server-ext.cnf <<'EOF'
[server_cert]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:web.inside.domusdigitalis.dev,DNS:web,IP:10.50.1.100
EOF
Sign the CSR with the intermediate CA
openssl x509 -req -in server.csr -CA intermediate-ca.pem -CAkey intermediate-ca.key \
    -CAcreateserial -sha256 -days 365 \
    -extfile server-ext.cnf -extensions server_cert \
    -out server.pem
Verify the certificate chains correctly
openssl verify -CAfile ca-chain.pem server.pem

Certificate Extensions

Server authentication — web servers, API endpoints
extendedKeyUsage = serverAuth
Client authentication — EAP-TLS, mutual TLS, ISE endpoints
extendedKeyUsage = clientAuth
Both — server that also authenticates to peers
extendedKeyUsage = serverAuth, clientAuth
Code signing
extendedKeyUsage = codeSigning
OCSP signing — for your OCSP responder
extendedKeyUsage = OCSPSigning

CRL — Certificate Revocation List

Create an empty CRL
openssl ca -gencrl -keyfile intermediate-ca.key -cert intermediate-ca.pem \
    -out crl.pem -config openssl.cnf
Revoke a certificate
openssl ca -revoke compromised-cert.pem -keyfile intermediate-ca.key \
    -cert intermediate-ca.pem -config openssl.cnf
Regenerate CRL after revocation
openssl ca -gencrl -keyfile intermediate-ca.key -cert intermediate-ca.pem \
    -out crl.pem -config openssl.cnf
Check CRL contents
openssl crl -in crl.pem -noout -text

Vault PKI Engine

Vault automates PKI: issue, renew, and revoke certificates via API. No manual openssl commands in production.

Enable the PKI engine
vault secrets enable -path=pki pki
vault secrets tune -max-lease-ttl=87600h pki
Generate internal root CA in Vault
vault write pki/root/generate/internal \
    common_name="Domus Root CA" \
    ttl=87600h
Enable intermediate PKI
vault secrets enable -path=pki_int pki
vault write pki_int/intermediate/generate/internal \
    common_name="Domus Intermediate CA" \
    ttl=43800h
Create a role for issuing server certificates
vault write pki_int/roles/domus-server \
    allowed_domains="inside.domusdigitalis.dev" \
    allow_subdomains=true \
    max_ttl=8760h \
    key_type=rsa \
    key_bits=2048 \
    require_cn=false
Issue a certificate via Vault
vault write pki_int/issue/domus-server \
    common_name="web.inside.domusdigitalis.dev" \
    alt_names="web" \
    ip_sans="10.50.1.100" \
    ttl=720h
Create a role for client certificates (EAP-TLS)
vault write pki_int/roles/domus-client \
    allowed_domains="inside.domusdigitalis.dev" \
    allow_subdomains=true \
    max_ttl=2160h \
    key_type=rsa \
    key_bits=2048 \
    client_flag=true \
    server_flag=false

Certificate Lifecycle Commands

Check certificate expiry across all certs in a directory
for cert in /etc/ssl/certs/*.pem; do
    expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
    [[ -n "$expiry" ]] && printf "%-50s %s\n" "$(basename "$cert")" "$expiry"
done | sort -k2
Find certificates expiring in the next 30 days
for cert in /etc/ssl/certs/*.pem; do
    openssl x509 -in "$cert" -checkend 2592000 -noout 2>/dev/null || \
        echo "EXPIRING: $(basename "$cert")"
done
Chain building — assemble the full chain in correct order
# Order: server cert → intermediate → root
cat server.pem intermediate-ca.pem root-ca.pem > fullchain.pem

# Verify the assembled chain
openssl verify -CAfile root-ca.pem -untrusted intermediate-ca.pem server.pem