golang/go · error
tls: server selected unoffered curve
Error message
tls: server selected unoffered curve
What it means
The client's ECDHE key agreement validation found that the curve the server chose for key exchange (ka.curveID) is not present in the clientHello.supportedCurves list the client originally offered. TLS requires the server pick a mutually supported group; selecting one the client never offered violates RFC 8422/8446 negotiation and is treated as a protocol error or attack. This is a deliberate guard against downgrade and invalid-curve tricks.
Source
Thrown at src/crypto/tls/key_agreement.go:309
if ka.version >= VersionTLS12 {
ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
sig = sig[2:]
if len(sig) < 2 {
return errServerKeyExchange
}
switch ka.signatureAlgorithm {
case MLDSA44, MLDSA65, MLDSA87:
return errors.New("tls: server selected ML-DSA with TLS version < 1.3")
}
}
sigLen := int(sig[0])<<8 | int(sig[1])
if sigLen+2 != len(sig) {
return errServerKeyExchange
}
sig = sig[2:]
if !slices.Contains(clientHello.supportedCurves, ka.curveID) {
return errors.New("tls: server selected unoffered curve")
}
if _, ok := curveForCurveID(ka.curveID); !ok {
return errors.New("tls: server selected unsupported curve")
}
key, err := generateECDHEKey(config.rand(), ka.curveID)
if err != nil {
return err
}
ka.key = key
peerKey, err := key.Curve().NewPublicKey(publicKey)
if err != nil {
return errServerKeyExchange
}
ka.preMasterSecret, err = key.ECDH(peerKey)
if err != nil {View on GitHub (pinned to b6b368adc5)
Solutions
- Inspect the negotiated curve and the client's CurvePreferences / supported groups to find the mismatch.
- If the client intentionally restricts curves, ensure the server offers at least one of them; otherwise widen CurvePreferences on the client to include the server's choice.
- Update or patch a non-conformant server to respect the client's supported_groups extension.
- Do not silently retry — investigate whether the connection was tampered with.
Example fix
// before
config.CurvePreferences = []tls.CurveID{tls.CurveP521} // server only offers P-256
// after: include a curve the server actually supports
config.CurvePreferences = []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP521} Defensive patterns
Strategy: validation
Validate before calling
// Ensure the server's likely curve is in your offered set before relying on it.
offered := map[tls.CurveID]bool{}
for _, c := range config.CurvePreferences {
offered[c] = true
}
// After connection, verify the negotiated curve was offered:
// if !offered[conn.ConnectionState().Curve] { /* should never happen */ } Type guard
// Confirm the intersection of local preferences and known-good curves is non-empty.
func hasOfferedCurve(prefs []tls.CurveID, want tls.CurveID) bool {
for _, c := range prefs {
if c == want { return true }
}
return false
} Try / catch
// if err != nil && strings.Contains(err.Error(), "unoffered curve") {
// log.Printf("server chose curve %d not in client prefs; investigate tampering", chosen)
// } Prevention
- Keep CurvePreferences non-empty and intersected with server capabilities.
- Avoid restricting to a single rare curve.
- Audit after upgrades that change default CurvePreferences.
When it happens
Trigger: After parsing the ServerKeyExchange, ka.curveID holds the server's chosen curve. slices.Contains(clientHello.supportedCurves, ka.curveID) returns false. Causes: a server bug mapping the wrong curve id, a MITM rewriting the ServerKeyExchange curve field, or a non-conformant server that ignores the client's supported_groups list.
Common situations: Interoperability testing against a server that hard-codes a curve (e.g. always P-521) the client did not advertise; a proxy/load-balancer rewriting TLS parameters; an attacker performing a downgrade; mismatches after a library upgrade that changed the default CurvePreferences.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: invalid server key share
- tls: invalid signature by the server certificate: {err}
- tls: invalid server finished hash
- tls: client using inappropriate protocol fallback
- tls: no key exchanges supported by both client and server
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/ba36a901fb5aee78.
Report an issue: GitHub.