Skip to content

[ENHANCEMENT] add tls curve preferences - #380

Merged
jgbernalp merged 3 commits into
mainfrom
add-tls-curves-preferences
Nov 10, 2026
Merged

jgbernalp merged 3 commits into
mainfrom
add-tls-curves-preferences

Conversation

@jgbernalp

Copy link
Copy Markdown
Contributor

No description provided.

@jgbernalp
jgbernalp requested a review from Nexucis November 10, 2026 09:42

@Nexucis Nexucis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this! The parsing and the TLS 1.3-only logic look right: isTLS13OnlyCurve matches isTLS13OnlyKeyExchange in Go 1.27, supportedCurves matches the internal list of crypto/tls, and the tests are a nice way to detect a change after a Go upgrade.

Two things to address before merging (details inline):

  1. FIPS 140-3 mode: with GODEBUG=fips140=on, crypto/tls doesn't use X25519.
    • TestIsTLS13OnlyCurve/X25519 fails, while the echo tests pass with this setting on main.
    • --web.tls-curve-preferences=X25519 passes Build, but every TLS handshake then fails: the server starts but can't be used.
  2. TLS 1.3-only curves while TLS 1.2 is allowed: --web.tls-curve-preferences=X25519MLKEM768 without --web.tls-min-version=1.3 is accepted without any warning, and every TLS 1.2 client then fails with remote error: tls: handshake failure.

Comment thread echo/server.go
flag.StringVar(&tlsMinVersion, "web.tls-min-version", "", "Minimum TLS version (e.g., \"1.2\", \"1.3\")")
flag.StringVar(&tlsMaxVersion, "web.tls-max-version", "", "Maximum TLS version (e.g., \"1.2\", \"1.3\")")
flag.StringVar(&tlsCipherSuites, "web.tls-cipher-suites", "", "Comma-separated list of TLS cipher suite names")
flag.StringVar(&tlsCurvePreferences, "web.tls-curve-preferences", "", "Comma-separated list of TLS curves (key exchange mechanisms), e.g. \"X25519MLKEM768,X25519,CurveP256\". Replaces Go's default list, including its post-quantum curves; the order is ignored.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With GODEBUG=fips140=on, crypto/tls ignores the curves that aren't FIPS-approved (with the Go FIPS 140-3 module, X25519 is currently the only one of supportedCurves). So --web.tls-curve-preferences=X25519 passes Build, but no key exchange is possible: every TLS handshake fails, and the server starts but can't be used.

Could checkCurvePreferences reject a list without any FIPS-approved curve when fips140.Enabled(), like it rejects a list without any TLS 1.2 curve when TLS 1.3 is disabled? Or at least, could the flag help mention it?

Comment thread echo/server.go Outdated
// checkCurvePreferences checks the curves can be used with the TLS max version: when TLS 1.3 is disabled and all the
// curves require it, no ECDHE key exchange is possible.
func checkCurvePreferences(maxVersion uint16, curves []tls.CurveID) error {
if maxVersion == 0 || maxVersion >= tls.VersionTLS13 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only covers TLS 1.3 being disabled. The opposite case isn't detected: when all the curves require TLS 1.3 while TLS 1.2 is still allowed (--web.tls-min-version unset or 1.2), for example --web.tls-curve-preferences=X25519MLKEM768 alone, Build succeeds silently, and every TLS 1.2 client then fails with remote error: tls: handshake failure.

Like the warning below, could we also pass parsedMinVersion and warn in this case (with a test case)? For example:

func checkCurvePreferences(minVersion, maxVersion uint16, curves []tls.CurveID) error {
	var tls13Only []string
	for _, id := range curves {
		if isTLS13OnlyCurve(id) {
			tls13Only = append(tls13Only, id.String())
		}
	}
	if len(tls13Only) == 0 {
		return nil
	}
	onlyTLS13 := len(tls13Only) == len(curves)
	// The versions are 0 when unset: TLS 1.2 and TLS 1.3 are then both enabled.
	tls13Disabled := maxVersion != 0 && maxVersion < tls.VersionTLS13
	switch {
	case tls13Disabled && onlyTLS13:
		return fmt.Errorf(...) // unchanged
	case tls13Disabled:
		logrus.Warnf(...) // unchanged
	case onlyTLS13 && minVersion < tls.VersionTLS13:
		logrus.Warn("TLS 1.2 clients cannot connect, because all the curves of --web.tls-curve-preferences require TLS 1.3: set --web.tls-min-version to 1.3 to make it explicit")
	}
	return nil
}

Comment thread echo/server_test.go

// TestIsTLS13OnlyCurve checks isTLS13OnlyCurve matches crypto/tls, which doesn't expose this information:
// for each curve, a TLS 1.2 handshake allowing only this curve must fail if and only if isTLS13OnlyCurve returns true.
func TestIsTLS13OnlyCurve(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fails with GODEBUG=fips140=on go test ./echo/ (X25519 subtest): in FIPS 140-3 mode, crypto/tls doesn't use X25519, so even the TLS 1.3 handshake fails. The echo tests pass with this setting on main.

As the regular run already checks the mapping, the test could be skipped in FIPS mode (with the crypto/fips140 import):

func TestIsTLS13OnlyCurve(t *testing.T) {
	if fips140.Enabled() {
		t.Skip("crypto/tls only uses the FIPS-approved curves in FIPS 140-3 mode")
	}

@jgbernalp

jgbernalp commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor Author

@Nexucis comments fixed, PTAL. Also rebased with the CA changes

Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
@jgbernalp
jgbernalp force-pushed the add-tls-curves-preferences branch from 51e49c2 to d904bd6 Compare November 10, 2026 12:03

@Nexucis Nexucis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! The three points are fixed:

  • isFIPSApprovedCurve matches allowedCurvePreferencesFIPS of crypto/tls in Go 1.27, and the note about Go+BoringCrypto is right (crypto/tls/fipsonly only forces fips140tls).
  • The warning for the curves that require TLS 1.3 while TLS 1.2 is allowed works, and it uses the curves that are left in FIPS 140-3 mode.
  • The echo tests pass with GODEBUG=fips140=on and GODEBUG=fips140=only (also with -race).

Two non-blocking suggestions inline.

Comment thread echo/server_test.go
// in FIPS 140-3 mode, a handshake allowing only one curve must succeed if and only if isFIPSApprovedCurve returns true.
// It only runs in FIPS 140-3 mode: GODEBUG=fips140=on go test ./echo/
func TestIsFIPSApprovedCurve(t *testing.T) {
if !fips140.Enabled() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: the CI runs make test without GODEBUG, so this test is always skipped there, like the fips140.Enabled() branch of build(). As isFIPSApprovedCurve copies the unexported allowedCurvePreferencesFIPS, a change of this list after a Go upgrade wouldn't be detected. For example, if Go allows X25519 in FIPS 140-3 mode one day, --web.tls-curve-preferences=X25519 would make Build fail, while crypto/tls would accept it.

Could make test also run the echo tests in FIPS 140-3 mode? It passes locally, also with -race:

test:
	@echo ">> running all tests"
	$(GO) test -race -count=1 -v $(pkgs)
	@echo ">> running the echo tests in FIPS 140-3 mode"
	GODEBUG=fips140=on $(GO) test -race -count=1 ./echo/...

Then, the comment of TestSupportedCurves could also mention TestIsFIPSApprovedCurve, next to TestIsTLS13OnlyCurve.

Comment thread echo/server.go Outdated
Comment on lines +541 to +549
usableCurves := parsedCurvePreferences
if fips140.Enabled() {
if usableCurves, err = checkFIPSCurvePreferences(usableCurves); err != nil {
return nil, err
}
}
if err = checkCurvePreferences(parsedMinVersion, parsedMaxVersion, usableCurves); err != nil {
return nil, err
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (non-blocking): these checks also run when HTTPS isn't enabled. Without --web.tls-cert-file:

  • --web.tls-curve-preferences=X25519MLKEM768 logs TLS 1.2 clients cannot connect, ..., and then that the TLS flags have no effect because HTTPS is not enabled.
  • In FIPS 140-3 mode, --web.tls-curve-preferences=X25519 makes Build fail, although the flag has no effect.

Could they only run when HTTPS is enabled (cert != "")?

Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
@jgbernalp
jgbernalp merged commit 5639335 into main Nov 10, 2026
6 checks passed
@jgbernalp
jgbernalp deleted the add-tls-curves-preferences branch November 10, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants