hyperledger/fabric · error

client certificate isn't in PEM format: %v

Error message

client certificate isn't in PEM format: %v

What it means

NewBlockPullerCreator decodes the configured client TLS certificate with pem.Decode; if no PEM block can be parsed it wraps the failure with the raw certificate bytes. This means the SecOpts.Certificate is not PEM-encoded, so the TLS material is unusable for the block puller's gRPC client.

Source

Thrown at orderer/common/follower/block_puller.go:74

// NewBlockPullerCreator creates a new BlockPullerCreator, using the configuration details that do not change during
// the life cycle of the orderer.
func NewBlockPullerCreator(
	channelID string,
	logger *flogging.FabricLogger,
	signer identity.SignerSerializer,
	baseDialer *cluster.PredicateDialer,
	clusterConfig localconfig.Cluster,
	bccsp bccsp.BCCSP,
) (*BlockPullerCreator, error) {
	stdDialer := &cluster.StandardDialer{
		Config: baseDialer.Config,
	}
	stdDialer.Config.AsyncConnect = false
	stdDialer.Config.SecOpts.VerifyCertificate = nil

	der, _ := pem.Decode(stdDialer.Config.SecOpts.Certificate)
	if der == nil {
		return nil, errors.Errorf("client certificate isn't in PEM format: %v",
			string(stdDialer.Config.SecOpts.Certificate))
	}

	factory := &BlockPullerCreator{
		channelID: channelID,
		bccsp:     bccsp,
		blockSigVerifierFactory: &deliverclient.BlockVerifierAssembler{
			Logger: logger,
			BCCSP:  bccsp,
		},
		clusterConfig:       clusterConfig,
		signer:              signer,
		stdDialer:           stdDialer,
		der:                 der,
		ClusterVerifyBlocks: cluster.VerifyBlocksBFT, // The default block sequence verification method.
		vb:                  cluster.BlockVerifierBuilder(bccsp),
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Point the TLS client certificate config to a valid PEM file beginning with '-----BEGIN CERTIFICATE-----'
  2. Verify the file content with `openssl x509 -in cert.pem -text -noout`; if it fails, re-export in PEM (`openssl x509 -inform DER -in cert.der -out cert.pem`)
  3. Re-generate the certificate from the MSP (e.g. copy signcerts/cert.pem from the organization's MSP directory)
  4. Check the crypto material wasn't truncated or replaced during channel join setup

Example fix

// before (yaml)
tls:
  clientKey:
    file: /path/to/client.cer   # DER binary
  clientCert:
    file: /path/to/client.cer
// after
tls:
  clientKey:
    file: /path/to/client.key
  clientCert:
    file: /path/to/client-cert.pem  # '-----BEGIN CERTIFICATE-----'
Defensive patterns

Strategy: validation

Validate before calling

certPEM, err := os.ReadFile(cfg.TLS.ClientCertFile)
if err != nil {
    return fmt.Errorf("cannot read client cert: %w", err)
}
block, _ := pem.Decode(certPEM)
if block == nil || block.Type != "CERTIFICATE" {
    return fmt.Errorf("client cert %s is not PEM-encoded", cfg.TLS.ClientCertFile)
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
    return fmt.Errorf("client cert is not a valid certificate: %w", err)
}

Type guard

func isPEMCertificate(data []byte) bool {
    block, _ := pem.Decode(data)
    return block != nil && block.Type == "CERTIFICATE"
}

Try / catch

creator, err := blockpuller.NewBlockPullerCreator(...)
if err != nil {
    if strings.Contains(err.Error(), "client certificate isn't in PEM format") {
        return fmt.Errorf("TLS client certificate invalid; re-export PEM from MSP: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Creating a follower's BlockPullerCreator (via createFollower or the anonymous init path) when tls.ClientCert (or the equivalent general config) points to a file whose bytes are not PEM (DER-encoded binary, empty file, HTML error page, wrong file).

Common situations: Configured certificate path actually contains the private key or a DER .cer file; file fetched by mistake (404 page); cert concatenated with garbage; Fabric version/config migration left tls paths pointing at wrong files.

Understand the failure class

Related errors


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