Documentation menu

Certificate hierarchy

homepki builds exactly three tiers: a self-signed root CA per domain, intermediate CAs signed by the root, and server or client leaves signed by an intermediate.

The three tiers

TierCommandSigned byPath lengthDefault validity
Root CAhomepki root-ca -d runlocal.devitself12190 days
Intermediate CAhomepki intermediate-ca -d runlocal.dev -n bu1the root02190 days
Server leafhomepki server-cert -d runlocal.dev -i bu1 -s gwthe intermediatenot a CA365 days
Client leafhomepki client-cert -d runlocal.dev -i bu1 -c clithe intermediatenot a CA365 days

The root has path length 1, so it can sign intermediates only. Intermediates have path length 0, so they can sign leaves only: you cannot nest a second intermediate under one. Each tier requires the tier above it and stops with an error when it is missing.

CA certificates carry keyCertSign and cRLSign. Server leaves carry serverAuth, client leaves clientAuth, and both carry digitalSignature; an RSA server leaf also carries keyEncipherment. Serial numbers are random 128-bit values, and NotBefore is set 5 minutes in the past so a new certificate is already valid in a container or VM whose clock runs slightly behind.

Derived subjects

Subject names are not configurable. They are derived from the domain, the intermediate name and the leaf name:

TierOOUCN
Root CArunlocal-devrunlocal.dev
Intermediate CArunlocal-devbu1bu1.runlocal.dev
Leafrunlocal-devbu1gw.bu1.runlocal.dev

The organization is the domain with dots replaced by dashes, which is also the directory name. The leaf CN <leaf>.<intermediate>.<domain> is always the leaf's first DNS Subject Alternative Name. Add the names clients actually use with --san; most TLS clients ignore the CN and match SANs only. See Subject Alternative Names.

Several intermediates under one root

Create one intermediate per tenant, team or environment under a single root:

$ homepki intermediate-ca -d runlocal.dev -n bu1
$ homepki intermediate-ca -d runlocal.dev -n bu2
$ homepki intermediate-ca list -d runlocal.dev

Leaves under one intermediate are independent of leaves under another. The chain file of bu1 also contains the root, so a server that uses it to verify client certificates accepts a bu2 client certificate when the client sends the bu2 intermediate with it. To keep tenants apart, have the server check the issuer or the OU of the client certificate, trust only the intermediate certificate (bu1-intermediate-ca.crt) where the server allows a non-root trust anchor, or use one root per tenant. Re-issuing one intermediate only orphans its own leaves.

Tiers can use different key types: an ECDSA leaf under an RSA intermediate is fine.

Chain files

intermediate-ca writes <name>-intermediate-ca-chain.crt: the intermediate followed by the root, in PEM. Use it as:

  • the CA bundle handed to clients that must trust a server certificate;
  • the trust store a server uses to verify client certificates;
  • the chain a server presents after its leaf, so clients that trust only the root can build the path.

There is no full-chain file that includes a leaf. Concatenate the leaf and the intermediate when a server needs one: see nginx.

File layout

Everything lives under the working directory: --workdir, then $HOMEPKI_WORKDIR, then ~/.homepki.

~/.homepki/
└── runlocal-dev/                                # domain, dots replaced by dashes
    ├── ca/                                      # the root CA
    │   ├── runlocal-dev-root-ca.crt
    │   ├── runlocal-dev-root-ca.crl             # after revoke or crl
    │   └── private/runlocal-dev-root-ca.key
    └── bu1/                                     # an intermediate CA
        ├── bu1-intermediate-ca.crt
        ├── bu1-intermediate-ca-chain.crt        # intermediate + root
        ├── bu1-intermediate-ca.crl              # after revoke or crl
        ├── bu1-crl-chain.crl                    # intermediate CRL + root CRL
        ├── private/bu1-intermediate-ca.key
        ├── server-tls/
        │   ├── kong-gateway.crt
        │   ├── kong-gateway.key
        │   └── kong-gateway.p12                 # with --pkcs12
        └── client-tls/
            ├── my-client.crt
            ├── my-client.key
            └── my-client.p12                    # with --pkcs12

Certificates and CRLs are PEM. Private keys are unencrypted PKCS#8 PEM with mode 0600; private/ directories are 0700; .p12 files are 0600. Leaf directories are created on first use, so server-cert list before any server certificate exists prints an empty list and exits 0.

Working directories created before 0.7.0 also hold OpenSSL .conf and .csr files, db/ directories and numbered .pem copies. homepki ignores them, and --force on a tier deletes that tier's leftovers.

Overwrite protection

A generate command refuses to replace existing material. It exits 1, prints the files in the way, and changes nothing. --force replaces them:

Command with --forceEffect
root-ca -d XNew root key and certificate; deletes the root CRL. Every intermediate and leaf under it stops verifying.
intermediate-ca -d X -n YNew intermediate key, certificate and chain file; deletes its CRLs. Every leaf under it stops verifying.
server-cert / client-certNew key and certificate; rewrites the .p12 with --pkcs12, deletes it otherwise.
signOverwrites the certificate at the output path.
⚠

--force on a CA tier orphans everything below it. The tier you replaced still reports ✓ OK in list; only the tier below flips to ✗ INVALID: x509: certificate signed by unknown authority. After forcing a CA, check the lowest tier.

There is no certificate database, so the same subject can be issued any number of times. In scripts, prefer a fresh working directory or a new name over --force.