hyperledger/fabric · error

client didn't send a TLS certificate

Error message

client didn't send a TLS certificate

What it means

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.

Source

Thrown at common/deliver/binding.go:56

			return errors.New("message is nil")
		}
		return inspectMessage(ctx, extractTLSCertHash(msg))
	}
}

// mutualTLSBinding enforces the client to send its TLS cert hash in the message,
// and then compares it to the computed hash that is derived
// from the gRPC context.
// In case they don't match, or the cert hash is missing from the request or
// there is no TLS certificate to be excavated from the gRPC context,
// an error is returned.
func mutualTLSBinding(ctx context.Context, claimedTLScertHash []byte) error {
	if len(claimedTLScertHash) == 0 {
		return errors.Errorf("client didn't include its TLS cert hash")
	}
	actualTLScertHash := util.ExtractCertificateHashFromContext(ctx)
	if len(actualTLScertHash) == 0 {
		return errors.Errorf("client didn't send a TLS certificate")
	}
	if !bytes.Equal(actualTLScertHash, claimedTLScertHash) {
		return errors.Errorf("claimed TLS cert hash is %v but actual TLS cert hash is %v", claimedTLScertHash, actualTLScertHash)
	}
	return nil
}

// noopBinding is a BindingInspector that always returns nil
func noopBinding(_ context.Context, _ []byte) error {
	return nil
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Configure the client with a client keypair (keyFile/certFile) for mutual TLS
  2. Ensure the server has client auth required (ClientAuth: tls.RequireAndVerifyClientCert) and no proxy strips the cert
  3. Verify the client actually connects via https/grpcs, not plain http/grpc

Example fix

// before
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
// after
tlsCfg := &tls.Config{Certificates: []tls.Certificate{clientCert}}
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))
Defensive patterns

Strategy: validation

Validate before calling

func hasClientTLSContext(ctx context.Context) bool {
    _, ok := credentials.FromContext(ctx).AuthType(), false
    _ = ok
    return util.ExtractCertificateHashFromContext(ctx) != nil
}

Try / catch

if err := inspector(ctx, msg); err != nil {
    if strings.Contains(err.Error(), "didn't send a TLS certificate") {
        return status.Error(codes.Unauthenticated, "client must connect with mutual TLS")
    }
    return err
}

Prevention

When it happens

Trigger: Request arrives on a connection without a client TLS certificate (non-TLS connection, or TLS without client auth) while mutual TLS binding is enforced.

Common situations: 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.

Understand the failure class

Related errors


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