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
- Check for and bypass any TLS-intercepting middleware/appliance in the path.
- Verify cipher suite and version compatibility with `openssl s_client -connect host:443`.
- Remove any GODEBUG TLS flags (e.g. tlsrsakex, tls3des) that force incompatible negotiation.
- 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
- Audit the path for TLS-intercepting appliances.
- Remove GODEBUG TLS flags that force weak/incompatible negotiation.
- Verify cipher/version compatibility with openssl s_client.
- Treat Finished failures as potential security incidents.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: invalid server finished hash
- tls: invalid outer extensions
- tls: malformed encrypted_client_hello extension
- tls: downgrade attempt detected, possibly due to a MitM atta
- tls: server echoed TLS 1.3 compatibility session ID in TLS 1
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/0c47078cd7e74586.
Report an issue: GitHub.