golang/go · error

tls: server's identity changed during renegotiation

Error message

tls: server's identity changed during renegotiation

What it means

During TLS renegotiation (TLS 1.2 and earlier; TLS 1.3 has none), Go requires the server's leaf certificate to be byte-identical (c.peerCertificates[0].Raw) to the one from the original handshake. This defeats 3SHAKE-style synchronization attacks (see mitls.org/pages/attacks/3SHAKE in the source comment). A different leaf on renegotiation is treated as an attack or a serious server-side inconsistency.

Source

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

		}
	}

	if c.handshakes == 0 {
		// If this is the first handshake on a connection, process and
		// (optionally) verify the server's certificates.
		if err := c.verifyServerCertificate(certMsg.certificates); err != nil {
			return err
		}
	} else {
		// This is a renegotiation handshake. We require that the
		// server's identity (i.e. leaf certificate) is unchanged and
		// thus any previous trust decision is still valid.
		//
		// See https://mitls.org/pages/attacks/3SHAKE for the
		// motivation behind this requirement.
		if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
			c.sendAlert(alertBadCertificate)
			return errors.New("tls: server's identity changed during renegotiation")
		}
	}

	keyAgreement := hs.suite.ka(c.vers)

	skx, ok := msg.(*serverKeyExchangeMsg)
	if ok {
		err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
		if err != nil {
			c.sendAlert(alertIllegalParameter)
			return err
		}
		if keyAgreement, ok := keyAgreement.(*ecdheKeyAgreement); ok {
			c.curveID = keyAgreement.curveID
			c.peerSigAlg = keyAgreement.signatureAlgorithm
		}

		msg, err = c.readHandshake(&hs.finishedHash)

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Prefer TLS 1.3 (no renegotiation) by setting MinVersion = VersionTLS13.
  2. Ensure every node in the server pool serves the identical leaf certificate.
  3. Coordinate certificate rotation so it does not span active renegotiating connections.
  4. Reduce connection lifetime below the rotation interval.

Example fix

// before: allows renegotiation on long-lived TLS 1.2 conn
cfg := &tls.Config{Renegotiation: tls.RenegotiateFreelyAsClient}
// after: move to TLS 1.3 where renegotiation does not exist
cfg := &tls.Config{MinVersion: tls.VersionTLS13}
Defensive patterns

Strategy: validation

Validate before calling

// Avoid renegotiation entirely by requiring TLS 1.3.
func avoidRenegotiation(cfg *tls.Config) {
    cfg.MinVersion = tls.VersionTLS13
}

Type guard

func isIdentityChangedRenegotiation(err error) bool {
    return err != nil && strings.Contains(err.Error(), "server's identity changed during renegotiation")
}

Try / catch

if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    if isIdentityChangedRenegotiation(err) {
        // The server cluster is serving inconsistent certs; cannot safely retry.
        reportClusterInconsistency(addr, err)
    }
}

Prevention

When it happens

Trigger: Server rotated its certificate between the original handshake and a renegotiation; server pool behind a load balancer serving different leaf certs per node; renegotiation triggered by IIS demanding a client certificate mid-connection.

Common situations: Long-lived connections spanning a certificate renewal window; clusters with non-uniform certificates; Windows IIS renegotiation-for-client-cert flows.

Understand the failure class

Related errors


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