hyperledger/fabric · error

TLS is active but chaincode %s didn't send certificate

Error message

TLS is active but chaincode %s didn't send certificate

What it means

When mutual TLS is enabled between peer and chaincode, the Authenticator requires the chaincode's TLS client certificate during registration. If the stream's gRPC context carries no certificate hash (the client presented no certificate, or TLS metadata is absent), registration is rejected with 'TLS is active but chaincode %s didn't send certificate'.

Source

Thrown at core/chaincode/accesscontrol/access.go:78

func (ac *Authenticator) authenticate(msg *pb.ChaincodeMessage, stream grpc.ServerStream) error {
	if msg.Type != pb.ChaincodeMessage_REGISTER {
		logger.Warning("Got message", msg, "but expected a ChaincodeMessage_REGISTER message")
		return errors.New("First message needs to be a register")
	}

	chaincodeID := &pb.ChaincodeID{}
	err := proto.Unmarshal(msg.Payload, chaincodeID)
	if err != nil {
		logger.Warning("Failed unmarshalling message:", err)
		return err
	}
	ccName := chaincodeID.Name
	// Obtain certificate from stream
	hash := extractCertificateHashFromContext(stream.Context())
	if len(hash) == 0 {
		errMsg := fmt.Sprintf("TLS is active but chaincode %s didn't send certificate", ccName)
		logger.Warning(errMsg)
		return errors.New(errMsg)
	}
	// Look it up in the mapper
	registeredName := ac.mapper.lookup(certHash(hash))
	if registeredName == "" {
		errMsg := fmt.Sprintf("Chaincode %s with given certificate hash %v not found in registry", ccName, hash)
		logger.Warning(errMsg)
		return errors.New(errMsg)
	}
	if registeredName != ccName {
		errMsg := fmt.Sprintf("Chaincode %s with given certificate hash %v belongs to a different chaincode", ccName, hash)
		logger.Warning(errMsg)
		return errors.New(errMsg)
	}

	logger.Debug("Chaincode", ccName, "'s authentication is authorized")
	return nil
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Configure the chaincode to use TLS with its client certificate (set CORE_CHAINCODE_TLS_CERT / CORE_CHAINCODE_TLS_KEY to the cert/key files).
  2. Ensure the chaincode's connection address uses the TLS port and the peer's chaincode TLS settings match (peer.chaincode.cert.enabled).
  3. If running in a container, verify the TLS files are mounted and readable, and the chaincode connects over TLS not plaintext.
  4. Remove any TLS-terminating proxy between chaincode and peer, or re-sign at the peer side so client certs reach the peer.

Example fix

// before: chaincode started without TLS
CORE_CHAINCODE_ID_NAME=mycc:1.0 /bin/chaincode -peer.address peer:7052
// after: chaincode started with TLS certs
CORE_CHAINCODE_ID_NAME=mycc:1.0 CORE_CHAINCODE_TLS_CERT=/certs/tls.crt CORE_CHAINCODE_TLS_KEY=/certs/tls.key /bin/chaincode -peer.address peer:7052
Defensive patterns

Strategy: validation

Validate before calling

// before connecting, confirm TLS material is present
if _, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath); err != nil {
    return fmt.Errorf("chaincode TLS cert/key missing: %w", err)
}

Type guard

func tlsCredentialsPresent(certPath, keyPath string) bool {
    _, err := tls.LoadX509KeyPair(certPath, keyPath)
    return err == nil
}

Try / catch

if err := authenticate(msg, stream); err != nil {
    if strings.Contains(err.Error(), "didn't send certificate") {
        // re-dial the peer with tls.Credentials{Certificates: clientCerts}
    }
}

Prevention

When it happens

Trigger: A chaincode registers over a connection where extractCertificateHashFromContext finds no client certificate: TLS client auth not configured on the chaincode's connection, or the chaincode's side of the gRPC connection was established without TLS.

Common situations: Chaincode configured with TLS disabled while the peer runs with peer.chaincode.cert.enabled=true (mtls); a dev-mode chaincode or local runner not presenting its TLS keypair; a proxy/load balancer terminating TLS so the peer never sees the client cert.

Understand the failure class

Related errors


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