golang/go · critical

tls: incorrect renegotiation extension contents

Error message

tls: incorrect renegotiation extension contents

What it means

On a renegotiation (c.handshakes > 0) where secure renegotiation was previously established, RFC 5746 requires the server's renegotiation_info to equal clientFinished[0:12] concatenated with serverFinished[12:24] from the prior handshake. The code constructs expectedSecureRenegotiation and compares with bytes.Equal; a mismatch indicates tampering or a broken channel and is aborted with alertHandshakeFailure.

Source

Thrown at src/crypto/tls/handshake_client.go:920

	if !supportsPointFormat && offeredNonCompressedFormat {
		return false, errors.New("tls: server offered only incompatible point formats")
	}

	if c.handshakes == 0 && hs.serverHello.secureRenegotiationSupported {
		c.secureRenegotiation = true
		if len(hs.serverHello.secureRenegotiation) != 0 {
			c.sendAlert(alertHandshakeFailure)
			return false, errors.New("tls: initial handshake had non-empty renegotiation extension")
		}
	}

	if c.handshakes > 0 && c.secureRenegotiation {
		var expectedSecureRenegotiation [24]byte
		copy(expectedSecureRenegotiation[:], c.clientFinished[:])
		copy(expectedSecureRenegotiation[12:], c.serverFinished[:])
		if !bytes.Equal(hs.serverHello.secureRenegotiation, expectedSecureRenegotiation[:]) {
			c.sendAlert(alertHandshakeFailure)
			return false, errors.New("tls: incorrect renegotiation extension contents")
		}
	}

	if err := checkALPN(hs.hello.alpnProtocols, hs.serverHello.alpnProtocol, false); err != nil {
		c.sendAlert(alertUnsupportedExtension)
		return false, err
	}
	c.clientProtocol = hs.serverHello.alpnProtocol

	c.scts = hs.serverHello.scts

	if !hs.serverResumedSession() {
		return false, nil
	}

	if hs.session.version != c.vers {
		c.sendAlert(alertHandshakeFailure)
		return false, errors.New("tls: server resumed a session with a different version")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Move to TLS 1.3 which has no renegotiation and removes this attack surface (MinVersion = VersionTLS13).
  2. Remove any TLS-intercepting appliance from the path of renegotiating connections.
  3. If renegotiation is required, ensure the path is transparent.

Example fix

// before: permissive renegotiation policy
cfg := &tls.Config{Renegotiation: tls.RenegotiateFreelyAsClient}
// after: TLS 1.3 eliminates renegotiation entirely
cfg := &tls.Config{MinVersion: tls.VersionTLS13}
Defensive patterns

Strategy: validation

Validate before calling

// Eliminate the renegotiation attack surface entirely.
func disableRenegotiation(cfg *tls.Config) {
    cfg.MinVersion = tls.VersionTLS13
}

Type guard

func isIncorrectRenegotiationContents(err error) bool {
    return err != nil && strings.Contains(err.Error(), "incorrect renegotiation extension contents")
}

Try / catch

if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    if isIncorrectRenegotiationContents(err) {
        // Likely interception on a renegotiating connection; do not retry silently.
        security.ReportInterception(addr, err)
    }
}

Prevention

When it happens

Trigger: MitM attempting to splice or synchronize renegotiation state; buggy middlebox that corrupts renegotiation_info; certificate rotation that breaks state continuity across renegotiations.

Common situations: TLS interception appliances on long-lived renegotiating connections; rarely legitimate servers.

Understand the failure class

Related errors


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