golang/go · warning

client doesn't support ECDHE, can only use legacy RSA key ex

Error message

client doesn't support ECDHE, can only use legacy RSA key exchange

What it means

Thrown by SupportsCertificate when the client does not support ECDHE key exchange (no common elliptic curve or point format), and the fallback static-RSA check is wrapped around this error. ECDHE is the only signed key exchange Go supports; without it the connection can only use legacy static RSA. The actual return is supportsRSAFallback(this error), so callers may see nil if static RSA is viable.

Source

Thrown at src/crypto/tls/common.go:1473

		if _, err := selectSignatureScheme(vers, c, chi.SignatureSchemes); err != nil {
			return supportsRSAFallback(err)
		}
	}

	// In TLS 1.3 we are done because supported_groups is only relevant to the
	// ECDHE computation, point format negotiation is removed, cipher suites are
	// only relevant to the AEAD choice, and static RSA does not exist.
	if vers == VersionTLS13 {
		return nil
	}

	// The only signed key exchange we support is ECDHE.
	ecdheSupported, err := supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints)
	if err != nil {
		return err
	}
	if !ecdheSupported {
		return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange"))
	}

	var ecdsaCipherSuite bool
	if priv, ok := c.PrivateKey.(crypto.Signer); ok {
		switch pub := priv.Public().(type) {
		case *ecdsa.PublicKey:
			var curve CurveID
			switch pub.Curve {
			case elliptic.P256():
				curve = CurveP256
			case elliptic.P384():
				curve = CurveP384
			case elliptic.P521():
				curve = CurveP521
			default:
				return supportsRSAFallback(unsupportedCertificateError(c))
			}
			var curveOk bool

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Add common curves (X25519, P-256) to the server's Config.CurvePreferences and the client's SupportedCurves.
  2. Upgrade the client to a TLS stack that advertises ECDHE curves.
  3. If legacy support is required, allow an RSA cipher suite and TLS <=1.2 so static RSA fallback works.
  4. Avoid disabling all curves in CurvePreferences.

Example fix

// before
cfg.CurvePreferences = []tls.CurveID{tls.CurveP521} // client has only X25519

// after
cfg.CurvePreferences = []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP521}
Defensive patterns

Strategy: validation

Validate before calling

// Verify ECDHE is achievable for a ClientHello before committing to an ECDSA cert.
func supportsModernECDHE(chi *tls.ClientHelloInfo) bool {
    for _, c := range chi.SupportedCurves {
        if c == tls.X25519 || c == tls.CurveP256 { return true }
    }
    return false
}

Prevention

When it happens

Trigger: SupportsCertificate on a cert where supportsECDHE returns false: the client's SupportedCurves/SupportedPoints share nothing with the server's allowed curves. The error is returned only if static-RSA fallback is also unsupported (TLS 1.3, non-RSA key, or no mutual RSA cipher suite).

Common situations: Legacy client advertising no modern curves; server restricted CurvePreferences to curves the client lacks; TLS 1.3 (no static RSA) combined with a curve-less client; hardening that removed common curves.

Related errors


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