{"record":{"id":"44da542d57189ca7","repo":"hyperledger/fabric","slug":"claimed-tls-cert-hash-is-v-but-actual-tls-cert-ha","errorCode":null,"errorMessage":"claimed TLS cert hash is %v but actual TLS cert hash is %v","messagePattern":"claimed TLS cert hash is (.+?) but actual TLS cert hash is (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/deliver/binding.go","lineNumber":59,"sourceCode":"\t}\n}\n\n// mutualTLSBinding enforces the client to send its TLS cert hash in the message,\n// and then compares it to the computed hash that is derived\n// from the gRPC context.\n// In case they don't match, or the cert hash is missing from the request or\n// there is no TLS certificate to be excavated from the gRPC context,\n// an error is returned.\nfunc mutualTLSBinding(ctx context.Context, claimedTLScertHash []byte) error {\n\tif len(claimedTLScertHash) == 0 {\n\t\treturn errors.Errorf(\"client didn't include its TLS cert hash\")\n\t}\n\tactualTLScertHash := util.ExtractCertificateHashFromContext(ctx)\n\tif len(actualTLScertHash) == 0 {\n\t\treturn errors.Errorf(\"client didn't send a TLS certificate\")\n\t}\n\tif !bytes.Equal(actualTLScertHash, claimedTLScertHash) {\n\t\treturn errors.Errorf(\"claimed TLS cert hash is %v but actual TLS cert hash is %v\", claimedTLScertHash, actualTLScertHash)\n\t}\n\treturn nil\n}\n\n// noopBinding is a BindingInspector that always returns nil\nfunc noopBinding(_ context.Context, _ []byte) error {\n\treturn nil\n}\n","sourceCodeStart":41,"sourceCodeEnd":68,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/common/deliver/binding.go#L41-L68","documentation":"The claimed TLS cert hash in the message does not match the hash of the actual TLS certificate on the gRPC connection. This is the core anti-replay binding check: it proves the signed message came over this specific TLS channel.","triggerScenarios":"A client sends a cert hash computed from one certificate while connecting with another (stale cached hash, cert rotated mid-session, or a forged/misrouted request).","commonSituations":"Certificate rotation without refreshing the client-side cached hash; multiple TLS certs on disk and the client hashes the wrong one; connection reused across identity switches.","solutions":["Recompute the TLS cert hash from the certificate actually presented on the current connection and resend","Restart/reconnect the client after certificate rotation so the hash matches the new cert","Verify the client hashes the same cert file configured for the TLS handshake"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"actualHash := util.ExtractCertificateHashFromContext(ctx)\nif !bytes.Equal(actualHash, claimedTLScertHash) {\n    return errors.New(\"stale TLS cert hash: reconnect and recompute\")\n}","typeGuard":null,"tryCatchPattern":"if err := inspector(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"claimed TLS cert hash is\") {\n        return reconnectAndResend() // hash mismatch: rebind to current TLS channel\n    }\n    return err\n}","preventionTips":["Recompute the cert hash after any certificate rotation","Hash exactly the cert configured in the TLS handshake, not another file on disk","Avoid reusing sessions across identity/certificate switches"],"tags":["grpc","tls","mutual-tls","binding-mismatch"],"backgroundTag":"tls-cert-hash-mismatch","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}