1use alloc::borrow::ToOwned;
2use alloc::format;
3use alloc::string::String;
4use alloc::vec;
5use alloc::vec::Vec;
6use core::fmt;
7use core::marker::PhantomData;
8use core::ops::ControlFlow;
9#[cfg(feature = "std")]
10use std::fs::File;
11#[cfg(feature = "std")]
12use std::io::{self, ErrorKind};
13
14use crate::base64;
15
16/// Items that can be decoded from PEM data.
17pub trait PemObject: Sized {
18 /// Decode the first section of this type from PEM contained in
19 /// a byte slice.
20 ///
21 /// [`Error::NoItemsFound`] is returned if no such items are found.
22 fn from_pem_slice(pem: &[u8]) -> Result<Self, Error> {
23 Self::pem_slice_iter(pem)
24 .next()
25 .unwrap_or(Err(Error::NoItemsFound))
26 }
27
28 /// Iterate over all sections of this type from PEM contained in
29 /// a byte slice.
30 fn pem_slice_iter(pem: &[u8]) -> SliceIter<'_, Self> {
31 SliceIter {
32 current: pem,
33 _ty: PhantomData,
34 }
35 }
36
37 /// Decode the first section of this type from the PEM contents of the named file.
38 ///
39 /// [`Error::NoItemsFound`] is returned if no such items are found.
40 #[cfg(feature = "std")]
41 fn from_pem_file(file_name: impl AsRef<std::path::Path>) -> Result<Self, Error> {
42 Self::pem_file_iter(file_name)?
43 .next()
44 .unwrap_or(Err(Error::NoItemsFound))
45 }
46
47 /// Iterate over all sections of this type from the PEM contents of the named file.
48 ///
49 /// This reports errors in two phases:
50 ///
51 /// - errors opening the file are reported from this function directly,
52 /// - errors reading from the file are reported from the returned iterator,
53 #[cfg(feature = "std")]
54 fn pem_file_iter(
55 file_name: impl AsRef<std::path::Path>,
56 ) -> Result<ReadIter<io::BufReader<File>, Self>, Error> {
57 Ok(ReadIter::<_, Self> {
58 rd: io::BufReader::new(File::open(file_name).map_err(Error::Io)?),
59 _ty: PhantomData,
60 })
61 }
62
63 /// Decode the first section of this type from PEM read from an [`io::Read`].
64 #[cfg(feature = "std")]
65 fn from_pem_reader(rd: impl std::io::Read) -> Result<Self, Error> {
66 Self::pem_reader_iter(rd)
67 .next()
68 .unwrap_or(Err(Error::NoItemsFound))
69 }
70
71 /// Iterate over all sections of this type from PEM present in an [`io::Read`].
72 #[cfg(feature = "std")]
73 fn pem_reader_iter<R: std::io::Read>(rd: R) -> ReadIter<io::BufReader<R>, Self> {
74 ReadIter::<_, Self> {
75 rd: io::BufReader::new(rd),
76 _ty: PhantomData,
77 }
78 }
79
80 /// Conversion from a PEM [`SectionKind`] and body data.
81 ///
82 /// This inspects `kind`, and if it matches this type's PEM section kind,
83 /// converts `der` into this type.
84 fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self>;
85}
86
87pub(crate) trait PemObjectFilter: PemObject + From<Vec<u8>> {
88 const KIND: SectionKind;
89}
90
91impl<T: PemObjectFilter + From<Vec<u8>>> PemObject for T {
92 fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self> {
93 match Self::KIND == kind {
94 true => Some(Self::from(der)),
95 false => None,
96 }
97 }
98}
99
100/// Extract and return all PEM sections by reading `rd`.
101#[cfg(feature = "std")]
102pub struct ReadIter<R, T> {
103 rd: R,
104 _ty: PhantomData<T>,
105}
106
107#[cfg(feature = "std")]
108impl<R: io::BufRead, T: PemObject> ReadIter<R, T> {
109 /// Create a new iterator.
110 pub fn new(rd: R) -> Self {
111 Self {
112 rd,
113 _ty: PhantomData,
114 }
115 }
116}
117
118#[cfg(feature = "std")]
119impl<R: io::BufRead, T: PemObject> Iterator for ReadIter<R, T> {
120 type Item = Result<T, Error>;
121
122 fn next(&mut self) -> Option<Self::Item> {
123 loop {
124 return match from_buf(&mut self.rd) {
125 Ok(Some((sec: SectionKind, item: Vec))) => match T::from_pem(kind:sec, der:item) {
126 Some(res: T) => Some(Ok(res)),
127 None => continue,
128 },
129 Ok(None) => return None,
130 Err(err: Error) => Some(Err(err)),
131 };
132 }
133 }
134}
135
136/// Iterator over all PEM sections in a `&[u8]` slice.
137pub struct SliceIter<'a, T> {
138 current: &'a [u8],
139 _ty: PhantomData<T>,
140}
141
142impl<'a, T: PemObject> SliceIter<'a, T> {
143 /// Create a new iterator.
144 pub fn new(current: &'a [u8]) -> Self {
145 Self {
146 current,
147 _ty: PhantomData,
148 }
149 }
150
151 /// Returns the rest of the unparsed data.
152 ///
153 /// This is the slice immediately following the most
154 /// recently returned item from `next()`.
155 #[doc(hidden)]
156 pub fn remainder(&self) -> &'a [u8] {
157 self.current
158 }
159}
160
161impl<T: PemObject> Iterator for SliceIter<'_, T> {
162 type Item = Result<T, Error>;
163
164 fn next(&mut self) -> Option<Self::Item> {
165 loop {
166 return match from_slice(self.current) {
167 Ok(Some(((sec: SectionKind, item: Vec), rest: &[u8]))) => {
168 self.current = rest;
169 match T::from_pem(kind:sec, der:item) {
170 Some(res: T) => Some(Ok(res)),
171 None => continue,
172 }
173 }
174 Ok(None) => return None,
175 Err(err: Error) => Some(Err(err)),
176 };
177 }
178 }
179}
180
181impl PemObject for (SectionKind, Vec<u8>) {
182 fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self> {
183 Some((kind, der))
184 }
185}
186
187/// Extract and decode the next supported PEM section from `input`
188///
189/// - `Ok(None)` is returned if there is no PEM section to read from `input`
190/// - Syntax errors and decoding errors produce a `Err(...)`
191/// - Otherwise each decoded section is returned with a `Ok(Some((..., remainder)))` where
192/// `remainder` is the part of the `input` that follows the returned section
193#[allow(clippy::type_complexity)]
194fn from_slice(mut input: &[u8]) -> Result<Option<((SectionKind, Vec<u8>), &[u8])>, Error> {
195 let mut b64buf = Vec::with_capacity(1024);
196 let mut section = None::<(Vec<_>, Vec<_>)>;
197
198 loop {
199 let next_line = if let Some(index) = input
200 .iter()
201 .position(|byte| *byte == b'\n' || *byte == b'\r')
202 {
203 let (line, newline_plus_remainder) = input.split_at(index);
204 input = &newline_plus_remainder[1..];
205 Some(line)
206 } else if !input.is_empty() {
207 let next_line = input;
208 input = &[];
209 Some(next_line)
210 } else {
211 None
212 };
213
214 match read(next_line, &mut section, &mut b64buf)? {
215 ControlFlow::Continue(()) => continue,
216 ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))),
217 }
218 }
219}
220
221/// Extract and decode the next supported PEM section from `rd`.
222///
223/// - Ok(None) is returned if there is no PEM section read from `rd`.
224/// - Underlying IO errors produce a `Err(...)`
225/// - Otherwise each decoded section is returned with a `Ok(Some(...))`
226#[cfg(feature = "std")]
227pub fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<(SectionKind, Vec<u8>)>, Error> {
228 let mut b64buf: Vec = Vec::with_capacity(1024);
229 let mut section: Option<(Vec, Vec)> = None::<(Vec<_>, Vec<_>)>;
230 let mut line: Vec = Vec::with_capacity(80);
231
232 loop {
233 line.clear();
234 let len: usize = read_until_newline(rd, &mut line).map_err(op:Error::Io)?;
235
236 let next_line: Option<&[u8]> = if len == 0 {
237 None
238 } else {
239 Some(line.as_slice())
240 };
241
242 match read(next_line, &mut section, &mut b64buf) {
243 Ok(ControlFlow::Break(opt: Option<(SectionKind, Vec<…>)>)) => return Ok(opt),
244 Ok(ControlFlow::Continue(())) => continue,
245 Err(e: Error) => return Err(e),
246 }
247 }
248}
249
250#[allow(clippy::type_complexity)]
251fn read(
252 next_line: Option<&[u8]>,
253 section: &mut Option<(Vec<u8>, Vec<u8>)>,
254 b64buf: &mut Vec<u8>,
255) -> Result<ControlFlow<Option<(SectionKind, Vec<u8>)>, ()>, Error> {
256 let line = if let Some(line) = next_line {
257 line
258 } else {
259 // EOF
260 return match section.take() {
261 Some((_, end_marker)) => Err(Error::MissingSectionEnd { end_marker }),
262 None => Ok(ControlFlow::Break(None)),
263 };
264 };
265
266 if line.starts_with(b"-----BEGIN ") {
267 let (mut trailer, mut pos) = (0, line.len());
268 for (i, &b) in line.iter().enumerate().rev() {
269 match b {
270 b'-' => {
271 trailer += 1;
272 pos = i;
273 }
274 b'\n' | b'\r' | b' ' => continue,
275 _ => break,
276 }
277 }
278
279 if trailer != 5 {
280 return Err(Error::IllegalSectionStart {
281 line: line.to_vec(),
282 });
283 }
284
285 let ty = &line[11..pos];
286 let mut end = Vec::with_capacity(10 + 4 + ty.len());
287 end.extend_from_slice(b"-----END ");
288 end.extend_from_slice(ty);
289 end.extend_from_slice(b"-----");
290 *section = Some((ty.to_owned(), end));
291 return Ok(ControlFlow::Continue(()));
292 }
293
294 if let Some((section_label, end_marker)) = section.as_ref() {
295 if line.starts_with(end_marker) {
296 let kind = match SectionKind::try_from(&section_label[..]) {
297 Ok(kind) => kind,
298 // unhandled section: have caller try again
299 Err(()) => {
300 *section = None;
301 b64buf.clear();
302 return Ok(ControlFlow::Continue(()));
303 }
304 };
305
306 let mut der = vec![0u8; base64::decoded_length(b64buf.len())];
307 let der_len = match kind.secret() {
308 true => base64::decode_secret(b64buf, &mut der),
309 false => base64::decode_public(b64buf, &mut der),
310 }
311 .map_err(|err| Error::Base64Decode(format!("{err:?}")))?
312 .len();
313
314 der.truncate(der_len);
315
316 return Ok(ControlFlow::Break(Some((kind, der))));
317 }
318 }
319
320 if section.is_some() {
321 b64buf.extend(line);
322 }
323
324 Ok(ControlFlow::Continue(()))
325}
326
327/// A single recognised section in a PEM file.
328#[non_exhaustive]
329#[derive(Clone, Copy, Debug, PartialEq)]
330pub enum SectionKind {
331 /// A DER-encoded x509 certificate.
332 ///
333 /// Appears as "CERTIFICATE" in PEM files.
334 Certificate,
335
336 /// A DER-encoded Subject Public Key Info; as specified in RFC 7468.
337 ///
338 /// Appears as "PUBLIC KEY" in PEM files.
339 PublicKey,
340
341 /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447
342 ///
343 /// Appears as "RSA PRIVATE KEY" in PEM files.
344 RsaPrivateKey,
345
346 /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958
347 ///
348 /// Appears as "PRIVATE KEY" in PEM files.
349 PrivateKey,
350
351 /// A Sec1-encoded plaintext private key; as specified in RFC 5915
352 ///
353 /// Appears as "EC PRIVATE KEY" in PEM files.
354 EcPrivateKey,
355
356 /// A Certificate Revocation List; as specified in RFC 5280
357 ///
358 /// Appears as "X509 CRL" in PEM files.
359 Crl,
360
361 /// A Certificate Signing Request; as specified in RFC 2986
362 ///
363 /// Appears as "CERTIFICATE REQUEST" in PEM files.
364 Csr,
365
366 /// An EchConfigList structure, as specified in
367 /// <https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-05.html>.
368 ///
369 /// Appears as "ECHCONFIG" in PEM files.
370 EchConfigList,
371}
372
373impl SectionKind {
374 fn secret(&self) -> bool {
375 match self {
376 Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true,
377 Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => {
378 false
379 }
380 }
381 }
382}
383
384impl TryFrom<&[u8]> for SectionKind {
385 type Error = ();
386
387 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
388 Ok(match value {
389 b"CERTIFICATE" => Self::Certificate,
390 b"PUBLIC KEY" => Self::PublicKey,
391 b"RSA PRIVATE KEY" => Self::RsaPrivateKey,
392 b"PRIVATE KEY" => Self::PrivateKey,
393 b"EC PRIVATE KEY" => Self::EcPrivateKey,
394 b"X509 CRL" => Self::Crl,
395 b"CERTIFICATE REQUEST" => Self::Csr,
396 b"ECHCONFIG" => Self::EchConfigList,
397 _ => return Err(()),
398 })
399 }
400}
401
402/// Errors that may arise when parsing the contents of a PEM file
403#[non_exhaustive]
404#[derive(Debug)]
405pub enum Error {
406 /// a section is missing its "END marker" line
407 MissingSectionEnd {
408 /// the expected "END marker" line that was not found
409 end_marker: Vec<u8>,
410 },
411
412 /// syntax error found in the line that starts a new section
413 IllegalSectionStart {
414 /// line that contains the syntax error
415 line: Vec<u8>,
416 },
417
418 /// base64 decode error
419 Base64Decode(String),
420
421 /// I/O errors, from APIs that accept `std::io` types.
422 #[cfg(feature = "std")]
423 Io(io::Error),
424
425 /// No items found of desired type
426 NoItemsFound,
427}
428
429impl fmt::Display for Error {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 match self {
432 Self::MissingSectionEnd { end_marker: &Vec } => {
433 write!(f, "missing section end marker: {end_marker:?}")
434 }
435 Self::IllegalSectionStart { line: &Vec } => {
436 write!(f, "illegal section start: {line:?}")
437 }
438 Self::Base64Decode(e: &String) => write!(f, "base64 decode error: {e}"),
439 #[cfg(feature = "std")]
440 Self::Io(e: &Error) => write!(f, "I/O error: {e}"),
441 Self::NoItemsFound => write!(f, "no items found"),
442 }
443 }
444}
445
446#[cfg(feature = "std")]
447impl std::error::Error for Error {}
448
449// Ported from https://github.com/rust-lang/rust/blob/91cfcb021935853caa06698b759c293c09d1e96a/library/std/src/io/mod.rs#L1990 and
450// modified to look for our accepted newlines.
451#[cfg(feature = "std")]
452fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> io::Result<usize> {
453 let mut read = 0;
454 loop {
455 let (done, used) = {
456 let available = match r.fill_buf() {
457 Ok(n) => n,
458 Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
459 Err(e) => return Err(e),
460 };
461 match available
462 .iter()
463 .copied()
464 .position(|b| b == b'\n' || b == b'\r')
465 {
466 Some(i) => {
467 buf.extend_from_slice(&available[..=i]);
468 (true, i + 1)
469 }
470 None => {
471 buf.extend_from_slice(available);
472 (false, available.len())
473 }
474 }
475 };
476 r.consume(used);
477 read += used;
478 if done || used == 0 {
479 return Ok(read);
480 }
481 }
482}
483