{"record":{"id":"0c500fe8ff3ebafb","repo":"hyperledger/fabric","slug":"client-didn-t-send-a-tls-certificate","errorCode":null,"errorMessage":"client didn't send a TLS certificate","messagePattern":"client didn't send a TLS certificate","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/deliver/binding.go","lineNumber":56,"sourceCode":"\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":38,"sourceCodeEnd":68,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/common/deliver/binding.go#L38-L68","documentation":"mutualTLSBinding extracts the actual TLS certificate hash from the gRPC context (util.ExtractCertificateHashFromContext). An empty result means no TLS certificate is present in the connection, so binding cannot be verified.","triggerScenarios":"Request arrives on a connection without a client TLS certificate (non-TLS connection, or TLS without client auth) while mutual TLS binding is enforced.","commonSituations":"Client connecting over plaintext to an orderer expecting TLS; client TLS config missing CertFile/KeyFile (no mutual TLS); a proxy/LB terminating TLS and dropping client certs.","solutions":["Configure the client with a client keypair (keyFile/certFile) for mutual TLS","Ensure the server has client auth required (ClientAuth: tls.RequireAndVerifyClientCert) and no proxy strips the cert","Verify the client actually connects via https/grpcs, not plain http/grpc"],"exampleFix":"// before\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))\n// after\ntlsCfg := &tls.Config{Certificates: []tls.Certificate{clientCert}}\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))","handlingStrategy":"validation","validationCode":"func hasClientTLSContext(ctx context.Context) bool {\n    _, ok := credentials.FromContext(ctx).AuthType(), false\n    _ = ok\n    return util.ExtractCertificateHashFromContext(ctx) != nil\n}","typeGuard":null,"tryCatchPattern":"if err := inspector(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"didn't send a TLS certificate\") {\n        return status.Error(codes.Unauthenticated, \"client must connect with mutual TLS\")\n    }\n    return err\n}","preventionTips":["Require client certificates on the server (RequireAndVerifyClientCert)","Always configure client keypair when connecting to binding-enforced services","Check proxy/TLS-terminator config so client certs reach the backend"],"tags":["grpc","tls","mutual-tls","client-certificate"],"backgroundTag":"missing-tls-certificate","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"}