{"record":{"id":"48745b53510a60bf","repo":"golang/go","slug":"tls-invalid-psk-binder","errorCode":null,"errorMessage":"tls: invalid PSK binder","messagePattern":"tls: invalid PSK binder","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_server_tls13.go","lineNumber":406,"sourceCode":"\n\t\ths.earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, sessionState.secret)\n\t\tbinderKey := hs.earlySecret.ResumptionBinderKey()\n\t\t// Clone the transcript in case a HelloRetryRequest was recorded.\n\t\ttranscript := cloneHash(hs.transcript, hs.suite.hash)\n\t\tif transcript == nil {\n\t\t\tc.sendAlert(alertInternalError)\n\t\t\treturn errors.New(\"tls: internal error: failed to clone hash\")\n\t\t}\n\t\tclientHelloBytes, err := hs.clientHello.marshalWithoutBinders()\n\t\tif err != nil {\n\t\t\tc.sendAlert(alertInternalError)\n\t\t\treturn err\n\t\t}\n\t\ttranscript.Write(clientHelloBytes)\n\t\tpskBinder := hs.suite.finishedHash(binderKey, transcript)\n\t\tif !hmac.Equal(hs.clientHello.pskBinders[i], pskBinder) {\n\t\t\tc.sendAlert(alertDecryptError)\n\t\t\treturn errors.New(\"tls: invalid PSK binder\")\n\t\t}\n\n\t\tif c.quic != nil && hs.clientHello.earlyData && i == 0 &&\n\t\t\tsessionState.EarlyData && sessionState.cipherSuite == hs.suite.id &&\n\t\t\tsessionState.alpnProtocol == c.clientProtocol {\n\t\t\ths.earlyData = true\n\n\t\t\ttranscript := hs.suite.hash.New()\n\t\t\tif err := transcriptMsg(hs.clientHello, transcript); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tearlyTrafficSecret := hs.earlySecret.ClientEarlyTrafficSecret(transcript)\n\t\t\tif err := c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tc.didResume = true","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_server_tls13.go#L388-L424","documentation":"The PSK binder is an HMAC over the ClientHello transcript with a key derived from the PSK (RFC 8446 §4.4.2). If it doesn't match the server's recomputation, the offered PSK identity is wrong or the transcript was tampered with; the server sends decrypt_error.","triggerScenarios":"Client offers a session ticket whose binder fails HMAC verification: stale/expired ticket, ticket from a server with different ticket keys, corrupted ticket, or a transcript modified in flight.","commonSituations":"Server pool with mismatched session ticket keys; ticket-key rotation without overlap; client reusing a cached ticket long after issuance; middleboxes modifying the ClientHello bytes.","solutions":["Ensure all servers in the pool share the same session ticket key (tls.Config.SessionTicketKey, or SetSessionTicketKeys for rotation)","During ticket-key rotation, keep the old key for an overlap window so in-flight tickets still validate","Have the client request a fresh ticket when resumption fails (fall back to a full handshake)"],"exampleFix":"// Shared ticket key across pool (all servers must agree)\nvar key [32]byte\n// ... populate key securely, distribute to every server ...\ncfg.SessionTicketKey = key\n\n// For rotation, use SetSessionTicketKeys with old+new during the overlap:\ncfg.SetSessionTicketKeys([][32]byte{newKey, oldKey})","handlingStrategy":"validation","validationCode":"// Ensure every server in the pool uses the same ticket key.\nvar ticketKey [32]byte\n// ... distribute ticketKey to all servers via a secure channel ...\ncfg.SessionTicketKey = ticketKey","typeGuard":null,"tryCatchPattern":"// On the server this is a peer/ticket error; let the client fall back to a full handshake.\nif err := tlsConn.Handshake(); err != nil {\n    if strings.Contains(err.Error(), \"invalid PSK binder\") {\n        // Common during ticket-key rotation; usually transient.\n        log.Printf(\"PSK binder mismatch from %v (ticket-key rotation?)\", remote)\n    }\n    c.Close()\n    return\n}","preventionTips":["Share session ticket keys across all servers in a pool","During rotation, call SetSessionTicketKeys with both old and new keys for an overlap window","Clients should request a fresh ticket when resumption fails"],"tags":["tls","go","psk","session-ticket","resumption","key-rotation","handshake"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}