hyperledger/fabric · error
client claimed TLS hash doesn't match computed TLS hash…
Error message
client claimed TLS hash doesn't match computed TLS hash from gRPC stream
What it means
The client sent a TLS certificate and declared a ClientTlsCertHash in the request Authentication, but the hash computed from the actual certificate on the gRPC stream does not match the claimed hash. The server logs a warning with both hex-encoded hashes and rejects the request, guarding against identity/certificate spoofing.
Solutions
- Recompute ClientTlsCertHash from the exact certificate the gRPC connection presents (SHA-256 over the DER-encoded leaf certificate) right before sending
- Rebuild the discovery request per connection instead of caching it when certificates rotate
- Log and compare the two hex hashes (the server warning shows both) to find which side is stale
- Use an SDK helper that sets ClientTlsCertHash automatically from the TLS config
Example fix
// before req.Authentication.ClientTlsCertHash = cachedHash // stale after cert rotation // after der, _ := x509.MarshalCertificate(leafCert) // DER bytes the conn presents sum := sha256.Sum256(der) req.Authentication.ClientTlsCertHash = sum[:]
Defensive patterns
Strategy: validation
Validate before calling
func computeCertHash(cert *x509.Certificate) []byte {
sum := sha256.Sum256(cert.Raw)
return sum[:]
}
// set req.Authentication.ClientTlsCertHash = computeCertHash(leafOfActiveConn) right before each send Type guard
func hashMatches(conn *grpc.ClientConn, claimed []byte) bool {
state := conn.GetCredentials() // or compute from the conn's TLS state
return state != nil && bytes.Equal(computeCertHash(state.LeafCert), claimed)
} Try / catch
resp, err := client.Send(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "claimed TLS hash doesn't match") {
req.Authentication.ClientTlsCertHash = computeCertHash(currentConnLeafCert())
return client.Send(ctx, req) // rebuild and retry once
}
return err
} Prevention
- Always compute the hash over the DER (cert.Raw) of the leaf certificate with SHA-256
- Rebuild discovery requests after any TLS certificate rotation; never cache AuthInfo across connections
- Generate the hash from the same tls.Config used to dial, not from a different cert file
- Compare server-side warning log hashes with local values when debugging
When it happens
Trigger: Calling Discover or TestValidateStructure where req.Authentication.ClientTlsCertHash differs from bytes computed by certHashFromContext over the stream's TLS certificate.
Common situations: Client computed the hash with a different algorithm or over the wrong DER encoding; client reconnected with a rotated/renewed certificate but reused an old hash; the request was built once and reused across connections with different certs; hash of an intermediate cert instead of the leaf.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client didn't send a TLS certificate
- access denied
- access denied, client identity wasn't supplied
- chaincode interest is nil
- chaincode query must have at least one chaincode interest
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/e09816b03d3fb122.
Report an issue: GitHub.
Appendix: source
Thrown at discovery/service.go:257
return nil, errors.Wrap(err, "failed parsing request")
}
if req.Authentication == nil {
return nil, errors.New("access denied, no authentication info in request")
}
if len(req.Authentication.ClientIdentity) == 0 {
return nil, errors.New("access denied, client identity wasn't supplied")
}
if !tlsEnabled {
return req, nil
}
computedHash := certHashFromContext(ctx)
if len(computedHash) == 0 {
return nil, errors.New("client didn't send a TLS certificate")
}
if !bytes.Equal(computedHash, req.Authentication.ClientTlsCertHash) {
claimed := hex.EncodeToString(req.Authentication.ClientTlsCertHash)
logger.Warningf("client claimed TLS hash %s doesn't match computed TLS hash from gRPC stream %s", claimed, hex.EncodeToString(computedHash))
return nil, errors.New("client claimed TLS hash doesn't match computed TLS hash from gRPC stream")
}
return req, nil
}
func validateCCQuery(ccQuery *discovery.ChaincodeQuery) error {
if len(ccQuery.Interests) == 0 {
return errors.New("chaincode query must have at least one chaincode interest")
}
for _, interest := range ccQuery.Interests {
if interest == nil {
return errors.New("chaincode interest is nil")
}
if len(interest.Chaincodes) == 0 {
return errors.New("chaincode interest must contain at least one chaincode")
}
for _, cc := range interest.Chaincodes {
if cc.Name == "" {
return errors.New("chaincode name in interest cannot be empty")View on GitHub (pinned to 2736b63f8f)