fipsx

package
v0.19.4 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package fipsx exposes a tiny façade over crypto/fips140 so the rest of apic can gate behavior on FIPS 140-3 mode without importing crypto/fips140 directly (keeping the import boundary tight makes FIPS-disabled callers easy to audit).

Index

Constants

View Source
const (
	// ExpvarCertReloadFailuresTotal counts handshakes that could not be
	// served from a fresh reload and fell back to the last good certificate
	// (resolve, stat, parse failures, and the remembered-failure and
	// budget-exhausted short circuits alike). Its rate is the alertable
	// signal: non-zero means the served certificate is aging.
	ExpvarCertReloadFailuresTotal = "tls_cert_reload_failures_total"
	// ExpvarCertLastLoadUnix is the Unix time (seconds) of the most recent
	// SUCCESSFUL certificate load by any reloader in the process.
	ExpvarCertLastLoadUnix = "tls_cert_last_load_unix"
)

Expvar names published by every CertReloader in the process (shared, process-wide values: a binary running several listeners sums its fallbacks and reports the most recent load).

View Source
const (
	ALPNHTTP2 = "h2"
	ALPNHTTP1 = "http/1.1"
)

ALPN protocol identifiers advertised on a server TLS config (RFC 7301 registry names). Listing both lets an HTTP/2-capable client negotiate h2 while a WebSocket client — which cannot run over net/http's bundled h2 server, as that has no RFC 8441 Extended CONNECT — falls back to http/1.1 on the same listener. PERF-0106 (#281).

View Source
const CertExpiringSoonMessage = "TLS certificate reload failing and the served certificate expires soon"

CertExpiringSoonMessage is the ERROR message GetCertificate emits (once per CertReloadErrorLogInterval) while it is serving a last-good certificate that expires within CertExpiryWarnWindow and reloads keep failing.

View Source
const CertExpiryWarnWindow = 24 * time.Hour

CertExpiryWarnWindow is how close to NotAfter the SERVED certificate has to be, while reloads are failing, for GetCertificate to escalate from the rate-limited reload-failure line to CertExpiringSoonMessage (SEC-NEW-03, #382): a last-good leaf that reloads cannot replace and that expires within a day is an operator page, not a log line.

View Source
const CertReloadErrorLogInterval = time.Minute

CertReloadErrorLogInterval bounds how often GetCertificate reports a reload failure. A single unreadable file is otherwise seen once per handshake, so an unbounded logger would turn one bad rotation into a log flood on a busy listener. The first failure after a quiet period always logs. It is also the window over which CertReloadMaxAttemptsPerInterval is counted.

View Source
const CertReloadMaxAttemptsPerInterval = 8

CertReloadMaxAttemptsPerInterval bounds how many times GetCertificate will re-read and re-parse a FAILING certificate/key pair per CertReloadErrorLogInterval, regardless of how often the files' stamps change (SEC-NEW-11, #382). The failed-stamp memory alone only stops the retry for a pair that stays byte-for-byte still; a file being actively rewritten presents a new stamp on every handshake, and without this floor every handshake would take the reload branch and serialise behind disk I/O under mu for as long as the rewrite loop runs. Once the budget is spent the last good certificate is served without touching disk until the interval elapses or the pair loads successfully; Reload() ignores the budget. The cost is that a repair landing after the budget is exhausted is picked up within one interval rather than on the very next handshake.

Variables

View Source
var ErrNotInFIPS = errors.New("fipsx: FIPS 140-3 mode is not enabled (build with GOFIPS140=v1.0.0)")

ErrNotInFIPS is returned by RequireFIPS when the running binary was not selected with GOFIPS140 or when the active module reports disabled.

View Source
var ErrNotRegularFile = errors.New("fipsx: TLS certificate/key path is not a regular file")

ErrNotRegularFile is returned (wrapped) when a resolved certificate or key path names something other than a regular file — a directory, a FIFO, a device. api/ and the generated server already refuse those in their resolve function (obsx.ResolveTLSCertKeyPaths); the reloader checks again itself so a caller whose resolve does NOT canonicalize still cannot hand tls.LoadX509KeyPair a FIFO and block the boot, or a handshake, in open(2).

Functions

func ApprovedCipherSuites

func ApprovedCipherSuites() []uint16

ApprovedCipherSuites returns the TLS 1.3 cipher suite IDs that the Go 1.27 FIPS 140-3 Cryptographic Module (GOFIPS140=v1.0.0) will actually negotiate. SP 800-140C / SP 800-140D approved AEADs only — ChaCha20-Poly1305 is excluded by design.

func ApprovedCurves

func ApprovedCurves() []tls.CurveID

ApprovedCurves returns NIST-approved curves accepted by the FIPS module. X25519 is intentionally absent (not FIPS-approved in the module).

func ApprovedSignatureSchemes

func ApprovedSignatureSchemes() []tls.SignatureScheme

ApprovedSignatureSchemes returns the signature schemes FIPS 140-3 will negotiate for TLS 1.3 handshake signatures and for client/server certificate verification. RSA-PSS preferred; Ed25519 excluded until the next module catalog update.

func CompiledWithFIPSTag

func CompiledWithFIPSTag() bool

CompiledWithFIPSTag reports whether this binary was compiled with the `fips` build tag.

func DefaultALPNProtocols added in v0.18.3

func DefaultALPNProtocols() []string

DefaultALPNProtocols is the ALPN list NewServerTLSConfig installs when the caller supplies none: HTTP/2 preferred, HTTP/1.1 available. Returns a fresh slice so a caller cannot mutate the package default.

func IsFIPS

func IsFIPS() bool

IsFIPS reports whether the Go Cryptographic Module is in FIPS 140-3 mode.

func NewClientTLSConfig

func NewClientTLSConfig() *tls.Config

NewClientTLSConfig is the client-side companion. Used by the generated SDK clients and by the apic pipeline when it pulls OIDC discovery documents over TLS.

func NewServerTLSConfig

func NewServerTLSConfig(certs []tls.Certificate, opts ...Option) *tls.Config

NewServerTLSConfig builds a tls.Config locked to TLS 1.3, the FIPS 140-3 approved cipher suites and curves, and the caller-supplied server certificate(s), then applies any opts. The returned config is safe to mutate further; callers that need ClientAuth / ClientCAs for mTLS should pass WithMTLS rather than mutating directly so the fail-closed cipher catalog stays in force.

The variadic Option list is backward-compatible: existing single-arg callers (api/server.go, pkg/httpx/server.go) compile unchanged.

PERF-0106 (#281): when no Option supplies an ALPN list, the config advertises DefaultALPNProtocols() ("h2", "http/1.1"). Without NextProtos the peer negotiates no protocol at all and net/http never configures its bundled HTTP/2 server, silently pinning every connection to HTTP/1.1.

func RequireFIPS

func RequireFIPS() error

RequireFIPS returns ErrNotInFIPS when called from a binary that did not activate the FIPS 140-3 module. Callers MUST treat this as a fail-closed startup gate.

func Version

func Version() string

Version returns the Cryptographic Module version string, or "" if FIPS is disabled.

Types

type CertReloader added in v0.18.3

type CertReloader struct {
	// contains filtered or unexported fields
}

CertReloader supplies a server's leaf certificate at handshake time from files on disk, so a certificate rotated underneath a running process is picked up without a restart (GAP-0116, #258). Install it as tls.Config.GetCertificate and leave tls.Config.Certificates EMPTY — a static slice alongside it would let crypto/tls fall back to a stale boot-loaded leaf.

Cost per handshake: with WithConfiguredPaths (the api/ and generated-server wiring), one os.Stat on each of the configured cert and key paths — the resolve function runs only when a stamp changed, immediately before the read it guards. Without it, one call to the caller's resolve function (for obsx.ResolveTLSCertKeyPaths that is EvalSymlinks + Stat on each leg, ~15 µs on a symlinked layout) plus the two stats. Certificate bytes are re-read ONLY when a stamp changed, so the steady state is a couple of syscalls with no parsing and no allocation of a new tls.Certificate. Deliberately absent is any TTL/once cache over the resolved path or the stamps — that is precisely the optimization (24b1b1c) that silently froze htpx rotation and had to be reverted; the trade was ruled on for pkg/htpx's loadCert (PERF_GAP_ANALYSIS.md, "2026-07-14 round-4 correction"): rotation correctness beats a micro-optimization, which is why the prescreen here changes WHAT is stat'd, never WHEN.

Failure posture is fail-SAFE, not fail-open, and it is UNBOUNDED IN TIME: the eager load in NewCertReloader means a misconfigured listener still refuses to start, but once a good certificate has been served, a later resolve/stat/parse failure keeps serving that last good certificate — for as long as the process runs and the failure persists, past the leaf's NotAfter if it comes to that — and logs at ERROR (rate-limited) rather than taking the listener down. A transient dangling-symlink window mid-rotation therefore self-heals on the next handshake; a permanently broken rotation (an operator rotating BECAUSE the old key leaked, whose new file lands unparseable) keeps presenting the old leaf until someone notices. Detect it: Stats().ConsecutiveFailures (the healthx "tls_cert_reload" check reads it), the tls_cert_reload_failures_total expvar rate, the ERROR line, and — when the served leaf is within CertExpiryWarnWindow of expiry — CertExpiringSoonMessage.

A CertReloader is safe for concurrent use by any number of handshakes.

func NewCertReloader added in v0.18.3

func NewCertReloader(resolve func() (certPath, keyPath string, err error), opts ...CertReloaderOption) (*CertReloader, error)

NewCertReloader builds a reloader over the cert/key paths returned by resolve, and loads them EAGERLY: a missing, unreadable or unparseable pair is a construction error, so boot still fails closed exactly as the previous one-shot tls.LoadX509KeyPair did.

resolve is called before every read of the pair (and, without WithConfiguredPaths, on every handshake) and must be safe for concurrent use. Callers pass the same canonicalization their configuration contract requires — api/ and the generated server hand it obsx.ResolveTLSCertKeyPaths so an attacker-influenced shared-volume symlink still cannot point the load at a non-regular file. fipsx deliberately does not import obsx: keeping this package dependency-light is what lets it sit under every runtime surface.

func (*CertReloader) GetCertificate added in v0.18.3

func (r *CertReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)

GetCertificate satisfies tls.Config.GetCertificate. It stats both files — the configured paths when WithConfiguredPaths was given, otherwise the re-resolved paths — and reloads only when the path, size, modification time or filesystem identity of either has changed since the last successful load — and, after a failed load, only when they have changed again since that failure and the per-interval retry budget is not spent. Resolution and stat errors are not remembered: they are retried on every handshake, which is what lets a transient dangling-symlink window mid-rotation self-heal on the next connection. Only their wrapping and formatting is reused while the same failure persists (preflightCause, PERF-0142).

func (*CertReloader) Reload added in v0.18.3

func (r *CertReloader) Reload() error

Reload re-resolves the paths and reloads the certificate unconditionally, bypassing the stamp comparison, the failed-stamp memory and the retry budget. It is what NewCertReloader uses for its eager first load, and it gives tests (and any caller wiring a SIGHUP handler) a way to force a swap without waiting for a handshake. On error the previously loaded certificate is left in place.

func (*CertReloader) Stats added in v0.18.3

func (r *CertReloader) Stats() CertReloaderStats

Stats returns the reloader's current reload state (see CertReloaderStats). Safe to call concurrently with handshakes; it takes no lock.

type CertReloaderOption added in v0.18.3

type CertReloaderOption func(*CertReloader)

CertReloaderOption configures NewCertReloader.

func WithConfiguredPaths added in v0.18.3

func WithConfiguredPaths(certPath, keyPath string) CertReloaderOption

WithConfiguredPaths tells the reloader the CONFIGURED certificate and key paths — the values from tls.cert_path / tls.key_path before any symlink resolution — so the per-handshake change check can stat those directly instead of canonicalising them first (PERF-NEW-02 / PERF-0124, #371).

os.Stat follows symlinks, so a stat of the configured path carries the TARGET's size, mtime and filesystem identity; certStamp.equal compares that identity with os.SameFile, so a certbot or Kubernetes symlink retarget is detected exactly as it is when the resolved path is stamped — the resolved path only ever changed because the target did, and the target's inode is what the stamp already keys on. The canonicalising resolve function (the security-load-bearing step: it is what refuses a symlink pointed at a non-regular file) still runs before every actual read of the pair; it just no longer runs on the steady-state handshake, which on the certbot layout measured ~19 µs / 90 allocs per handshake with it and ~1.5 µs / 4 allocs without (about the cost of a P-256 signature, saved per connection).

Callers that cannot name the configured paths (an opaque resolve function) omit this option and keep the resolve-per-handshake behaviour.

CONTRACT (QG-135, #391): certPath and keyPath MUST be the very inputs the reloader's resolve function canonicalises. Nothing validates that — the constructor only checks that both are set or both are empty — and a mismatch is silent and dangerous: the reloader stats ONE pair to decide whether anything changed and loads ANOTHER, so a rotation of the pair actually being served is never noticed (its stamps are not the ones being watched) while Stats, the expvar counters and the tls_cert_reload health check all stay green. Derive both from a single configured value, as api/, pkg/htpx and the generated server do:

fipsx.NewCertReloader(
    func() (string, string, error) { return obsx.ResolveTLSCertKeyPaths(certPath, keyPath) },
    fipsx.WithConfiguredPaths(certPath, keyPath),
)

type CertReloaderStats added in v0.18.3

type CertReloaderStats struct {
	// ConsecutiveFailures is the number of handshakes in a row that were
	// served the last good certificate because a fresh reload was not
	// possible (resolve/stat/parse failure, a remembered failing pair, or
	// an exhausted retry budget). Reset to zero by the next successful load.
	ConsecutiveFailures int
	// LastLoadUnix is the Unix time (seconds) of the last successful load
	// (the eager load at construction counts).
	LastLoadUnix int64
	// LastError is the most recent reload failure, or "" while healthy.
	LastError string
	// NotAfter is the expiry of the certificate currently being served.
	NotAfter time.Time
}

CertReloaderStats is the machine-readable reload state read by CertReloader.Stats (SEC-NEW-03, #382). A health check (healthx "tls_cert_reload") degrades readiness once ConsecutiveFailures crosses its threshold; a monitor pages when NotAfter approaches while LastError is set.

type MTLSOpts

type MTLSOpts struct {
	ClientAuth tls.ClientAuthType
	ClientCAs  *x509.CertPool
}

MTLSOpts groups the per-listener mutual-TLS settings consumed by WithMTLS. Used by api.WithMTLS (Plan 02 Task 12) to bind the listener to a trust pool while inheriting the FIPS cipher catalog.

type Option

type Option func(*tls.Config)

Option mutates a tls.Config before NewServerTLSConfig returns it. Options are applied AFTER the FIPS-policy baseline, so they may layer on top (e.g. set ClientAuth, ClientCAs) but cannot weaken the cipher suite or version pins without explicitly overwriting them.

func WithALPNProtocols added in v0.18.3

func WithALPNProtocols(protos ...string) Option

WithALPNProtocols sets tls.Config.NextProtos explicitly, suppressing the DefaultALPNProtocols fallback NewServerTLSConfig would otherwise apply. Pass ALPNHTTP1 alone to keep HTTP/2 off a listener's ALPN list.

NOTE: on an http.Server served via ServeTLS, the ALPN list is not the whole story — net/http re-appends "h2" unless Server.Protocols forbids HTTP/2 (see adjustNextProtos in net/http/server.go), so a caller that must genuinely refuse h2 sets both.

func WithMTLS

func WithMTLS(o MTLSOpts) Option

WithMTLS attaches ClientAuth and ClientCAs to the returned config.

Jump to

Keyboard shortcuts

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