hyperledger/fabric · error

failed to PEM decode identity bytes

Error message

failed to PEM decode identity bytes: %s

What it means

SanitizeX509Cert expects the input to be a PEM block containing a certificate. pem.Decode returned nil, meaning the bytes are not valid PEM (wrong format, binary DER, or empty), so the error includes the raw input string.

Solutions

  1. Ensure the input is PEM: starts with '-----BEGIN CERTIFICATE-----' and ends with the END line
  2. If you have DER, convert to PEM (openssl x509 -inform DER -outform PEM) before calling
  3. Verify file paths/config so the certificate, not the private key or another artifact, is loaded
  4. Strip extraneous whitespace/BOM and re-export the certificate cleanly

Example fix

// before
sanitized, _ := crypto.SanitizeX509Cert(derBytes)
// after
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
sanitized, err := crypto.SanitizeX509Cert(pemBytes)
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

out, err := crypto.SanitizeX509Cert(pemBytes)
if err != nil && strings.Contains(err.Error(), "failed to PEM decode") {
    // re-encode DER to PEM or fix the file path before retrying
}

Prevention

When it happens

Trigger: Calling SanitizeX509Cert (directly or via SanitizeIdentity, ConfigureNodeCerts, IsChannelMember) with DER-encoded certificates, double-encoded PEM, empty bytes, or text that merely looks like a certificate.

Common situations: Storing certs DER-encoded then feeding them to fabric's sanitizer; copy-paste losing the BEGIN/END lines or adding stray whitespace/BOM; config pointing at the wrong file (key instead of cert); IdBytes populated with a hash rather than PEM.

Related errors


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

Appendix: source

Thrown at common/crypto/sanitize.go:45

	if err := proto.Unmarshal(identity, sID); err != nil {
		return nil, errors.Wrapf(err, "failed unmarshaling identity %s", string(identity))
	}

	finalPEM, err := SanitizeX509Cert(sID.IdBytes)
	if err != nil {
		return nil, err
	}

	sID.IdBytes = finalPEM

	return proto.Marshal(sID)
}

// SanitizeX509Cert sanitizes an X.509 certificate to ensure that the ECDSA signature uses a "low-S" value.
func SanitizeX509Cert(initialPEM []byte) ([]byte, error) {
	der, _ := pem.Decode(initialPEM)
	if der == nil {
		return nil, errors.Errorf("failed to PEM decode identity bytes: %s", string(initialPEM))
	}
	cert, err := x509.ParseCertificate(der.Bytes)
	if err != nil {
		return nil, errors.Wrapf(err, "failed parsing certificate %s", string(initialPEM))
	}

	r, s, err := utils.UnmarshalECDSASignature(cert.Signature)
	if err != nil {
		return nil, errors.Wrapf(err, "failed unmarshaling ECDSA signature on identity: %s", string(initialPEM))
	}

	// We assume that the consenter and the CA use the same signature scheme.
	curveOrderUsedByCryptoGen := cert.PublicKey.(*ecdsa.PublicKey).Curve.Params().N
	halfOrder := new(big.Int).Rsh(curveOrderUsedByCryptoGen, 1)
	// Low S, nothing to do here!
	if s.Cmp(halfOrder) != 1 {
		return initialPEM, nil
	}

View on GitHub (pinned to 2736b63f8f)