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