{"record":{"id":"8315de6cc6494431","repo":"cloudflare/pingora","slug":"failed-to-parse-pem","errorCode":null,"errorMessage":"Failed to parse PEM","messagePattern":"Failed to parse PEM","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pingora-core/src/utils/tls/s2n.rs","lineNumber":152,"sourceCode":"        get_serial(self.leaf()).unwrap()\n    }\n\n    pub fn raw_pem(&self) -> &[u8] {\n        &self.pem.raw_pem\n    }\n}\n\n#[derive(Debug)]\npub struct X509Pem {\n    pub raw_pem: Vec<u8>,\n    pub certs: Vec<WrappedX509>,\n}\n\nimpl X509Pem {\n    pub fn new(raw_pem: Vec<u8>) -> Self {\n        let certs = Pem::iter_from_buffer(&raw_pem)\n            .map(|part| {\n                let raw_cert = part.expect(\"Failed to parse PEM\").contents;\n                WrappedX509::new(raw_cert, parse_x509)\n            })\n            .collect();\n        X509Pem { raw_pem, certs }\n    }\n\n    pub fn iter(&self) -> std::slice::Iter<'_, WrappedX509> {\n        self.certs.iter()\n    }\n}\n\nfn parse_x509<C>(raw_cert: &C) -> X509Certificate<'_>\nwhere\n    C: AsRef<[u8]>,\n{\n    X509Certificate::from_der(raw_cert.as_ref())\n        .expect(\"Failed to parse certificate from DER format.\")\n        .1","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/cloudflare/pingora/blob/0046038bd402bc82912da862dadf9a479f31e9f1/pingora-core/src/utils/tls/s2n.rs#L134-L170","documentation":"X509Pem::new() in the s2n backend iterates every PEM section in the buffer and expects each to parse (pem::Pem). The expect fires on the first section whose base64 body or -----BEGIN/END----- framing is malformed — stray characters, broken base64, CRLF issues, or a truncated final block. It usually runs while loading certificate chains, so one bad block aborts TLS setup/startup.","triggerScenarios":"Loading a PEM buffer through X509Pem::new (s2n cert chain load path) where any section fails pem parsing: corrupt base64, missing END line, garbage lines, or non-PEM content mixed into the buffer.","commonSituations":"Hand-edited or copy-pasted PEMs with dropped characters; concatenating cert and key into one file; files without a trailing newline; CRLF line endings from Windows; secrets-injection systems mangling base64 padding.","solutions":["Re-emit each certificate canonically: `openssl x509 -in broken.pem -out fixed.pem`, then concatenate the fixed blocks","Bisect the file: run `openssl x509 -in <section> -noout` per block to find the one that fails to decode","Remove non-CERTIFICATE blocks (keys, CSRs, commentary text) from cert chain files","Ensure LF line endings and a trailing newline; re-transfer in binary mode"],"exampleFix":"# before: chain.pem contains a corrupt/truncated block\n-----BEGIN CERTIFICATE-----\nMIIB... (truncated mid-base64)\n\n# after: rebuild the chain from canonically re-exported certs\nopenssl x509 -in leaf.pem -out chain.pem\nopenssl x509 -in intermediate.pem >> chain.pem","handlingStrategy":"validation","validationCode":"fn pem_fully_parses(buf: &[u8]) -> bool {\n    x509_parser::pem::Pem::iter_from_buffer(buf).all(|part| part.is_ok())\n}\n\n// before X509Pem::new(raw_pem)\nanyhow::ensure!(pem_fully_parses(&raw_pem), \"PEM contains a malformed section\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate PEM files in CI with openssl (each block must load) before deploy","Generate/normalize PEMs with openssl rather than manual concatenation","Transfer cert files in binary-safe mode; avoid CRLF translation and missing trailing newlines","Keep only CERTIFICATE blocks in chain files"],"tags":["rust","pingora","tls","s2n","pem","certificate","base64","parsing","panic"],"backgroundTag":"pem-decode-failed","analyzedSha":"0046038bd402bc82912da862dadf9a479f31e9f1","analyzedAt":"2026-08-16T21:33:22.341Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}