Documentation menu

Server and client certificates

Issue TLS leaves under an intermediate CA: what goes in the subject and the SANs, which key and validity to use, and how to re-issue.

Server and client leaves

server-cert and client-cert issue a key and a certificate under an intermediate CA. They differ in the extended key usage they set and the directory they write to.

$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway
$ homepki client-cert -d runlocal.dev -i bu1 -c my-client
CommandExtended key usageKey usageWritten to
server-certserverAuthdigitalSignature, plus keyEncipherment for an RSA key<intermediate>/server-tls/<name>.crt, .key
client-certclientAuthdigitalSignature<intermediate>/client-tls/<name>.crt, .key

The intermediate must exist. Every new certificate is verified against its chain before anything is written, so a leaf that would not verify, for example one outside a CA's name constraints, is refused and leaves nothing on disk.

Derived subject

The subject is not configurable. The organization is the root CA's literal name (the domain with dots replaced by dashes), the organizational unit is the intermediate name, and the common name is <leaf>.<intermediate>.<domain>:

O=runlocal-dev, OU=bu1, CN=kong-gateway.bu1.runlocal.dev

The common name is always the first DNS Subject Alternative Name. Most TLS clients ignore the common name and match SANs only, so if a service must answer on a name that does not fit this shape, add it with --san rather than changing the leaf name.

Subject Alternative Names

Both commands take a repeatable --san. The values are appended after the common name.

$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway \
    --san kong.local \
    --san 192.168.1.10 \
    --san 'DNS:*.kong.local' \
    --san IP:::1
$ openssl x509 -noout -ext subjectAltName -in kong-gateway.crt
X509v3 Subject Alternative Name:
    DNS:kong-gateway.bu1.runlocal.dev, DNS:kong.local, DNS:*.kong.local,
    IP Address:192.168.1.10, IP Address:0:0:0:0:0:0:0:1
ValueBecomes
a bare value that parses as an IP addressan IP SAN
any other bare valuea DNS SAN
DNS:, IP:, email:, URI: prefix (any case)that type, whatever the value looks like

An invalid address after IP: is rejected instead of being treated as a hostname. Duplicates are dropped.

⚠

Single-quote any SAN that contains *. An unquoted --san DNS:*.kong.local is expanded by the shell before homepki receives it, and zsh fails the whole command with no matches found.

SANs on client certificates

client-cert takes --san the same way. Use a URI: SAN to give a workload a SPIFFE ID:

$ homepki client-cert -d runlocal.dev -i bu1 -c my-client \
    --san URI:spiffe://runlocal.dev/ns/default/sa/my-client

Key types

Every generate command takes --key-type. Tiers are independent: an ECDSA leaf under an RSA intermediate is fine. An unknown value is rejected before anything is written.

--key-typeKeySignature digest when this key signs
rsa (default)RSA 2048-bitSHA-256
ecdsa, ecdsa-p256ECDSA P-256SHA-256
ecdsa-p384ECDSA P-384SHA-384
ecdsa-p521ECDSA P-521SHA-512
ed25519Ed25519none (Ed25519 signs the message directly)

The digest follows the issuing CA's key, not the key being certified. A P-384 leaf under an RSA intermediate is signed with SHA-256.

$ homepki intermediate-ca -d runlocal.dev -n bu2 --key-type ecdsa
$ homepki server-cert     -d runlocal.dev -i bu2 -s gw --key-type ecdsa-p384
⚠

Browsers do not accept Ed25519 certificates. Use ed25519 for service-to-service TLS between Go or OpenSSL 1.1.1+ peers, and RSA or ECDSA for anything a browser opens.

Validity

--validity sets how long a certificate stays valid, as a number of days (90d) or a Go duration (24h, 30m). There is no year suffix: write 3650d.

CommandDefault
root-ca, intermediate-ca2190 days (about 6 years)
server-cert, client-cert, sign365 days
$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway --validity 24h   # to test rotation

A certificate never outlives its issuer. A longer request is capped at the issuer's expiry and the command prints a note:

$ homepki server-cert -d runlocal.dev -i bu1 -s long --validity 5000d
Generating server certificate long under runlocal.dev/bu1
Wrote ~/.homepki/runlocal-dev/bu1/server-tls/long.key
Wrote ~/.homepki/runlocal-dev/bu1/server-tls/long.crt
Note: validity capped at the issuer's expiry, 2032-09-24.
Server Certificate generated successfully.

NotBefore is always set 5 minutes in the past, so a new certificate is already valid in a container or VM whose clock runs slightly behind. It also means an already-expired certificate cannot be issued.

PKCS#12 bundles

--pkcs12 also writes <name>.p12 next to the .crt and .key. It holds the private key, the leaf and the CA chain (intermediate, then root).

$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway --pkcs12
Generating server certificate kong-gateway under runlocal.dev/bu1
Wrote ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.key
Wrote ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.crt
Wrote ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.p12 (key, certificate and CA chain; password "changeit")
Server Certificate generated successfully.

$ keytool -list -keystore kong-gateway.p12 -storepass changeit
  • The password is changeit, the default of mkcert and of the JDK keystores. --pkcs12-password sets another.
  • The file uses the 3DES/SHA-1 PKCS#12 encoding, which Java, macOS Keychain, Windows and OpenSSL 3 import without extra options such as -legacy.
  • The file has mode 0600, like the .key.

Re-issuing a leaf

A generate command refuses to overwrite existing material. It exits 1, lists what is in the way, and changes nothing:

$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway
Error: a server certificate named "kong-gateway" already exists under runlocal.dev/bu1:
  ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.crt
  ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.key
  ~/.homepki/runlocal-dev/bu1/server-tls/kong-gateway.p12

Re-issuing overwrites its private key, so anything already serving that pair breaks. Pass --force to replace it, or issue under another name

--force writes a new key and certificate over the old pair. There is no CA database, so nothing else needs cleaning up. The key is regenerated, so reload anything that serves or presents the old pair.

$ homepki server-cert -d runlocal.dev -i bu1 -s kong-gateway \
    --san kong.local --san 10.0.0.5 --force

Re-issuing with --force but without --pkcs12 deletes the old .p12, since it holds the replaced key. A re-issued certificate gets a new serial number, so it is not affected by an earlier revocation of the same name.

ℹ

In scripts, prefer a new leaf name or a fresh HOMEPKI_WORKDIR over --force. --force on a CA tier orphans every certificate below it; see Checking after --force.