Skip to content

Security threat model & defaults

This package exists so that "use TLS" does not become "hand-roll a crypto/tls.Config and hope." Go's zero-value tls.Config is deliberately permissive — it negotiates down to TLS 1.0, offers CBC-mode and RSA-key-exchange cipher suites, and leaves curve selection to the runtime. Those defaults prioritise interoperability with old peers over security. For first-party services talking to first-party clients, that trade is backwards. DefaultConfig inverts it.

Every server and client config this package produces is built from DefaultConfig, so the guarantees below hold uniformly across both ends of a connection.

What we defend against

  • Protocol-downgrade attacks. A network attacker who can influence the handshake should not be able to force a connection onto a weak, break-able protocol version.
  • Weak cipher negotiation. Cipher suites with known structural weaknesses (CBC padding oracles, non-AEAD constructions, static RSA key exchange with no forward secrecy) should never be on the table, even if a peer offers them.
  • Missing forward secrecy. Compromise of a server's long-term private key should not retroactively decrypt previously-captured sessions.
  • Fragile trust configuration. A misconfigured certificate path or an empty CA bundle should fail loudly at construction, not silently accept an untrusted peer.

What this package does not do: it is not a PKI, a certificate issuer, or a secret store. It consumes certificate paths you provide and applies a hardened policy. How those files are provisioned, rotated, and protected on disk is the caller's responsibility.

The defaults, and why

TLS 1.2 as the floor

MinVersion is pinned to VersionTLS12. TLS 1.0 and 1.1 are deprecated (RFC 8996) and carry downgrade and cipher-construction weaknesses. Setting an explicit floor removes them from negotiation entirely, defeating downgrade attempts rather than relying on peers to prefer better. TLS 1.3 is negotiated automatically when both ends support it; the floor guarantees the worst case is still 1.2.

AEAD-only cipher suites

The suite list is restricted to six ECDHE authenticated-encryption suites:

Suite Key exchange Auth Cipher
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ECDHE ECDSA AES-256-GCM
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ECDHE RSA AES-256-GCM
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ECDHE ECDSA AES-128-GCM
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDHE RSA AES-128-GCM
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 ECDHE ECDSA ChaCha20-Poly1305
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 ECDHE RSA ChaCha20-Poly1305

Three properties are guaranteed by construction:

  • Forward secrecy — every suite uses ephemeral ECDHE key exchange, so a stolen long-term key cannot decrypt past captured traffic.
  • AEAD only — GCM and ChaCha20-Poly1305 authenticate as they encrypt. There are no CBC-mode suites, so the padding-oracle class of attacks (Lucky 13 and relatives) does not apply.
  • Both signature families — ECDSA and RSA variants are present so the list works with either an EC or an RSA server certificate; the negotiated suite follows the certificate's key type.

Both AES-GCM and ChaCha20-Poly1305 are included so hardware without AES acceleration still negotiates a fast, constant-time cipher.

Cipher order and TLS 1.3

Go ignores the CipherSuites list for TLS 1.3 connections — the 1.3 suites are fixed by the protocol and always safe. This list governs the TLS 1.2 floor. Go also chooses the mutually-preferred suite itself; the list defines which suites are permitted, not a rigid server-dictated order.

Modern curve preferences

CurvePreferences is set to X25519 then CurveP256. X25519 is a fast, misuse- resistant Montgomery curve and is preferred; P-256 follows for peers or certificates that require a NIST curve. Slower and less-scrutinised curves are omitted.

Failing closed on trust

The trust helpers are written to error rather than silently degrade:

  • CertPool returns an error if a CA file cannot be read, and — importantly — if a file parses to zero certificates. An empty or malformed bundle is a configuration error, not an empty set of trusted roots to shrug at.
  • ClientConfig propagates those errors, so a client is never constructed with a broken custom trust store. With no CA files it falls back to the system root store (the correct default for public endpoints); it never disables verification.
  • Pair.Certificate and ServerConfig surface load failures at startup, so a bad certificate path stops the process rather than breaking the first client handshake.

There is deliberately no option to skip verification or lower the floor. If you need a looser policy for a specific integration, build a crypto/tls.Config yourself for that one call site — the insecure path should be explicit and local, never the default this package hands out.

Relationship to crypto/tls

This package is a thin, opinionated layer over the standard library, not a replacement for it. DefaultConfig returns a plain *crypto/tls.Config you can inspect or further constrain. The value it adds is a single, reviewed set of decisions applied consistently — so a security review happens once, here, instead of at every listener in every service.