golang/go · error

tls: server selected unsupported curve

Error message

tls: server selected unsupported curve

What it means

During TLS 1.0–1.2 ECDHE key exchange on the client side, the server's ServerKeyExchange message specified a curve type other than 'named_curve' (3). The first byte of the ECDHE params (skx.key[0]) must be 3 per RFC 4492/8422; any other value (e.g. 1=explicit_prime or 2=explicit_char2) is rejected because Go's crypto/tls only supports named curves.

Source

Thrown at src/crypto/tls/key_agreement.go:276

	peerKey, err := ka.key.Curve().NewPublicKey(ckx.ciphertext[1:])
	if err != nil {
		return nil, errClientKeyExchange
	}
	preMasterSecret, err := ka.key.ECDH(peerKey)
	if err != nil {
		return nil, errClientKeyExchange
	}

	return preMasterSecret, nil
}

func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
	if len(skx.key) < 4 {
		return errServerKeyExchange
	}
	if skx.key[0] != 3 { // named curve
		return errors.New("tls: server selected unsupported curve")
	}
	ka.curveID = CurveID(skx.key[1])<<8 | CurveID(skx.key[2])

	publicLen := int(skx.key[3])
	if publicLen+4 > len(skx.key) {
		return errServerKeyExchange
	}
	serverECDHEParams := skx.key[:4+publicLen]
	publicKey := serverECDHEParams[4:]

	sig := skx.key[4+publicLen:]
	if len(sig) < 2 {
		return errServerKeyExchange
	}
	if ka.version >= VersionTLS12 {
		ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
		sig = sig[2:]
		if len(sig) < 2 {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Update the server to a modern TLS library that uses named curves (curve type 3).
  2. Verify no intermediary is modifying the ServerKeyExchange message.
  3. If you control the server, ensure it only sends named_curve ECDHE parameters.
  4. Capture the ServerKeyExchange with Wireshark and check the ECParameters curve_type field.
  5. Use TLS 1.3 which eliminates this legacy parameter format.
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side: no pre-check is possible since the server controls ServerKeyExchange.
// You can restrict cipher suites to avoid ECDHE with non-conformant servers:
// Prefer TLS 1.3 which doesn't have this format issue.
func preferTLS13(cfg *tls.Config) {
    cfg.MinVersion = tls.VersionTLS13
}

Try / catch

// Client-side: handle during handshake
if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "server selected unsupported curve") {
        log.Printf("server sent non-named-curve ECDHE params: %v", err)
        // Server is non-conformant or very old — upgrade or avoid
    }
}

Prevention

When it happens

Trigger: Client calls ecdheKeyAgreement.processServerKeyExchange and the first byte of skx.key is not 3. The server sent an explicit curve parameter set (deprecated in RFC 8422) instead of a named curve ID.

Common situations: Server uses a very old or non-conformant TLS library that sends explicit curve parameters; a custom server implementation that doesn't use named curves; a MITM altering the ServerKeyExchange; testing against an old server that predates RFC 8422 deprecation of explicit curves.

Understand the failure class

Related errors


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