Skip to content

crypto/tls: X25519MLKEM768 listed in allowedCurvePreferencesFIPS but always fails under GODEBUG=fips140=only #78178

@ycombinator

Description

@ycombinator

Go version

go1.25.8

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/shaunak/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/shaunak/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=...=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/shaunak/.gvm/pkgsets/go1.25.8/global/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/shaunak/.gvm/pkgsets/go1.25.8/global'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/shaunak/.gvm/gos/go1.25.8'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/shaunak/.gvm/gos/go1.25.8/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.25.8'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Running a TLS test suite with GODEBUG=fips140=only (no tlsmlkem=0) against a TLS server using the default curve preferences.

What did you see happen?

TLS handshakes fail immediately with:

crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode

This happens even though the TLS FIPS policy in defaults_fips140.go lists X25519MLKEM768 in allowedCurvePreferencesFIPS, implying it should be permitted.

What did you expect to see?

Either:

  • The handshake succeeds by negotiating X25519MLKEM768 (if it is truly FIPS-permitted), or
  • X25519MLKEM768 is absent from allowedCurvePreferencesFIPS so it is never offered, consistent with the primitive-layer enforcement in src/crypto/ecdh/x25519.go lines 39, 51, and 71

Root cause

X25519MLKEM768 is a hybrid key exchange combining ML-KEM-768 and X25519. When negotiated, it invokes ecdh.X25519() operations, which are unconditionally rejected in fips140=only mode at src/crypto/ecdh/x25519.go lines 39, 51, and 71:

if fips140only.Enforced() {
    return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
}

X25519/Curve25519 is correctly blocked here — it is not a NIST-approved curve and has never been added to the FIPS 140 approved list. However, allowedCurvePreferencesFIPS in src/crypto/tls/defaults_fips140.go lines 33–39 currently includes it:

allowedCurvePreferencesFIPS = []CurveID{
    X25519MLKEM768,        // ← always fails under fips140=only
    SecP256r1MLKEM768,
    SecP384r1MLKEM1024,
    CurveP256,
    CurveP384,
    CurveP521,
}

This creates an inconsistency: the TLS policy layer permits X25519MLKEM768, so the client advertises it in the ClientHello and attempts to generate a key share — but the crypto primitive layer then immediately rejects it. The result is a broken handshake rather than graceful fallback to a supported group.

History

X25519MLKEM768 was added to allowedCurvePreferencesFIPS in commit 6114b69e0c ("crypto/tls: relax native FIPS 140-3 mode", fixing #71757) with the intent of allowing ML-KEM under the native FIPS 140-3 module. At that time, X25519MLKEM768 was the only ML-KEM TLS option available. The FIPS-safe alternatives SecP256r1MLKEM768 (ML-KEM-768 + P-256) and SecP384r1MLKEM1024 (ML-KEM-1024 + P-384) were added to the allowlist later in commit 1768cb40b8. Now that those exist in allowedCurvePreferencesFIPS, X25519MLKEM768 is both unreachable and broken in fips140=only mode.

Suggested fix

Remove X25519MLKEM768 from allowedCurvePreferencesFIPS in src/crypto/tls/defaults_fips140.go. The FIPS-approved post-quantum options SecP256r1MLKEM768 and SecP384r1MLKEM1024 already cover ML-KEM under fips140=only mode, paired with NIST-approved P-curves as required.

Impact

Any program running with GODEBUG=fips140=only using the default TLS curve preferences must currently also set GODEBUG=tlsmlkem=0 as a workaround, even though tlsmlkem=0 disables all MLKEM variants (including the FIPS-valid ones SecP256r1MLKEM768 and SecP384r1MLKEM1024). Removing X25519MLKEM768 from the allowlist would eliminate the need for this workaround and allow those groups to be negotiated correctly in FIPS-only mode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions