golang/go · critical

tls: server's Finished message was incorrect

Error message

tls: server's Finished message was incorrect

What it means

The Finished message is the integrity capstone of the handshake: verify_data is an HMAC over the entire transcript keyed by the master secret. The client computes verify := hs.finishedHash.serverSum(hs.masterSecret) and compares in constant time against serverFinished.verifyData. A length mismatch or ConstantTimeCompare != 1 means the master secret differs or the transcript was tampered with — the handshake is aborted with alertHandshakeFailure.

Source

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

	// finishedMsg is included in the transcript, but not until after we
	// check the client version, since the state before this message was
	// sent is used during verification.
	msg, err := c.readHandshake(nil)
	if err != nil {
		return err
	}
	serverFinished, ok := msg.(*finishedMsg)
	if !ok {
		c.sendAlert(alertUnexpectedMessage)
		return unexpectedMessageError(serverFinished, msg)
	}

	verify := hs.finishedHash.serverSum(hs.masterSecret)
	if len(verify) != len(serverFinished.verifyData) ||
		subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 {
		c.sendAlert(alertHandshakeFailure)
		return errors.New("tls: server's Finished message was incorrect")
	}

	if err := transcriptMsg(serverFinished, &hs.finishedHash); err != nil {
		return err
	}

	copy(out, verify)
	return nil
}

func (hs *clientHandshakeState) readSessionTicket() error {
	if !hs.serverHello.ticketSupported {
		return nil
	}
	c := hs.c

	if !hs.hello.ticketSupported {
		c.sendAlert(alertIllegalParameter)

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Check for and bypass any TLS-intercepting middleware/appliance in the path.
  2. Verify cipher suite and version compatibility with `openssl s_client -connect host:443`.
  3. Remove any GODEBUG TLS flags (e.g. tlsrsakex, tls3des) that force incompatible negotiation.
  4. Confirm the FIPS/non-FIPS build matches on both sides if one is custom.
Defensive patterns

Strategy: try-catch

Validate before calling

// You cannot pre-validate the Finished MAC without performing the handshake.
// Best prevention: ensure a clean, non-intercepted path and compatible crypto policy.
func cleanTLSConfig() *tls.Config {
    return &tls.Config{MinVersion: tls.VersionTLS12} // no GODEBUG-forced weak suites
}

Type guard

func isFinishedIncorrect(err error) bool {
    return err != nil && strings.Contains(err.Error(), "server's Finished message was incorrect")
}

Try / catch

if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    if isFinishedIncorrect(err) {
        // Likely interception or incompatible crypto; investigate, do not blindly retry.
        security.ReportHandshakeFailure(addr, err)
    }
}

Prevention

When it happens

Trigger: A MitM that cannot compute the correct master secret; corrupted handshake packets; incompatible cipher/version computation between endpoints; a middlebox re-encrypting with the wrong keys; buggy crypto path (FIPS / GODEBUG forcing divergent behavior).

Common situations: TLS interception appliance whose CA is not actually being used (so it cannot derive the right keys); faulty middlebox; rare for genuine compliant servers; client/server version skew under unusual GODEBUG TLS flags.

Understand the failure class

Related errors


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