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. 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. Every root and server leaf localca generates is ECDSA P-256. There is no configuration knob for RSA.

The one place RSA appears is a key a machine brings itself: a Private CA signs a CSR for an RSA key of 2048 bits or more, as well as P-256, P-384 and Ed25519. That key is the machine's, not one localca generates. 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 server leaf carries DNS names and IP addresses only. Email and URI SANs, which mkcert also supports, are for service identity, which localca does not serve. A client certificate from a Private CA carries no SANs at all; its identity is its CommonName. 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.

Only a private CA issues client certificates

By design. An Authority, whose root is installed in the machine's trust stores, issues server certificates only, and has no way to sign a CSR. A root the machine trusts for every HTTPS site must not sign requests that other machines send.

Client certificates for mutual TLS come from a Private CA (NewPrivate), whose root is never installed. It signs a CSR only when the CSR proves possession of its key and names exactly the client the caller expects, and takes nothing else from it. Every certificate is ClientAuth only, so even if someone installed a private root by hand, nothing signed from a CSR could act as a server. That was measured against Go, OpenSSL, curl and Chrome; see the trust model and Run mutual TLS with a private CA.

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.

A Private CA changes the blast radius. Its key signs the client certificates of every machine enrolled with it, so a stolen key can mint a certificate for any of them. localca keeps no record of what it signed. Revocation is the caller's: record each certificate's fingerprint (ClientCert.Fingerprint) when you issue it, accept only a recorded one, and replace the record when a machine renews. A stolen key then mints certificates nothing accepts. Renewing the root means every machine enrols again.

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. Whether that should change, and where commands would live, is an open question in go/localca#9.

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.