hyperledger/fabric · error

no TLS certificate sent

Error message

no TLS certificate sent

What it means

The orderer requires mutual TLS on cluster communication: the sender's TLS client certificate proves the sender's identity. util.ExtractRawCertificateFromContext(ctx) returned empty because the incoming gRPC request's context carried no client certificate, so the sender cannot be authenticated and the request is rejected.

Source

Thrown at orderer/common/cluster/comm.go:103

// requestContext identifies the sender and channel of the request and returns
// it wrapped in a requestContext
func (c *Comm) requestContext(ctx context.Context, msg proto.Message) (*requestContext, error) {
	channel := c.ChanExt.TargetChannel(msg)
	if channel == "" {
		return nil, errors.Errorf("badly formatted message, cannot extract channel")
	}

	c.Lock.RLock()
	mapping, exists := c.Chan2Members[channel]
	c.Lock.RUnlock()

	if !exists {
		return nil, errors.Errorf("channel %s doesn't exist", channel)
	}

	cert := util.ExtractRawCertificateFromContext(ctx)
	if len(cert) == 0 {
		return nil, errors.Errorf("no TLS certificate sent")
	}

	stub := mapping.LookupByClientCert(cert)
	if stub == nil {
		return nil, errors.Errorf("certificate extracted from TLS connection isn't authorized")
	}
	return &requestContext{
		channel: channel,
		sender:  stub.ID,
	}, nil
}

// Remote obtains a RemoteContext linked to the destination node on the context
// of a given channel
func (c *Comm) Remote(channel string, id uint64) (*RemoteContext, error) {
	c.Lock.RLock()
	defer c.Lock.RUnlock()

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Enable mutual TLS: set General.TLS.ClientAuthRequired: true in orderer.yaml and configure the client cert on the sender side
  2. Ensure the sender's gRPC dial includes its TLS client keypair (tls.Config{Certificates: ...})
  3. Check no proxy/load balancer terminates TLS between sender and orderer, dropping client certs
  4. Confirm General.TLS.Enabled is true on both ends and certs are signed by the same CA

Example fix

// before (sender dials without client cert)
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
// after
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: caPool}
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))
Defensive patterns

Strategy: validation

Validate before calling

// Sender-side: fail fast if mutual TLS is not fully configured
func requireMutualTLS(cfg *tls.Config) error {
    if cfg == nil || len(cfg.Certificates) == 0 {
        return errors.New("client certificate required for cluster communication")
    }
    if cfg.RootCAs == nil { return errors.New("server CA pool required") }
    return nil
}

Try / catch

if _, err := comm.Remote(channel, id); err != nil {
    if strings.Contains(err.Error(), "no TLS certificate sent") {
        // reconfigure TLS creds and redial
        creds := credentials.NewTLS(loadMutualTLSConfig())
        conn, err = grpc.Dial(addr, grpc.WithTransportCredentials(creds))
    }
}

Prevention

When it happens

Trigger: A cluster Step/Submit request arrives over a connection that did not present a TLS client certificate — TLS client auth disabled or misconfigured, or the request was not routed through the mutual-TLS gRPC server.

Common situations: Orderer TLS enabled but clientAuthRequired not set (or vice versa) in orderer.yaml; a test/tool connecting with one-way TLS; a proxy/LB terminating TLS and stripping client certs; sender built without TLS credentials.

Understand the failure class

Related errors


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