localca

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 25 Imported by: 0

README

localca

A framework-free, mkcert-style local development CA for Go — generate a per-machine root, install it into the OS + browser trust stores, and mint browser-trusted HTTPS certs with zero manual setup

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules for the phpboyscout tool family. Docs: localca.go.phpboyscout.uk


gitlab.com/phpboyscout/go/localca lets a Go tool serve browser-trusted HTTPS on localhost and LAN IPs with no certificate fuss. On first run it generates a per-machine ECDSA root CA, installs it into the host's system and NSS (Firefox/ Chromium) trust stores, and mints short-lived leaf certificates — surfaced as a go/tls.Pair so any transport serves the trusted cert unchanged.

It is Layer 1 of a two-layer TLS strategy for the phpboyscout tool family: a per-machine local CA that works offline forever. The public ACME path (Layer 2) is a separate concern; this module stays the offline / CI / air-gapped fallback.

Design

  • Framework-free. Works from a typed Config, an injected afero.Fs for storage, and an optional *slog.Logger. No config framework, no TUI, no OpenTelemetry, no go-tool-base — a depfootprint_test.go guard enforces it.
  • One-liner first run. EnsureServed ensures the root exists and is trusted, then returns a servable go/tls.Pair. Idempotent: an already-trusted root re-prompts nothing and a still-valid leaf is reused.
  • Cross-OS trust, as a library. The trust-store install uses smallstep/truststore (mkcert's logic), behind an injectable TrustStore seam so the crypto core is fully testable without a real store.

Security

The root's private key never leaves the machine and is stored 0600. A shared or shipped CA key would be a universal MITM key against every user — so one is never generated or bundled; every install mints its own root. Installing a system-trusted root requires an OS elevation prompt (the consent gate), and it is reversible via Authority.Uninstall.

Install

go get gitlab.com/phpboyscout/go/localca

Quick start

package main

import (
	"context"
	"net/http"

	"gitlab.com/phpboyscout/go/localca"
)

func main() {
	cfg := localca.Config{DataDir: "/home/me/.myapp", AppName: "myapp"}

	// First run installs the local root (one OS prompt); later runs are silent.
	pair, err := localca.EnsureServed(context.Background(), cfg, []string{"localhost", "127.0.0.1"})
	if err != nil {
		panic(err)
	}

	tlsCfg, err := pair.ServerConfig() // go/tls hardened config with the leaf loaded
	if err != nil {
		panic(err)
	}

	srv := &http.Server{Addr: ":8443", TLSConfig: tlsCfg}
	_ = srv.ListenAndServeTLS("", "")
}

Key concepts

  • Authority — a per-machine CA: Install / Uninstall the root into trust stores, Installed to report where it's trusted, Leaf to mint a server cert.
  • EnsureServed — the first-run one-liner: ensure + install + mint + persist, returning a go/tls.Pair.
  • Config — typed, module-owned: DataDir, AppName, and overridable RootTTL (~10y) / LeafTTL (90d).
  • TrustStore — the cross-OS install seam (default: smallstep/truststore); inject a fake in tests.

Documentation

Guides and the trust/security model: localca.go.phpboyscout.uk. API reference: pkg.go.dev.

License

See LICENSE.

Documentation

Overview

Package localca is a framework-free, mkcert-style local development certificate authority for Go tools. On first run it generates a per-machine ECDSA root CA, installs it into the host's system and NSS (Firefox/Chromium) trust stores, and mints short-lived leaf certificates for local hosts (localhost, 127.0.0.1, ::1, and LAN IPs) so a tool can serve browser-trusted HTTPS with zero manual setup.

The leaf is surfaced as a gitlab.com/phpboyscout/go/tls.Pair, so any transport that already consumes the shared TLS plumbing serves the trusted certificate unchanged. EnsureServed is the one-liner: ensure the root exists and is trusted, then return a ready-to-serve Pair.

The package is framework-free: it works from a typed Config, an injected github.com/spf13/afero.Fs for storage, and an optional *log/slog.Logger. The cross-OS trust-store install sits behind the TrustStore seam so the pure-crypto core is fully testable without touching a real trust store.

This is Layer 1 of a two-layer TLS strategy for the phpboyscout tool family: a per-machine local CA that works offline forever. The public ACME path (Layer 2) is a separate, downstream concern; this module remains the offline/CI/air-gapped fallback even once Layer 2 exists.

Security

The root's private key never leaves the machine and is stored 0600. A shared or shipped CA key is a universal MITM key against every user and is never generated or bundled — every install mints its own root. Installing a system-trusted root requires OS elevation; that prompt is the consent gate, and the install is reversible via Authority.Uninstall.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrElevationUnavailable = errors.New(
	"localca: administrator privileges are required to install into the system trust " +
		"store, but no elevation is available (not root, and no interactive terminal to prompt)",
)

ErrElevationUnavailable is returned when installing into the system trust store needs administrator privileges but none can be obtained interactively: the process is not root and there is no terminal (or no sudo) to prompt with. A consumer catches this to fall back — e.g. keryx drops to an untrusted self-signed cert (spec 0027 §5.4) — rather than hanging on a prompt that can never be answered.

Functions

func EnsureServed

func EnsureServed(ctx context.Context, cfg Config, hosts []string, opts ...Option) (gotls.Pair, error)

EnsureServed is the first-run one-liner: ensure the root exists and is trusted in every supported store, mint (or reuse a cached) leaf for hosts, persist it, and return a go/tls.Pair ready for any transport's ServerConfig. Idempotent: a second call with a still-installed root and a still-valid cached leaf re-prompts nothing and re-mints nothing.

Example

ExampleEnsureServed shows the first-run one-liner: provision a per-machine local CA (installing it into the trust store on first run) and serve browser-trusted HTTPS. It compiles as documentation; it is not executed (it would mutate the host trust store).

package main

import (
	"context"
	"log"
	"net/http"

	"gitlab.com/phpboyscout/go/localca"
)

func main() {
	cfg := localca.Config{DataDir: "/home/me/.myapp", AppName: "myapp"}

	pair, err := localca.EnsureServed(context.Background(), cfg, []string{"localhost", "127.0.0.1"})
	if err != nil {
		log.Fatal(err)
	}

	tlsCfg, err := pair.ServerConfig()
	if err != nil {
		log.Fatal(err)
	}

	srv := &http.Server{Addr: ":8443", TLSConfig: tlsCfg}
	log.Fatal(srv.ListenAndServeTLS("", ""))
}

Types

type Authority

type Authority interface {
	// Installed reports where the root is currently trusted.
	Installed(ctx context.Context) (TrustState, error)

	// Install mints the root if absent and installs it into the given trust stores
	// (all supported when none are given). Installing into the system store triggers
	// the OS elevation prompt.
	Install(ctx context.Context, stores ...Store) (TrustState, error)

	// Uninstall removes the root from trust stores; with Purge it also deletes the
	// stored key and cert.
	Uninstall(ctx context.Context, opts ...UninstallOption) error

	// Leaf returns a short-lived server certificate valid for hosts, signed by the
	// root. It does not install the root; it errors if no root exists yet (use
	// EnsureServed for the mint-and-install path).
	Leaf(ctx context.Context, hosts ...string) (*cryptotls.Certificate, error)
}

Authority is a per-machine local CA that can install itself into the host's trust stores and issue leaf certificates for local hosts.

func New

func New(cfg Config, opts ...Option) (Authority, error)

New constructs an Authority from typed config and injected dependencies. DataDir is required.

type Config

type Config struct {
	// DataDir is where the root key/cert and cached leaves live. Required. The key
	// files are written 0600. Must be a stable per-machine location.
	DataDir string

	// AppName seeds the root CommonName ("<AppName> local CA (<user>@<host>)") so an
	// installed root is self-evidently a per-machine dev identity. Defaults to "local".
	AppName string

	// Organization seeds the root subject Organization. Defaults to AppName.
	Organization string

	// RootTTL is the root CA validity. Zero uses defaultRootTTL (~10 years).
	RootTTL time.Duration

	// LeafTTL is the issued leaf validity. Zero uses defaultLeafTTL (90 days).
	LeafTTL time.Duration
}

Config is the typed configuration for an Authority. It is owned by this module and carries no framework coupling: a GTB consumer decodes its own config section into this struct in its adapter; a standalone consumer fills it directly.

type Option

type Option func(*options)

Option injects the runtime environment. All options are optional and nil-safe.

func WithClock

func WithClock(now func() time.Time) Option

WithClock overrides the time source for deterministic tests.

func WithFS

func WithFS(fs afero.Fs) Option

WithFS overrides the filesystem used for key/cert storage (default: OS fs).

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger attaches a structured logger. Nil is ignored (logging stays silent).

func WithTrustStore

func WithTrustStore(ts TrustStore) Option

WithTrustStore swaps the cross-OS trust-store backend — the seam that keeps the crypto core testable without touching a real store.

type Store

type Store int

Store identifies a trust store the root can be installed into.

const (
	// StoreSystem is the OS system trust store (macOS Keychain, Linux
	// ca-certificates, Windows CryptoAPI). Installing here needs OS elevation.
	StoreSystem Store = iota
	// StoreNSS is the shared NSS database used by Firefox and Chromium. Install is
	// user-level but needs the platform certutil.
	StoreNSS
)

type TrustState

type TrustState struct {
	System bool
	NSS    bool
}

TrustState reports where the root is currently trusted.

type TrustStore

type TrustStore interface {
	// Install adds the root to the given stores (all supported when none are given)
	// and returns the resulting state.
	Install(root *x509.Certificate, stores ...Store) (TrustState, error)
	// Uninstall removes the root from the given stores (all when none are given).
	Uninstall(root *x509.Certificate, stores ...Store) error
	// State reports where the root is currently trusted.
	State(root *x509.Certificate) (TrustState, error)
}

TrustStore is the cross-OS trust-store backend seam. The default implementation wraps github.com/smallstep/truststore (mkcert's logic); tests inject a fake so the crypto core needs no real store. Implementations act on the root's public certificate only — a trust store never holds the private key.

type UninstallOption

type UninstallOption func(*uninstallOpts)

UninstallOption tunes Authority.Uninstall.

func Purge

func Purge() UninstallOption

Purge deletes the stored root key/cert after removing it from trust stores.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL