Documentation
¶
Overview ¶
Package ca implements Culvert's SSL-inspection Root CA — the MITM trust core (ADR-0002 extraction; engine was ca.go in package main). It owns the Root CA lifecycle (in-memory init, encrypted-bundle load/save, custom-CA import), on-the-fly leaf-cert signing with an LRU+TTL cache, dual-CA rotation with an overlap window, and the pluggable KeyProvider (HSM/KMS) seam.
The encrypted-bundle wire format is FROZEN: `caMagic` ("PSCA"), `caVersion`, and the PBKDF2-600k + AES-256-GCM layout are read from bundles already on operators' disks — changing any of them breaks load. See encryptBundle.
Observability crosses the boundary via publish-once package-level hooks (SignLatencyObserver, RotationObserver), wired once by package main to its Prometheus histogram / alert store / rotation counter — the obs/fileutil injection pattern (ADR-0003), so the engine never imports main. The auto-rotation LOOP (StartCAAutoRotation) and the cluster CA stay in main; this package exposes the per-tick primitives (RotateIfNeeded, CleanupSecondaryCA) the loop drives.
Index ¶
- Constants
- Variables
- func DecryptBundle(data, passphrase []byte) ([]byte, error)
- func EncryptBundle(plaintext, passphrase []byte) ([]byte, error)
- func HasBundleMagic(data []byte) bool
- func Magic() []byte
- type KeyProvider
- type Manager
- func (cm *Manager) AgeCacheEntryForTest(host string, createdAt time.Time) bool
- func (cm *Manager) CACertForTest() *x509.Certificate
- func (cm *Manager) CACertInfo() map[string]any
- func (cm *Manager) CACertPEM() []byte
- func (cm *Manager) CAExpiry() time.Time
- func (cm *Manager) CacheStats() (hits, misses int64, size int)
- func (cm *Manager) CertCacheLen() int
- func (cm *Manager) CleanupSecondaryCA()
- func (cm *Manager) ClearCache()
- func (cm *Manager) GetCert(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
- func (cm *Manager) HasKeyProviderForTest() bool
- func (cm *Manager) ImportBundle(data []byte) error
- func (cm *Manager) InitCA() error
- func (cm *Manager) KeyProviderName() string
- func (cm *Manager) LoadCA(path, passphrase string) error
- func (cm *Manager) LoadCustomCA(certPEM, keyPEM []byte) error
- func (cm *Manager) LoadOrInitCA(path, passphrase string) error
- func (cm *Manager) ParseTLSPair(certPEM, keyPEM []byte) (*tls.Certificate, error)
- func (cm *Manager) Ready() bool
- func (cm *Manager) RotateIfNeeded(caPath, passphrase string) bool
- func (cm *Manager) SaveCA(path, passphrase string) error
- func (cm *Manager) SecondaryCAActive() bool
- func (cm *Manager) SecondaryCAInfo() map[string]any
- func (cm *Manager) SeedCacheEntryForTest(host string, cert *tls.Certificate, createdAt time.Time)
- func (cm *Manager) SetCAForTest(cert *x509.Certificate, key *ecdsa.PrivateKey)
- func (cm *Manager) SetKeyProvider(kp KeyProvider)
Constants ¶
const ( CacheTTL = certCacheTTL CacheMaxSize = certCacheMaxSize BundleVersion = caVersion )
Exported mirrors of internal tuning constants, for tests that assert cache behavior at the real bounds.
const RotationCheckInterval = caRotationCheckInterval
RotationCheckInterval is how often main's auto-rotation loop should tick.
Variables ¶
var CAChangedObserver func()
CAChangedObserver is invoked (with mu NOT held) whenever the active Root CA key/cert changes at runtime — InitCA (fresh generation, incl. the manual force-rotate API and the InitCA inside RotateIfNeeded) and LoadCustomCA (admin-uploaded CA). Publish-once: package main wires it to flush the client-facing MITM session-ticket keys so a TLS 1.3 client cannot RESUME a session authenticated under the previous CA (the PSK path never re-runs GetCertificate). nil ⇒ no-op. Startup-time calls are harmless (no sessions exist yet). This is intentionally distinct from RotationObserver, which is dual-CA-overlap-specific and does not fire on InitCA/LoadCustomCA.
var RotationObserver func(oldExpiry, newExpiry time.Time)
RotationObserver is invoked after a successful RotateIfNeeded with the old and new CA expiry times when set. Publish-once: package main wires it to fire the cert-rotation alert and bump the rotation counter. nil ⇒ no-op.
var SignLatencyObserver func(seconds float64)
SignLatencyObserver receives each successful leaf-sign's latency in seconds when set. Publish-once: package main wires it to its Prometheus histogram at startup. nil ⇒ no-op (the engine never depends on metrics wiring).
Functions ¶
func DecryptBundle ¶
DecryptBundle opens a PSCA envelope produced by EncryptBundle, validating the magic, version, and minimum iteration count before AES-256-GCM decrypt. It returns a generic error on any failure without disclosing key material.
func EncryptBundle ¶
EncryptBundle seals plaintext into a PSCA envelope: a magic+version header, PBKDF2-SHA256 (600k iterations) key derivation over a random salt, and AES-256-GCM with a random nonce. The layout is FROZEN (see the package doc).
func HasBundleMagic ¶
HasBundleMagic reports whether data begins with the PSCA envelope magic (i.e. is an encrypted bundle rather than plain PEM).
Types ¶
type KeyProvider ¶
type KeyProvider interface {
// SignCertificate creates and signs a certificate using the provider's key.
SignCertificate(template, parent *x509.Certificate, pubKey any) ([]byte, error)
// PublicKey returns the public key corresponding to the signing key.
PublicKey() any
// Name returns a human-readable provider name (e.g. "local", "aws-kms").
Name() string
}
KeyProvider signs certificate data using an externally managed private key.
func NewLocalKeyProvider ¶
func NewLocalKeyProvider(key *ecdsa.PrivateKey) KeyProvider
NewLocalKeyProvider returns the default in-memory KeyProvider backed by key.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager manages the Root CA used for SSL inspection (MITM). It generates leaf certificates on-the-fly and caches them with LRU eviction at certCacheMaxSize entries and TTL of certCacheTTL.
func New ¶
func New() *Manager
New returns a ready-to-use Manager with an initialised (empty) leaf cache. Call InitCA / LoadOrInitCA / LoadCustomCA to install a Root CA.
func (*Manager) AgeCacheEntryForTest ¶
AgeCacheEntryForTest back-dates an existing entry's creation time (past the TTL when the delta exceeds certCacheTTL), for cache-expiry tests.
func (*Manager) CACertForTest ¶
func (cm *Manager) CACertForTest() *x509.Certificate
CACertForTest returns the current Root CA certificate (nil if unset).
func (*Manager) CACertInfo ¶
CACertInfo returns metadata about the current Root CA for the UI dashboard.
func (*Manager) CACertPEM ¶
CACertPEM returns the Root CA certificate encoded as PEM (for browser import).
func (*Manager) CAExpiry ¶
CAExpiry returns the CA certificate NotAfter time, or zero if CA is not ready.
func (*Manager) CacheStats ¶
CacheStats returns (hits, misses, currentSize) for the leaf-cert cache (CA-2). Hits/misses are lock-free atomics; size is read under the cache lock.
func (*Manager) CertCacheLen ¶
CertCacheLen returns the current number of cached leaf certificates (testing).
func (*Manager) CleanupSecondaryCA ¶
func (cm *Manager) CleanupSecondaryCA()
CleanupSecondaryCA removes the secondary CA when its overlap window expires. Driven per-tick by main's StartCAAutoRotation loop.
func (*Manager) ClearCache ¶
func (cm *Manager) ClearCache()
ClearCache removes all cached leaf certificates.
func (*Manager) GetCert ¶
func (cm *Manager) GetCert(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
GetCert is a tls.Config.GetCertificate callback that returns a dynamically signed certificate for the requested ServerName. Results are cached with TTL-based expiry and LRU eviction at certCacheMaxSize entries.
func (*Manager) HasKeyProviderForTest ¶
HasKeyProviderForTest reports whether a key provider has been installed.
func (*Manager) ImportBundle ¶
ImportBundle parses a PEM bundle (CERTIFICATE + EC PRIVATE KEY blocks) and installs the CA into the Manager, clearing the leaf cache.
func (*Manager) InitCA ¶
InitCA generates a fresh in-memory Root CA key pair. Call once at startup when no persisted CA is available.
func (*Manager) KeyProviderName ¶
KeyProviderName returns the name of the active key provider.
func (*Manager) LoadCA ¶
LoadCA reads and decrypts a CA bundle previously written by SaveCA. If passphrase is empty the file is treated as plain PEM.
func (*Manager) LoadCustomCA ¶
LoadCustomCA loads a PEM-encoded CA certificate and private key supplied by the user (e.g. an enterprise intermediate CA). Private key is never stored in cleartext memory beyond the parsing step.
func (*Manager) LoadOrInitCA ¶
LoadOrInitCA loads an existing CA bundle from path (decrypting with passphrase) or, if the file does not exist, generates a fresh CA and saves it. An empty passphrase disables encryption (development/testing only).
The env var CULVERT_CA_PASSPHRASE is the recommended way to supply the passphrase so it never appears in CLI history or process listings.
func (*Manager) ParseTLSPair ¶
func (cm *Manager) ParseTLSPair(certPEM, keyPEM []byte) (*tls.Certificate, error)
ParseTLSPair validates a PEM cert+key pair without storing it.
func (*Manager) RotateIfNeeded ¶
RotateIfNeeded checks whether the CA cert is nearing expiry and generates a new one with dual-CA overlap: the old CA is kept as secondary for the remaining lifetime of its certificate, so leaf certs signed by either CA remain valid during the transition. Returns true if rotation occurred.
func (*Manager) SaveCA ¶
SaveCA encrypts (AES-256-GCM, key derived via PBKDF2-SHA256) and writes the current Root CA key+cert to path. If passphrase is empty the bundle is written in plain PEM — only suitable for development/testing environments.
func (*Manager) SecondaryCAActive ¶
SecondaryCAActive returns whether a secondary (old) CA is still in the overlap window.
func (*Manager) SecondaryCAInfo ¶
SecondaryCAInfo returns info about the secondary CA, or nil if inactive.
func (*Manager) SeedCacheEntryForTest ¶
SeedCacheEntryForTest inserts a leaf-cache entry with an explicit creation time so cache TTL / LRU-eviction behavior can be exercised deterministically.
func (*Manager) SetCAForTest ¶
func (cm *Manager) SetCAForTest(cert *x509.Certificate, key *ecdsa.PrivateKey)
SetCAForTest installs a Root CA cert+key directly, bypassing generation.
func (*Manager) SetKeyProvider ¶
func (cm *Manager) SetKeyProvider(kp KeyProvider)
SetKeyProvider allows an external key provider (HSM/KMS) to be registered.