rustls_pki_types/
pem.rs

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, item))) => match T::from_pem(sec, item) {
126                    Some(res) => Some(Ok(res)),
127                    None => continue,
128                },
129                Ok(None) => return None,
130                Err(err) => 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, item), rest))) => {
168                    self.current = rest;
169                    match T::from_pem(sec, item) {
170                        Some(res) => Some(Ok(res)),
171                        None => continue,
172                    }
173                }
174                Ok(None) => return None,
175                Err(err) => 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 {
207            None
208        };
209
210        match read(next_line, &mut section, &mut b64buf)? {
211            ControlFlow::Continue(()) => continue,
212            ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))),
213        }
214    }
215}
216
217/// Extract and decode the next supported PEM section from `rd`.
218///
219/// - Ok(None) is returned if there is no PEM section read from `rd`.
220/// - Underlying IO errors produce a `Err(...)`
221/// - Otherwise each decoded section is returned with a `Ok(Some(...))`
222#[cfg(feature = "std")]
223pub fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<(SectionKind, Vec<u8>)>, Error> {
224    let mut b64buf = Vec::with_capacity(1024);
225    let mut section = None::<(Vec<_>, Vec<_>)>;
226    let mut line = Vec::with_capacity(80);
227
228    loop {
229        line.clear();
230        let len = read_until_newline(rd, &mut line).map_err(Error::Io)?;
231
232        let next_line = if len == 0 {
233            None
234        } else {
235            Some(line.as_slice())
236        };
237
238        match read(next_line, &mut section, &mut b64buf) {
239            Ok(ControlFlow::Break(opt)) => return Ok(opt),
240            Ok(ControlFlow::Continue(())) => continue,
241            Err(e) => return Err(e),
242        }
243    }
244}
245
246#[allow(clippy::type_complexity)]
247fn read(
248    next_line: Option<&[u8]>,
249    section: &mut Option<(Vec<u8>, Vec<u8>)>,
250    b64buf: &mut Vec<u8>,
251) -> Result<ControlFlow<Option<(SectionKind, Vec<u8>)>, ()>, Error> {
252    let line = if let Some(line) = next_line {
253        line
254    } else {
255        // EOF
256        return match section.take() {
257            Some((_, end_marker)) => Err(Error::MissingSectionEnd { end_marker }),
258            None => Ok(ControlFlow::Break(None)),
259        };
260    };
261
262    if line.starts_with(b"-----BEGIN ") {
263        let (mut trailer, mut pos) = (0, line.len());
264        for (i, &b) in line.iter().enumerate().rev() {
265            match b {
266                b'-' => {
267                    trailer += 1;
268                    pos = i;
269                }
270                b'\n' | b'\r' | b' ' => continue,
271                _ => break,
272            }
273        }
274
275        if trailer != 5 {
276            return Err(Error::IllegalSectionStart {
277                line: line.to_vec(),
278            });
279        }
280
281        let ty = &line[11..pos];
282        let mut end = Vec::with_capacity(10 + 4 + ty.len());
283        end.extend_from_slice(b"-----END ");
284        end.extend_from_slice(ty);
285        end.extend_from_slice(b"-----");
286        *section = Some((ty.to_owned(), end));
287        return Ok(ControlFlow::Continue(()));
288    }
289
290    if let Some((section_label, end_marker)) = section.as_ref() {
291        if line.starts_with(end_marker) {
292            let kind = match SectionKind::try_from(&section_label[..]) {
293                Ok(kind) => kind,
294                // unhandled section: have caller try again
295                Err(()) => {
296                    *section = None;
297                    b64buf.clear();
298                    return Ok(ControlFlow::Continue(()));
299                }
300            };
301
302            let mut der = vec![0u8; base64::decoded_length(b64buf.len())];
303            let der_len = match kind.secret() {
304                true => base64::decode_secret(b64buf, &mut der),
305                false => base64::decode_public(b64buf, &mut der),
306            }
307            .map_err(|err| Error::Base64Decode(format!("{err:?}")))?
308            .len();
309
310            der.truncate(der_len);
311
312            return Ok(ControlFlow::Break(Some((kind, der))));
313        }
314    }
315
316    if section.is_some() {
317        b64buf.extend(line);
318    }
319
320    Ok(ControlFlow::Continue(()))
321}
322
323/// A single recognised section in a PEM file.
324#[non_exhaustive]
325#[derive(Clone, Copy, Debug, PartialEq)]
326pub enum SectionKind {
327    /// A DER-encoded x509 certificate.
328    ///
329    /// Appears as "CERTIFICATE" in PEM files.
330    Certificate,
331
332    /// A DER-encoded Subject Public Key Info; as specified in RFC 7468.
333    ///
334    /// Appears as "PUBLIC KEY" in PEM files.
335    PublicKey,
336
337    /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447
338    ///
339    /// Appears as "RSA PRIVATE KEY" in PEM files.
340    RsaPrivateKey,
341
342    /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958
343    ///
344    /// Appears as "PRIVATE KEY" in PEM files.
345    PrivateKey,
346
347    /// A Sec1-encoded plaintext private key; as specified in RFC 5915
348    ///
349    /// Appears as "EC PRIVATE KEY" in PEM files.
350    EcPrivateKey,
351
352    /// A Certificate Revocation List; as specified in RFC 5280
353    ///
354    /// Appears as "X509 CRL" in PEM files.
355    Crl,
356
357    /// A Certificate Signing Request; as specified in RFC 2986
358    ///
359    /// Appears as "CERTIFICATE REQUEST" in PEM files.
360    Csr,
361
362    /// An EchConfigList structure, as specified in
363    /// <https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-05.html>.
364    ///
365    /// Appears as "ECHCONFIG" in PEM files.
366    EchConfigList,
367}
368
369impl SectionKind {
370    fn secret(&self) -> bool {
371        match self {
372            Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true,
373            Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => {
374                false
375            }
376        }
377    }
378}
379
380impl TryFrom<&[u8]> for SectionKind {
381    type Error = ();
382
383    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
384        Ok(match value {
385            b"CERTIFICATE" => Self::Certificate,
386            b"PUBLIC KEY" => Self::PublicKey,
387            b"RSA PRIVATE KEY" => Self::RsaPrivateKey,
388            b"PRIVATE KEY" => Self::PrivateKey,
389            b"EC PRIVATE KEY" => Self::EcPrivateKey,
390            b"X509 CRL" => Self::Crl,
391            b"CERTIFICATE REQUEST" => Self::Csr,
392            b"ECHCONFIG" => Self::EchConfigList,
393            _ => return Err(()),
394        })
395    }
396}
397
398/// Errors that may arise when parsing the contents of a PEM file
399#[non_exhaustive]
400#[derive(Debug)]
401pub enum Error {
402    /// a section is missing its "END marker" line
403    MissingSectionEnd {
404        /// the expected "END marker" line that was not found
405        end_marker: Vec<u8>,
406    },
407
408    /// syntax error found in the line that starts a new section
409    IllegalSectionStart {
410        /// line that contains the syntax error
411        line: Vec<u8>,
412    },
413
414    /// base64 decode error
415    Base64Decode(String),
416
417    /// I/O errors, from APIs that accept `std::io` types.
418    #[cfg(feature = "std")]
419    Io(io::Error),
420
421    /// No items found of desired type
422    NoItemsFound,
423}
424
425impl fmt::Display for Error {
426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427        match self {
428            Self::MissingSectionEnd { end_marker } => {
429                write!(f, "missing section end marker: {end_marker:?}")
430            }
431            Self::IllegalSectionStart { line } => {
432                write!(f, "illegal section start: {line:?}")
433            }
434            Self::Base64Decode(e) => write!(f, "base64 decode error: {e}"),
435            #[cfg(feature = "std")]
436            Self::Io(e) => write!(f, "I/O error: {e}"),
437            Self::NoItemsFound => write!(f, "no items found"),
438        }
439    }
440}
441
442#[cfg(feature = "std")]
443impl std::error::Error for Error {}
444
445// Ported from https://github.com/rust-lang/rust/blob/91cfcb021935853caa06698b759c293c09d1e96a/library/std/src/io/mod.rs#L1990 and
446// modified to look for our accepted newlines.
447#[cfg(feature = "std")]
448fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> io::Result<usize> {
449    let mut read = 0;
450    loop {
451        let (done, used) = {
452            let available = match r.fill_buf() {
453                Ok(n) => n,
454                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
455                Err(e) => return Err(e),
456            };
457            match available
458                .iter()
459                .copied()
460                .position(|b| b == b'\n' || b == b'\r')
461            {
462                Some(i) => {
463                    buf.extend_from_slice(&available[..=i]);
464                    (true, i + 1)
465                }
466                None => {
467                    buf.extend_from_slice(available);
468                    (false, available.len())
469                }
470            }
471        };
472        r.consume(used);
473        read += used;
474        if done || used == 0 {
475            return Ok(read);
476        }
477    }
478}