{"record":{"id":"21c214cac7fafba3","repo":"hyperledger/fabric","slug":"node-identity-certificate-s-is-not-a-valid-pem","errorCode":null,"errorMessage":"node identity certificate %s is not a valid PEM","messagePattern":"node identity certificate (.+?) is not a valid PEM","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/common/cluster/util.go","lineNumber":845,"sourceCode":"\tbinary.LittleEndian.PutUint64(b, uint64(t.Seconds))\n\treturn b\n}\n\n// ExtractPublicKeyFromCert extracts the public key from an X.509 certificate\nfunc ExtractPublicKeyFromCert(der []byte) ([]byte, error) {\n\tcert, err := x509.ParseCertificate(der)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to parse certificate\")\n\t}\n\n\treturn x509.MarshalPKIXPublicKey(cert.PublicKey)\n}\n\nfunc CompareCertPublicKeys(cert1, cert2 []byte) (bool, error) {\n\t// Extract public key using the same approach as IsConsenterOfChannel\n\tbl, _ := pem.Decode(cert1)\n\tif bl == nil {\n\t\treturn false, errors.Errorf(\"node identity certificate %s is not a valid PEM\", string(cert1))\n\t}\n\n\tpublicKey1, err := ExtractPublicKeyFromCert(bl.Bytes)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tbl, _ = pem.Decode(cert2)\n\tif bl == nil {\n\t\treturn false, errors.Errorf(\"node identity certificate %s is not a valid PEM\", string(cert2))\n\t}\n\n\tpublicKey2, err := ExtractPublicKeyFromCert(bl.Bytes)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn bytes.Equal(publicKey1, publicKey2), nil","sourceCodeStart":827,"sourceCodeEnd":863,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/common/cluster/util.go#L827-L863","documentation":"CompareCertPublicKeys decodes the first certificate (cert1) as PEM to extract its public key. If pem.Decode yields no block, cert1 is not valid PEM and the error reports the raw string content of the input for diagnosis.","triggerScenarios":"Calling CompareCertPublicKeys with cert1 as raw DER bytes, an empty byte slice, a path string, or text with the PEM armor stripped — the first argument specifically.","commonSituations":"Mixing DER and PEM inputs for the two arguments; loading identity material from a source that strips headers; passing the wrong variable (e.g. node ID string) as cert1.","solutions":["Ensure cert1 is PEM-encoded (-----BEGIN CERTIFICATE----- included)","If you hold DER bytes, wrap them: pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: der})","Check that the right variable/file is bound to cert1 (the error message echoes the raw input, inspect it)","Both arguments must use the same encoding — normalize both before comparing"],"exampleFix":"// before\nsame, err := CompareCertPublicKeys(derCert1, pemCert2) // derCert1 not PEM\n// after\npem1 := pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: derCert1})\nsame, err := CompareCertPublicKeys(pem1, pemCert2)","handlingStrategy":"validation","validationCode":"if blk, _ := pem.Decode(cert1); blk == nil {\n    return errors.New(\"cert1 must be a PEM-encoded certificate\")\n}","typeGuard":"func isPEM(data []byte) bool {\n    blk, _ := pem.Decode(data)\n    return blk != nil\n}","tryCatchPattern":"same, err := cluster.CompareCertPublicKeys(cert1, cert2)\nif err != nil && strings.Contains(err.Error(), \"not a valid PEM\") {\n    return fmt.Errorf(\"normalize both certs to PEM before comparing: %w\", err)\n}","preventionTips":["Normalize both certificate inputs to PEM before comparison","Never pass file paths or DER bytes where PEM is expected","Check error message echo of raw input to spot wrong-variable bugs","Store all node identity certs in PEM format consistently"],"tags":["pem","certificate","comparison"],"backgroundTag":"invalid-pem-certificate","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}