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.