Skip to main content

rustls_pki_types/
pem.rs

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