Skip to content

Limitations: what localca does not do

localca is deliberately small. Some of what follows is a design boundary that will not move, some is a gap that a future release may close, and some is a consequence of the libraries underneath. Each section says which.

It is not a certificate authority anyone else can trust

By design, permanently. The root is trusted only where it has been installed — your machine, and any device you deliberately import it into. Certificates it issues are worthless to anyone who has not done that. localca cannot issue a certificate for a public hostname that a visitor's browser will accept, and must never be used to try.

For publicly-trusted certificates you need a public CA and a domain you control. That is a different problem with different infrastructure, described as Layer 2 in the trust model.

Windows trust-store installation does not work

A gap, not a decision. The certificate code runs on Windows; the trust install does not.

The elevation pre-flight requires either os.Geteuid() == 0 — which on Windows always returns -1, even in an elevated Administrator console — or sudo on PATH plus an interactive terminal, then a sudo -v pre-warm that Windows sudo does not accept. So a system-store install cannot succeed on Windows regardless of how the process was launched. Separately, NSS is implemented only for macOS and Linux, so StoreNSS is a silent no-op there.

A Windows user can still serve TLS from localca certificates by importing rootCA.pem into the Windows root store by hand. See platform support.

There is no GUI password dialog

A gap, scoped and understood. Elevation is terminal sudo only. An application with a window and no terminal — a .dmg, a desktop app launched from a menu — has nowhere for sudo to prompt, so it receives ErrElevationUnavailable and cannot install a system-trusted root on its own.

A native dialog needs OS-specific elevation (osascript ... with administrator privileges on macOS, pkexec on Linux, a UAC re-exec on Windows), and the trust library underneath exposes no hook for supplying one. Adding that means owning the privileged install commands directly. Until then, a GUI application has to either ship a terminal step or fall back to an untrusted certificate.

Only one leaf certificate is cached per data directory

A design consequence. The cache is a single pair of files, leaf.pem and leaf-key.pem. EnsureServed reuses them when they cover every requested host; otherwise it mints a new leaf and overwrites them.

Two servers in one process that call EnsureServed with different host lists therefore fight over one file. Each call overwrites the other's certificate, and because both returned Pairs point at the same two paths, the first server ends up serving the second server's certificate — for hostnames it does not answer on.

Two ways out: ask for the union of the hostnames in a single call so one leaf covers both, or give each server its own DataDir. Giving each its own DataDir also gives each its own root and its own trust-store entry, which is usually not what you want; prefer the union.

Authority.Leaf is unaffected — it never touches the cache and returns an independent in-memory certificate every call.

Purging the root does not purge the cached leaf

A gap. Uninstall with Purge() deletes the root certificate and key and leaves the cached leaf behind, still signed by the deleted root. Provisioning again in the same data directory mints a fresh root but reuses that stale leaf, and everything rejects the chain.

Delete leaf.pem and leaf-key.pem yourself when you purge. Detail and commands are in what Purge leaves behind.

Removing the root by other means is not detected

A design consequence, shared with mkcert. System-store membership is read from a marker file recording localca's own installs, because no portable "is this trusted?" query exists for an OS trust store. Delete the root from Keychain Access or from /usr/local/share/ca-certificates and localca still reports it as installed, and EnsureServed will not reinstall it.

The recovery is the same as mkcert's: install again. Deleting trust-install.json first makes EnsureServed do it for you.

Uninstall cannot be scoped to one store

A gap. Install takes a store list; Uninstall does not. It always targets both stores, which means it always runs the elevation pre-flight — so removing an NSS-only install still asks for a password, and fails with ErrElevationUnavailable where none is available.

To remove an NSS entry without elevation, use certutil -D directly against the profile, matching the entry name <AppName>-localca-<serial>.

There is no way to remove a root whose key you have deleted

A design consequence. Uninstall needs the stored root certificate to know what to remove. If the data directory is deleted before Uninstall runs, the root stays in the system trust store with nothing in localca able to remove it. Remove it by hand with your platform's trust-store tool.

Always Uninstall before deleting a data directory.

RSA is not offered

A deliberate default that could become an option. Roots and leaves are ECDSA P-256. There is no configuration knob for RSA. If you need to serve a client too old for ECDSA, localca cannot help; the reasoning is in certificate choices.

Only DNS and IP SANs are issued

By design for the use case. A leaf carries DNS names and IP addresses only. Email and URI SANs, which mkcert also supports, are for client-certificate and service-identity scenarios localca does not serve. Anything in the hosts slice that parses as an IP becomes an IP SAN; everything else becomes a DNS SAN, with no validation that it is a plausible hostname.

Client certificates are not issued

By design. Every leaf is ExtKeyUsage: ServerAuth only. localca issues server certificates for local development. Mutual TLS needs a client certificate with ClientAuth, which it will not mint.

The go/tls.Pair it returns does have ClientCAs and ClientAuth fields for verifying client certificates — but you would have to obtain those certificates elsewhere.

The Java trust store is not touched

By design, reversible. mkcert can install into the Java keystore; localca does not, because its consumers are browsers and Go clients. The library underneath supports it, so this is a small change if a real need appears.

There is no revocation

By design. No CRL, no OCSP, nothing to revoke against. The mitigation for a compromised root is to Uninstall it, which removes it from exactly one machine — the same machine where the key lived. That is what "the blast radius is one machine" buys.

There is no command-line tool

By design, library-first. localca ships as a Go library with no binary. A tool that wants install / uninstall / status as visible commands wraps Authority in its own command. There is nothing to go install and nothing to run.

Certificates cannot be issued for hostnames that are not yours to serve

Not enforced, but true. Nothing stops you passing example.com to EnsureServed, and a leaf will be issued for it, and your own machine will trust it. That is a man-in-the-middle certificate for a site you do not control, valid on your machine only. Doing it deliberately to intercept traffic is the failure mode a locally-trusted CA makes possible, and the reason the root key must not leave the machine.