hyperledger/fabric · error
client claimed TLS hash doesn't match computed TLS hash from
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.
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)
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
- failed connecting to %s: %v
- access denied
- unknown or missing request type
- access denied, client identity wasn't supplied
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/e09816b03d3fb122.
Report an issue: GitHub.