{"record":{"id":"6f92a91615eb4661","repo":"hyperledger/fabric","slug":"client-didn-t-send-a-tls-certificate-6f92a9","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":"discovery/service.go","lineNumber":252,"sourceCode":"\tif request == nil {\n\t\treturn nil, errors.New(\"nil request\")\n\t}\n\treq, err := protoext.SignedRequestToRequest(request)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed parsing request\")\n\t}\n\tif req.Authentication == nil {\n\t\treturn nil, errors.New(\"access denied, no authentication info in request\")\n\t}\n\tif len(req.Authentication.ClientIdentity) == 0 {\n\t\treturn nil, errors.New(\"access denied, client identity wasn't supplied\")\n\t}\n\tif !tlsEnabled {\n\t\treturn req, nil\n\t}\n\tcomputedHash := certHashFromContext(ctx)\n\tif len(computedHash) == 0 {\n\t\treturn nil, errors.New(\"client didn't send a TLS certificate\")\n\t}\n\tif !bytes.Equal(computedHash, req.Authentication.ClientTlsCertHash) {\n\t\tclaimed := hex.EncodeToString(req.Authentication.ClientTlsCertHash)\n\t\tlogger.Warningf(\"client claimed TLS hash %s doesn't match computed TLS hash from gRPC stream %s\", claimed, hex.EncodeToString(computedHash))\n\t\treturn nil, errors.New(\"client claimed TLS hash doesn't match computed TLS hash from gRPC stream\")\n\t}\n\treturn req, nil\n}\n\nfunc validateCCQuery(ccQuery *discovery.ChaincodeQuery) error {\n\tif len(ccQuery.Interests) == 0 {\n\t\treturn errors.New(\"chaincode query must have at least one chaincode interest\")\n\t}\n\tfor _, interest := range ccQuery.Interests {\n\t\tif interest == nil {\n\t\t\treturn errors.New(\"chaincode interest is nil\")\n\t\t}\n\t\tif len(interest.Chaincodes) == 0 {","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/discovery/service.go#L234-L270","documentation":"The discovery service has TLS enabled and extracts the TLS certificate hash from the incoming gRPC stream context via certHashFromContext. An empty result means the client did not present any TLS certificate, so the server cannot verify the client's claimed ClientTlsCertHash. This is a mutual-TLS requirement failure.","triggerScenarios":"Calling Discover or TestValidateStructure with TLS enabled on the server while the client connects without a client certificate (no mTLS), leaving certHashFromContext(ctx) empty.","commonSituations":"Client configured with one-way TLS only while the peer/discovery server requires mutual TLS; TLS credentials omitted from the dial options; a proxy/load balancer stripping the client cert; mismatch between client and server TLS settings after config change.","solutions":["Configure the client gRPC connection with TLS credentials that include the client certificate (grpc.WithTransportCredentials + credentials.NewTLS with Certificates set)","Verify the client certificate chain is valid and actually sent (check with openssl s_client)","Ensure any proxy between client and server forwards the client certificate","Alternatively disable mutual TLS requirement on the server if your security model permits"],"exampleFix":"// before\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))\n// after\ntlsCfg := &tls.Config{Certificates: []tls.Certificate{clientCert}, RootCAs: caPool}\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))","handlingStrategy":"validation","validationCode":"func ensureClientCert(tlsCfg *tls.Config) error {\n    if tlsCfg == nil || len(tlsCfg.Certificates) == 0 {\n        return errors.New(\"mutual TLS required: client certificate missing from TLS config\")\n    }\n    return nil\n}","typeGuard":"func hasClientCertificate(cfg *tls.Config) bool {\n    return cfg != nil && len(cfg.Certificates) > 0\n}","tryCatchPattern":"resp, err := client.Send(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"client didn't send a TLS certificate\") {\n        return fmt.Errorf(\"server requires mTLS; reconfigure dial options with client cert: %w\", err)\n    }\n    return err\n}","preventionTips":["Match client TLS mode to server policy (mTLS enabled on both ends)","Include the client certificate in grpc.WithTransportCredentials, not just CA roots","Check for proxies/load balancers that terminate or strip TLS","Verify with openssl s_client -cert that the cert is transmitted"],"tags":["tls","mtls","grpc","discovery","fabric"],"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"}