golang/go · error

tls: no supported key exchange methods (CurveIDs)

Error message

tls: no supported key exchange methods (CurveIDs)

What it means

Thrown by makeClientHello when TLS 1.3 is being negotiated (maxVersion >= VersionTLS13) but hello.supportedCurves is empty after filtering. The supportedCurves list is derived from config.curvePreferences(maxVersion), which filters the library's known curve set by the config's CurvePreferences and FIPS constraints. An empty result means no usable key exchange curve is available for TLS 1.3, which requires at least one named group for key exchange.

Source

Thrown at src/crypto/tls/handshake_client.go:143

	}

	var keyShareKeys *keySharePrivateKeys
	if maxVersion >= VersionTLS13 {
		// Reset the list of ciphers when the client only supports TLS 1.3.
		if minVersion >= VersionTLS13 {
			hello.cipherSuites = nil
		}

		if fips140tls.Required() {
			hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...)
		} else if hasAESGCMHardwareSupport {
			hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
		} else {
			hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
		}

		if len(hello.supportedCurves) == 0 {
			return nil, nil, nil, errors.New("tls: no supported key exchange methods (CurveIDs)")
		}
		// Since the order is fixed, the first one is always the one to send a
		// key share for. All the PQ hybrids sort first, and produce a fallback
		// ECDH share.
		curveID := hello.supportedCurves[0]
		ke, err := keyExchangeForCurveID(curveID)
		if err != nil {
			return nil, nil, nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve")
		}
		keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
		if err != nil {
			return nil, nil, nil, err
		}
		// Only send the fallback ECDH share if the corresponding CurveID is enabled.
		if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) {
			hello.keyShares = hello.keyShares[:1]
		}
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Include at least one standard, library-supported curve in CurvePreferences (e.g., tls.X25519, tls.CurveP256, tls.CurveP384)
  2. Leave CurvePreferences unset (nil) to use the library default curve set
  3. If running in FIPS mode, ensure CurvePreferences includes at least one FIPS-approved curve (P-256, P-384)
  4. Check GODEBUG settings that might disable specific curves at runtime

Example fix

// before — only unsupported curves
config := &tls.Config{
    CurvePreferences: []tls.CurveID{999, 998}, // unknown curve IDs
}
// after
config := &tls.Config{
    CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384},
}
// or omit entirely:
config := &tls.Config{}
Defensive patterns

Strategy: validation

Validate before calling

func validateCurvePreferences(config *tls.Config) error {
    if len(config.CurvePreferences) == 0 {
        return nil // nil/empty uses library defaults — always valid
    }
    knownCurves := []tls.CurveID{
        tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521,
    }
    hasKnown := false
    for _, c := range config.CurvePreferences {
        for _, k := range knownCurves {
            if c == k {
                hasKnown = true
                break
            }
        }
    }
    if !hasKnown {
        return errors.New("CurvePreferences contains no library-supported curves")
    }
    return nil
}

Type guard

// Check if a CurveID is a standard supported curve
func isStandardCurve(c tls.CurveID) bool {
    switch c {
    case tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521:
        return true
    }
    return false
}

Try / catch

// Pre-validate before dial:
//
//   if err := validateCurvePreferences(config); err != nil {
//       config.CurvePreferences = nil // reset to defaults
//   }

Prevention

When it happens

Trigger: Setting config.CurvePreferences to a non-empty slice containing only curve IDs that are unknown to the library (all filtered out by supportsCurve). Enabling FIPS mode (fips140tls.Required()) while CurvePreferences contains only curves not in the FIPS-allowed set. A GODEBUG setting that disables all default curves.

Common situations: Setting CurvePreferences to experimental or unsupported curve IDs. FIPS mode filtering out all configured curves (e.g., X25519 may not be FIPS-approved depending on the module). A GODEBUG flag like x25519mul=0 or similar that disables a curve at runtime, combined with a CurvePreferences list that only includes that curve.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/276effeebf1c5d93. Report an issue: GitHub.