Files
cloudflared/crypto/curves.go
T
lneto f674b82e2a TUN-10413: Centralize TLS curve configuration in crypto/ and adopt X25519MLKEM768 for QUIC/H2
Introduce a new crypto/ package as the single source of truth for TLS
curve preferences used on every edge-facing connection, and adopt
X25519MLKEM768 as the primary post-quantum key exchange for both QUIC
and HTTP/2:

  PQ Prefer (default):     X25519MLKEM768, P256Kyber768Draft00, CurveP256
  PQ Strict (--post-quantum): X25519MLKEM768, P256Kyber768Draft00

The curve list is identical under FIPS and non-FIPS builds, so
crypto.GetCurvePreferences takes only a features.PostQuantumMode and
returns a fresh slice on every call.

HTTP/2 now applies these curve preferences the same way QUIC does. The
previous PostQuantumStrict rejection in serveHTTP2 and the forced
QUIC-only selection in NewProtocolSelector are removed since both
transports support the same post-quantum curves; the needPQ parameter
is dropped from NewProtocolSelector accordingly.

Also fix a shared tls.Config race: both the QUIC and HTTP/2 paths now
Clone() the per-protocol entry from TunnelConfig.EdgeTLSConfigs before
mutating CurvePreferences instead of writing through the shared map
entry.

Legacy Kyber draft curve X25519Kyber768Draft00
and the unused removeDuplicates helper are removed along with the old
supervisor/pqtunnels.go / _test.go files.

AGENTS.md is updated with guidance on the new crypto/ package, the
cfdcrypto import alias, the tls.Config cloning rule, and the lint
workflow implications of .golangci.yaml's whole-files: true setting.
2026-05-12 07:47:38 +01:00

80 lines
3.2 KiB
Go

package crypto
import (
"crypto/tls"
"errors"
"fmt"
"slices"
"github.com/cloudflare/cloudflared/features"
)
// errUnknownPostQuantumMode is returned by GetCurvePreferences when the
// caller passes a features.PostQuantumMode value that is not one of the
// documented constants. It is intentionally unexported: callers should treat
// any non-nil error as a programming mistake rather than inspecting it.
var errUnknownPostQuantumMode = errors.New("the provided post quantum mode is unknown")
// P256Kyber768Draft00 is a post-quantum KEM based on Kyber768.
const P256Kyber768Draft00 = tls.CurveID(0xfe32) // ID 65074
// Canonical curve lists returned by GetCurvePreferences. They are kept
// package-private so that callers cannot accidentally mutate the shared
// slice; GetCurvePreferences always returns a clone.
var (
// postQuantumStrictCurves is used when the caller requires a
// post-quantum handshake. Only PQ curves (X25519MLKEM768 and the
// deprecated P256Kyber768Draft00 for backward compatibility) are
// advertised; no classical-only curve is included.
postQuantumStrictCurves = []tls.CurveID{tls.X25519MLKEM768, P256Kyber768Draft00}
// postQuantumPreferCurves is used for the default "prefer" mode: the PQ
// curve is advertised first and the classical CurveP256 is listed as a
// fallback so peers without PQ support can still negotiate.
postQuantumPreferCurves = []tls.CurveID{tls.X25519MLKEM768, P256Kyber768Draft00, tls.CurveP256}
)
// getCurvePreferences returns the TLS curve preferences that should be
// applied to edge-facing connections for the given post-quantum mode.
//
// The returned slice is the canonical, protocol-agnostic curve list and is
// suitable for direct assignment to tls.Config.CurvePreferences. A fresh
// slice is returned on every call, so callers may mutate it freely without
// affecting other callers.
//
// An error is returned only when profile is not a recognised
// features.PostQuantumMode value, which indicates a programming bug in the
// caller.
func getCurvePreferences(profile features.PostQuantumMode) ([]tls.CurveID, error) {
switch profile {
case features.PostQuantumPrefer:
return slices.Clone(postQuantumPreferCurves), nil
case features.PostQuantumStrict:
return slices.Clone(postQuantumStrictCurves), nil
}
return nil, errUnknownPostQuantumMode
}
// TLSConfigWithCurvePreferences clones the provided tls.Config and applies
// curve preferences based on the given post-quantum mode.
//
// The original tls.Config is never modified; a clone is returned so that
// callers can safely use the same base configuration across multiple
// goroutines without racing on CurvePreferences.
//
// Returns an error only when pqMode is not a recognised
// features.PostQuantumMode value.
func TLSConfigWithCurvePreferences(tlsConfig *tls.Config, pqMode features.PostQuantumMode) (*tls.Config, error) {
// Clone the TLS config before applying per-connection curve
// preferences. The TlsConfig may be shared across goroutines;
// mutating it directly would race with concurrent connection attempts.
config := tlsConfig.Clone()
curvePref, err := getCurvePreferences(pqMode)
if err != nil {
return nil, fmt.Errorf("get curve preferences: %w", err)
}
config.CurvePreferences = curvePref
return config, nil
}