From cf664eda55e18f2e8cd8f2b61a22e01ecf34113a Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Mon, 23 Feb 2026 09:16:36 +0800 Subject: [PATCH 1/2] fix(pem): clear line buffer after InvalidData error When `read_line` returns `ErrorKind::InvalidData` (invalid UTF-8), the line buffer was not cleared before `continue`, so stale data from a previous successful read could leak into the next iteration and corrupt the parsed result. Add `line.clear()` before `continue` to prevent stale data accumulation. Add test verifying PEM with invalid UTF-8 comment lines is handled correctly. --- src/pem.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pem.rs b/src/pem.rs index f2f930f..da09665 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -123,6 +123,7 @@ impl Pem { Ok(line) => line, Err(e) if e.kind() == ErrorKind::InvalidData => { // some tools put invalid UTF-8 data in PEM comment section. Ignore line + line.clear(); continue; } Err(e) => { @@ -261,4 +262,22 @@ mod tests { let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem"); assert_eq!(pem.label, "MULTI WORD LABEL"); } + + #[test] + fn pem_invalid_utf8_in_comment_is_skipped() { + // Build PEM data with an invalid UTF-8 line before the BEGIN header. + // The parser should skip the invalid line and still parse the PEM block. + let mut data: Vec = Vec::new(); + // A comment line with invalid UTF-8 bytes (0xFF is never valid in UTF-8) + data.extend_from_slice(b"# comment \xff\xff\n"); + data.extend_from_slice(b"-----BEGIN CERTIFICATE-----\n"); + // Minimal base64 content (one byte: 0x00) + data.extend_from_slice(b"AA==\n"); + data.extend_from_slice(b"-----END CERTIFICATE-----\n"); + + let cursor = Cursor::new(data.as_slice()); + let (pem, _) = Pem::read(cursor).expect("should skip invalid UTF-8 and parse PEM"); + assert_eq!(pem.label, "CERTIFICATE"); + assert_eq!(pem.contents, vec![0x00]); + } } From cb621e39a01c9dbca4831d3af44620e7b3b2efcb Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Mon, 23 Feb 2026 09:18:20 +0800 Subject: [PATCH 2/2] fix(pem): validate that END label matches BEGIN label The PEM parser did not check that the END label matched the BEGIN label, silently accepting mismatched PEM blocks (e.g., BEGIN CERTIFICATE / END PRIVATE KEY). Add validation that rejects mismatched labels with InvalidHeader error. Convert `label` from `&str` to `String` to avoid lifetime issues with the END label comparison. Add test verifying mismatched labels produce an error. --- src/pem.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/pem.rs b/src/pem.rs index da09665..43d3b1f 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -145,7 +145,11 @@ impl Pem { let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?; break label; }; - let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?; + let label = label + .split('-') + .next() + .ok_or(PEMError::InvalidHeader)? + .to_string(); let mut s = String::new(); loop { let mut l = String::new(); @@ -154,7 +158,13 @@ impl Pem { return Err(PEMError::IncompletePEM); } if l.starts_with("-----END ") { - // finished reading + // validate that END label matches BEGIN label + let end_v: Vec<&str> = l.trim_end().split("-----").collect(); + if let Some(end_label) = end_v.get(1).and_then(|s| s.strip_prefix("END ")) { + if end_label != label { + return Err(PEMError::InvalidHeader); + } + } break; } s.push_str(l.trim_end()); @@ -163,10 +173,7 @@ impl Pem { let contents = data_encoding::BASE64 .decode(s.as_bytes()) .or(Err(PEMError::Base64DecodeError))?; - let pem = Pem { - label: label.to_string(), - contents, - }; + let pem = Pem { label, contents }; Ok((pem, r.stream_position()? as usize)) } @@ -280,4 +287,20 @@ mod tests { assert_eq!(pem.label, "CERTIFICATE"); assert_eq!(pem.contents, vec![0x00]); } + + #[test] + fn pem_mismatched_end_label_returns_error() { + // A PEM block where the END label does not match the BEGIN label should + // be rejected with an InvalidHeader error. + const PEM_BYTES: &[u8] = b"-----BEGIN CERTIFICATE-----\nAA==\n-----END PRIVATE KEY-----\n"; + let cursor = Cursor::new(PEM_BYTES); + let result = Pem::read(cursor); + assert!(result.is_err(), "mismatched END label should return error"); + let err = result.unwrap_err(); + assert!( + matches!(err, PEMError::InvalidHeader), + "expected InvalidHeader, got: {:?}", + err + ); + } }