Documentation
¶
Overview ¶
Package cert provides secure TLS certificate and CA pool loading with file permission and ownership validation for Go.
The package enforces strict Unix file permissions by default. A nil SecurityOpts always means strict defaults — the zero-effort path is the secure path. On Windows, permission and ownership checks are unavailable; use SecurityEnforced at runtime to detect the platform capability.
Policy is split by file role via the nested FilePerm type: SecurityOpts.Cert applies to CA files and to the cert file in LoadCertKey; SecurityOpts.Key applies only to the key file in LoadCertKey.
Store.Finalize returns a *Pool: an immutable, concurrency-safe value that wraps the assembled *x509.CertPool along with per-cert provenance metadata. Use Pool.Pool() to obtain the *x509.CertPool for use with tls.Config.
Caching and reload: a Store caches directory handles for its lifetime (inherited from the underlying safeio.Root). Permission changes to a directory after its first access are not observed by subsequent loads on the same Store. Use a fresh Store per startup cycle if you need to re-evaluate directory permissions.
Index ¶
- func CertExpiry(tc tls.Certificate) (cert *x509.Certificate, remaining time.Duration, err error)
- func ClientConfig(rootCA *x509.CertPool, clientCert *tls.Certificate, opts ...TLSOption) *tls.Config
- func LoadCertKey(certFile, keyFile string, opts *SecurityOpts) (tls.Certificate, error)
- func SecurityEnforced() bool
- func ServerConfig(cert tls.Certificate, clientCA *x509.CertPool, opts ...TLSOption) *tls.Config
- type FilePerm
- type OwnershipError
- type PermissionError
- type Pool
- type SecurityOpts
- type Store
- func (s *Store) Close() error
- func (s *Store) Count() (int, error)
- func (s *Store) Finalize() (*Pool, error)
- func (s *Store) LoadCADir(dir string, recurse bool) (int, error)
- func (s *Store) LoadCAFile(path string) (int, error)
- func (s *Store) SecurityEnforced() bool
- func (s *Store) Sources() ([]string, error)
- type TLSOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CertExpiry ¶
func CertExpiry(tc tls.Certificate) (cert *x509.Certificate, remaining time.Duration, err error)
CertExpiry scans the leaf and every intermediate in tc.Certificate, returning the certificate with the earliest NotAfter along with the duration remaining until that cert expires. remaining is negative if the returned cert has already expired.
When tc.Leaf is populated, it is used in place of parsing tc.Certificate[0] — this saves one parse and avoids re-parsing what crypto/tls has already cached.
Root CAs are NOT inspected here — they live in the peer's trust store (x509.CertPool), not in tls.Certificate.Certificate. Only the leaf plus shipped intermediates are considered.
Returns an error when tc has no leaf at all (both Certificate is empty and Leaf is nil) or when any chain entry fails to parse.
Tie-break: when two chain entries share the same NotAfter, the one closer to the leaf (lower index in tc.Certificate) is returned.
func ClientConfig ¶
func ClientConfig(rootCA *x509.CertPool, clientCert *tls.Certificate, opts ...TLSOption) *tls.Config
ClientConfig returns an opinionated *tls.Config for a TLS client.
- rootCA is the CA pool for verifying the server's certificate. If nil, the system trust store is used (Go default behavior).
- clientCert is the client's certificate for mTLS. If nil, no client certificate is presented.
- opts are zero or more TLSOption values applied after the opinionated defaults, so they can override them (e.g., WithMinVersion(tls.VersionTLS13) replaces the default TLS 1.2 floor).
Opinionated defaults:
- MinVersion: tls.VersionTLS12
- CipherSuites: not set (Go stdlib defaults are secure and preferred)
- InsecureSkipVerify: false (never set)
The caller may further customize the returned config directly or via TLSOption helpers such as WithServerName, WithNextProtos, and WithSPKIPins.
func LoadCertKey ¶
func LoadCertKey(certFile, keyFile string, opts *SecurityOpts) (tls.Certificate, error)
LoadCertKey loads a TLS certificate and private key from the given files with permission validation. The cert file is checked against opts.Cert (default Perm=0o644); the key file is checked against opts.Key (default Perm=0o600). The cert file may contain a full chain (leaf + intermediates). Encrypted keys are rejected with actionable error messages.
On Windows, permission and ownership checks are no-ops; query SecurityEnforced to detect this at runtime.
If opts is nil, strict defaults are used (Cert: Perm=0o644, Uid=-1, Gid=-1; Key: Perm=0o600, Uid=-1, Gid=-1).
func SecurityEnforced ¶
func SecurityEnforced() bool
SecurityEnforced reports whether permission and ownership checks are actually performed on this platform. Returns true on Unix and false on Windows. Callers that rely on the security guarantee should query this at startup and refuse to run (or log a clear warning) when it returns false.
func ServerConfig ¶
ServerConfig returns an opinionated *tls.Config for a TLS server.
- cert is the server's certificate (from LoadCertKey).
- clientCA is the CA pool for verifying client certificates (for mTLS). If nil, client certificate verification is disabled. If non-nil, ClientAuth is set to RequireAndVerifyClientCert; callers may override via WithClientAuth.
- opts are zero or more TLSOption values applied after the opinionated defaults, so they can override them (e.g., WithMinVersion(tls.VersionTLS13) replaces the default TLS 1.2 floor).
Opinionated defaults:
- MinVersion: tls.VersionTLS12
- CipherSuites: not set (Go stdlib defaults are secure and preferred)
- InsecureSkipVerify: false (never set)
The caller may further customize the returned config directly or via TLSOption helpers such as WithNextProtos, WithClientAuth, WithSPKIPins, and WithSessionTicketKeys.
Types ¶
type FilePerm ¶
type FilePerm struct {
// Perm is the maximum acceptable permission bits.
// Any bits set beyond this mask cause rejection.
Perm fs.FileMode
// Uid is the expected file owner UID. Set to -1 to skip UID check.
Uid int
// Gid is the expected file owner GID. Set to -1 to skip GID check.
Gid int
}
FilePerm is a permission and ownership policy for a single file role. Use -1 for Uid or Gid to skip that ownership check.
Each field has its own zero-value fallback: Perm=0 becomes the role-appropriate default, and Uid=0/Gid=0 become -1 (skip). Because 0 is also the legitimate UID/GID of root, this package does not support "require root ownership" via FilePerm; callers who need that must perform their own out-of-band check. Use -1 explicitly to skip an ownership check.
type OwnershipError ¶
type OwnershipError = safeio.OwnershipError
OwnershipError is returned when a file fails ownership validation. Callers can use errors.As to distinguish ownership failures from I/O or parse errors.
type PermissionError ¶
type PermissionError = safeio.PermissionError
PermissionError is returned when a file fails permission validation. Callers can use errors.As to distinguish permission failures from I/O or parse errors.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool is the immutable result of a finalized Store. It holds the assembled *x509.CertPool plus metadata for provenance lookups.
A Pool is produced by Store.Finalize and remains valid after the originating Store is closed. Pool holds no file descriptors.
A Pool is safe for concurrent readers. Pool retains its reference to the *x509.CertPool; concurrent readers may call Pool() multiple times.
func (*Pool) Count ¶
Count returns the number of CA certificates loaded by the Store that produced this Pool. System trust-store certs (if any) are NOT counted — use Pool() and walk its contents for that.
func (*Pool) Pool ¶
Pool returns the assembled *x509.CertPool for use with tls.Config. The returned value is the same *x509.CertPool instance this Pool holds; the caller takes shared read-only ownership. Mutating it affects any future Source lookups on this Pool and any other reader of the same Pool. Do not call AddCert on the returned pool.
func (*Pool) Source ¶
func (p *Pool) Source(cert *x509.Certificate) string
Source returns the source description for cert — typically the file path or bundle it was loaded from. Returns "" if cert is nil, if cert is not in this Pool, or if cert came from the system trust store (seeded by NewSystemStore and therefore not tracked for provenance).
type SecurityOpts ¶
type SecurityOpts struct {
// Cert is the policy for cert/CA files.
Cert FilePerm
// Key is the policy for private key files (LoadCertKey only).
Key FilePerm
// Role is an optional label included in error messages. When empty,
// the package supplies a role-appropriate label ("ca", "cert", "key")
// based on which file is being checked.
Role string
// Filter, if non-nil, is applied to directory walks in Store
// operations. The Store sets its own default when this is nil.
Filter func(name string) bool
}
SecurityOpts controls per-file permission and ownership policy. A nil value means DefaultSecurityOpts().
Cert applies to CA files (LoadCAFile, LoadCADir) and to the cert file in LoadCertKey. Key applies only to the key file in LoadCertKey and is ignored by Store operations.
Group-writable and world-writable bits on files and directories are always rejected regardless of Perm (hardcoded security floor).
func DefaultSecurityOpts ¶
func DefaultSecurityOpts() *SecurityOpts
DefaultSecurityOpts returns a SecurityOpts with strict role-appropriate defaults:
- Cert: Perm=0o644, Uid=-1, Gid=-1
- Key: Perm=0o600, Uid=-1, Gid=-1
Ownership is not checked by default because the common deployment pattern is a non-root service loading root-owned cert files. Callers that want ownership enforcement must set Uid/Gid explicitly. Permission-bit checks (and the hardcoded group/world-writable floor) still apply and provide the security floor.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store provides secure loading and accumulation of CA certificates into a *Pool. It wraps a safeio.Root with a permission policy appropriate for CA files (default Perm=0644).
For loading cert+key pairs, use the standalone LoadCertKey function which applies its own stricter permission policy (default Perm=0600).
On Windows, permission and ownership checks are no-ops; query SecurityEnforced to detect this at runtime.
Store is safe for concurrent use; a single mutex serializes LoadCAFile, LoadCADir, Finalize, Close, Count, and Sources. The *Pool it produces via Finalize is safe for concurrent readers.
Store inherits the directory-handle caching of safeio.Root: permission changes to a directory after its first access are not observed by subsequent loads on the same Store. Use a fresh Store per startup cycle if you need to re-evaluate directory permissions.
func NewStore ¶
func NewStore(opts *SecurityOpts) *Store
NewStore creates a Store with the given security policy. If opts is nil, strict defaults are used (Cert: Perm=0644, Uid=-1, Gid=-1). Store uses only opts.Cert; opts.Key is ignored.
The Store's safeio.Root filters directory walks by certificate extension (.pem, .crt, .cer) automatically.
func NewSystemStore ¶
func NewSystemStore(opts *SecurityOpts) (*Store, error)
NewSystemStore creates a Store initialized with the system trust store (x509.SystemCertPool). System CA files are NOT subject to permission checks (they are managed by the OS). Only subsequently loaded files are checked. If opts is nil, strict defaults are used.
func (*Store) Close ¶
Close closes all cached directory handles and invalidates the store. After Close, load and read methods on the Store return errors. Any *Pool previously returned by Finalize remains valid (a Pool holds no file descriptors).
Close is idempotent and safe to call after Finalize.
func (*Store) Count ¶
Count returns the number of CA certificates loaded so far (not counting system CAs if NewSystemStore was used).
Count continues to work after Finalize; it returns an error only after the Store has been Close'd without being finalized.
func (*Store) Finalize ¶
Finalize assembles the accumulated CA certificates into an *x509.CertPool, wraps it in a *Pool with provenance metadata, caches the result, and releases all internal directory handles. Subsequent calls return the same cached *Pool.
Callers that previously used the returned *x509.CertPool directly should call Pool.Pool() on the returned value.
After Finalize, LoadCAFile and LoadCADir return errors. Count and Sources continue to work (they read from the preserved state).
func (*Store) LoadCADir ¶
LoadCADir loads PEM-encoded CA certificates from all files in the given directory. If recurse is true, subdirectories are walked. Files are filtered by extension (.pem, .crt, .cer) before opening. Files that are not valid PEM are silently skipped. Files that fail permission checks stop the walk on the first error. Duplicate certificates (same DER bytes) are counted once; the first-seen file path is recorded as the source.
On error, previously loaded certificates remain in the Store and will be included if Finalize is subsequently called. Callers who require all-or-nothing semantics should call Close() on the Store after a LoadCADir error.
Returns the total number of certificates added and any error.
LoadCADir holds the Store's internal mutex for the duration of the directory walk. Concurrent calls to any Store method (including Count and Sources) on the same Store will block until the walk completes. Since LoadCADir is typically called at startup, this is usually fine; callers loading from directories with many large files or across slow filesystems should be aware.
func (*Store) LoadCAFile ¶
LoadCAFile loads one or more PEM-encoded CA certificates from a single file (which may be a bundle containing multiple certs). Returns the number of certificates added and any error.
Returns an error if the file contains no CERTIFICATE blocks OR if every certificate is already present in the Store (duplicates are detected by sha256 of the DER bytes; the first-seen source is kept).
func (*Store) SecurityEnforced ¶
SecurityEnforced reports whether permission and ownership checks are actually performed on this platform. Returns true on Unix and false on Windows.
Unguarded: reads only the immutable build-time platform constant.
type TLSOption ¶
TLSOption mutates a *tls.Config after opinionated defaults have been applied. It lets callers layer small, composable overrides onto the configs returned by ServerConfig and ClientConfig.
func WithClientAuth ¶
func WithClientAuth(mode tls.ClientAuthType) TLSOption
WithClientAuth sets cfg.ClientAuth. This is a server-side option. ServerConfig sets ClientAuth to tls.RequireAndVerifyClientCert when a non-nil clientCA is provided; this option lets callers downgrade to tls.VerifyClientCertIfGiven, tls.RequestClientCert, or similar.
func WithMinVersion ¶
WithMinVersion sets cfg.MinVersion to v. No clamping is performed; callers are responsible for passing a meaningful value. Common values: tls.VersionTLS12, tls.VersionTLS13.
func WithNextProtos ¶
WithNextProtos sets cfg.NextProtos for ALPN negotiation. A typical use is WithNextProtos("h2", "http/1.1") to advertise HTTP/2 with a fallback to HTTP/1.1.
func WithSPKIPins ¶
WithSPKIPins installs a cfg.VerifyConnection callback that rejects the handshake unless the leaf peer certificate's SubjectPublicKeyInfo SHA-256 digest matches one of the provided pins. Pinning is leaf-only: intermediates and roots in the chain are not considered, so a compromised intermediate cannot bypass the pin by issuing a new leaf. Each pin must be a raw 32-byte SHA-256 digest; pins of any other length are skipped.
Error behavior:
- If all supplied pins are malformed (none is 32 bytes), every handshake is rejected with "cert: no valid SPKI pins configured".
- If the handshake presents no peer certificates, it is rejected with "cert: no peer certificates presented".
- If the leaf's SPKI hash does not match any configured pin, the handshake is rejected with "cert: no matching SPKI pin".
If cfg.VerifyConnection is already set when this option is applied, the existing callback is chained and invoked only on a successful pin match; pin rejection short-circuits and the previous callback is not called.
func WithServerName ¶
WithServerName sets cfg.ServerName. This is primarily a client-side option used for SNI and certificate verification. On a server config it is a no-op for most code paths but remains harmless.
func WithSessionTicketKeys ¶
WithSessionTicketKeys installs the given session ticket keys via cfg.SetSessionTicketKeys. This is a server-side option used to rotate ticket keys across instances for mTLS fleets. If len(keys) == 0 the option is a no-op.