{"record":{"id":"48b1f8274e6c294a","repo":"hyperledger/fabric","slug":"client-didn-t-include-its-tls-cert-hash","errorCode":null,"errorMessage":"client didn't include its TLS cert hash","messagePattern":"client didn't include its TLS cert hash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/deliver/binding.go","lineNumber":52,"sourceCode":"\t\tinspectMessage = noopBinding\n\t}\n\treturn func(ctx context.Context, msg proto.Message) error {\n\t\tif msg == nil {\n\t\t\treturn errors.New(\"message is nil\")\n\t\t}\n\t\treturn inspectMessage(ctx, extractTLSCertHash(msg))\n\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":34,"sourceCodeEnd":68,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/common/deliver/binding.go#L34-L68","documentation":"mutualTLSBinding checks that the client message carries a TLS cert hash field. An empty claimedTLScertHash means the client did not populate the cert_hash in its request, which is required to bind the message to its TLS channel.","triggerScenarios":"Deliver or broadcast requests sent with mutual TLS enabled but without the TLS cert hash set in the message (e.g., Envelope with missing cert_hash in ChannelHeader-related binding).","commonSituations":"Older SDKs or CLI tools that don't compute/populate the TLS cert hash; clients built without TLS binding support connecting to an orderer with mutualTLS binding enforced.","solutions":["Update the client SDK/CLI to a version that sets the TLS cert hash in requests","Ensure the client connects over TLS with a client certificate so a hash can be computed","If binding is not needed, configure the server to disable mutual TLS binding (mutualTLS=false)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if len(msg.TLSCertHash) == 0 {\n    return errors.New(\"client must set TLSCertHash when mutual TLS binding is enabled\")\n}","typeGuard":null,"tryCatchPattern":"if err := inspector(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"didn't include its TLS cert hash\") {\n        return status.Error(codes.Unauthenticated, \"update client SDK to set TLS cert hash\")\n    }\n    return err\n}","preventionTips":["Use an SDK version that populates the TLS cert hash","Enable mutual TLS on the client so a hash can always be computed","Only disable mutualTLS binding when the deployment explicitly doesn't need replay protection"],"tags":["grpc","tls","mutual-tls","binding"],"backgroundTag":"missing-tls-cert-hash","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"}