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

  1. Recompute ClientTlsCertHash from the exact certificate the gRPC connection presents (SHA-256 over the DER-encoded leaf certificate) right before sending
  2. Rebuild the discovery request per connection instead of caching it when certificates rotate
  3. Log and compare the two hex hashes (the server warning shows both) to find which side is stale
  4. 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

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

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/e09816b03d3fb122. Report an issue: GitHub.