golang/go · error

tls: initial handshake had non-empty renegotiation extension

Error message

tls: initial handshake had non-empty renegotiation extension

What it means

On an initial (non-renegotiation) handshake, the client sent a non-empty secure_renegotiation extension data field. RFC 5746 requires that field to be empty on the first handshake; only renegotiation handshakes carry the previous verify_data. Sending renegotiation data on the initial handshake is a handshake_failure.

Source

Thrown at src/crypto/tls/handshake_server.go:255

	// Downgrade protection canaries. See RFC 8446, Section 4.1.3.
	maxVers := c.config.maxSupportedVersion(roleServer, c.quic != nil)
	if maxVers >= VersionTLS12 && c.vers < maxVers || testingOnlyForceDowngradeCanary {
		if c.vers == VersionTLS12 {
			copy(serverRandom[24:], downgradeCanaryTLS12)
		} else {
			copy(serverRandom[24:], downgradeCanaryTLS11)
		}
		serverRandom = serverRandom[:24]
	}
	_, err := io.ReadFull(c.config.rand(), serverRandom)
	if err != nil {
		c.sendAlert(alertInternalError)
		return err
	}

	if len(hs.clientHello.secureRenegotiation) != 0 {
		c.sendAlert(alertHandshakeFailure)
		return errors.New("tls: initial handshake had non-empty renegotiation extension")
	}

	hs.hello.extendedMasterSecret = hs.clientHello.extendedMasterSecret
	hs.hello.secureRenegotiationSupported = hs.clientHello.secureRenegotiationSupported
	hs.hello.compressionMethod = compressionNone
	if len(hs.clientHello.serverName) > 0 {
		c.serverName = hs.clientHello.serverName
	}

	selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, false)
	if err != nil {
		c.sendAlert(alertNoApplicationProtocol)
		return err
	}
	hs.hello.alpnProtocol = selectedProto
	c.clientProtocol = selectedProto

	hs.cert, err = c.config.getCertificate(clientHelloInfo(hs.ctx, c, hs.clientHello))

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Use a compliant client library — RFC 5746 mandates an empty renegotiation_info on the initial handshake.
  2. Restart the client's TLS state (clear cached sessions/renegotiation context) and reconnect.
  3. If the client is your own, audit how renegotiation_info is populated before the first handshake completes.
  4. Treat persistent occurrences as a sign of a tampered or malicious client.
Defensive patterns

Strategy: validation

Validate before calling

// Client: ensure renegotiation_info is empty on the initial handshake.
// Standard libraries do this; if you maintain a custom stack, never seed
// renegotiation data before the first handshake completes.

Try / catch

// Server: log as a protocol anomaly.
if err != nil && strings.Contains(err.Error(), "non-empty renegotiation extension") {
    log.Warn("possible CVE-2009-3555 probe", "remote", conn.RemoteAddr())
}

Prevention

When it happens

Trigger: processClientHello checks len(hs.clientHello.secureRenegotiation) != 0 on the initial handshake and alerts. The peer transmitted a renegotiation_info extension with non-empty contents on its first ClientHello.

Common situations: A buggy client reusing renegotiation state across connections, a TLS stack that incorrectly seeds the renegotiation field, or a malicious peer probing for CVE-2009-3555 style renegotiation vulnerabilities.

Understand the failure class

Related errors


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