golang/go · error

tls: server sent an unnecessary HelloRetryRequest message

Error message

tls: server sent an unnecessary HelloRetryRequest message

What it means

Thrown during HelloRetryRequest processing when the HRR contains neither a selected_group (in key_share) nor a cookie. RFC 8446 section 4.1.4 requires that a HelloRetryRequest must result in a change to the ClientHello — an HRR with no actionable content is unnecessary and invalid.

Source

Thrown at src/crypto/tls/handshake_client_tls13.go:297

				c.echAccepted = true
			}
		}

		if err := transcriptMsg(hs.serverHello, hs.echContext.innerTranscript); err != nil {
			return err
		}
	} else if hs.serverHello.encryptedClientHello != nil {
		// Unsolicited ECH extension should be rejected
		c.sendAlert(alertUnsupportedExtension)
		return errors.New("tls: unexpected encrypted client hello extension in serverHello")
	}

	// The only HelloRetryRequest extensions we support are key_share and
	// cookie, and clients must abort the handshake if the HRR would not result
	// in any change in the ClientHello.
	if hs.serverHello.selectedGroup == 0 && hs.serverHello.cookie == nil {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server sent an unnecessary HelloRetryRequest message")
	}

	if hs.serverHello.cookie != nil {
		hello.cookie = hs.serverHello.cookie
	}

	if hs.serverHello.serverShare.group != 0 {
		c.sendAlert(alertDecodeError)
		return errors.New("tls: received malformed key_share extension")
	}

	// If the server sent a key_share extension selecting a group, ensure it's
	// a group we advertised but did not send a key share for, and send a key
	// share for it this time.
	if curveID := hs.serverHello.selectedGroup; curveID != 0 {
		if !slices.Contains(hello.supportedCurves, curveID) {
			c.sendAlert(alertIllegalParameter)
			return errors.New("tls: server selected unsupported group")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Server must include either a key_share selected_group or a cookie in the HelloRetryRequest.
  2. Report as a server-side protocol violation per RFC 8446 section 4.1.4.
  3. If the server cannot be fixed, restrict client to TLS 1.2 to avoid HRR entirely.
  4. Capture the handshake with Wireshark to confirm the HRR contents.
Defensive patterns

Strategy: try-catch

Try / catch

conn, err := tls.Dial("tcp", addr, config)
if err != nil {
    if strings.Contains(err.Error(), "unnecessary HelloRetryRequest") {
        // Server sends pointless HRR — fall back to TLS 1.2
        config.MaxVersion = tls.VersionTLS12
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered when both hs.serverHello.selectedGroup == 0 and hs.serverHello.cookie == nil. The client sends alertIllegalParameter. The HRR would not cause any change in the next ClientHello.

Common situations: Server bug sending a pointless HelloRetryRequest with no key_share or cookie extensions. Server using HRR for hello random manipulation without including required actionable extensions. Non-compliant TLS 1.3 server implementation. Server sending HRR to work around a bug in its own state machine.

Understand the failure class

Related errors


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