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
15pub trait PemObject: Sized {
17    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    fn pem_slice_iter(pem: &[u8]) -> SliceIter<'_, Self> {
30        SliceIter::new(pem)
31    }
32
33    #[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    #[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    #[cfg(feature = "std")]
60    fn from_pem_reader(rd: impl std::io::Read) -> Result<Self, Error> {
61        Self::pem_reader_iter(rd)
62            .next()
63            .unwrap_or(Err(Error::NoItemsFound))
64    }
65
66    #[cfg(feature = "std")]
68    fn pem_reader_iter<R: std::io::Read>(rd: R) -> ReadIter<io::BufReader<R>, Self> {
69        ReadIter::new(io::BufReader::new(rd))
70    }
71
72    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#[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}
100
101#[cfg(feature = "std")]
102impl<R: io::BufRead, T: PemObject> ReadIter<R, T> {
103    pub fn new(rd: R) -> Self {
105        Self {
106            rd,
107            _ty: PhantomData,
108            line: Vec::with_capacity(80),
109            b64_buf: Vec::with_capacity(1024),
110        }
111    }
112}
113
114#[cfg(feature = "std")]
115impl<R: io::BufRead, T: PemObject> Iterator for ReadIter<R, T> {
116    type Item = Result<T, Error>;
117
118    fn next(&mut self) -> Option<Self::Item> {
119        loop {
120            self.b64_buf.clear();
121            return match from_buf_inner(&mut self.rd, &mut self.line, &mut self.b64_buf) {
122                Ok(Some((sec, item))) => match T::from_pem(sec, item) {
123                    Some(res) => Some(Ok(res)),
124                    None => continue,
125                },
126                Ok(None) => return None,
127                Err(err) => Some(Err(err)),
128            };
129        }
130    }
131}
132
133pub struct SliceIter<'a, T> {
135    current: &'a [u8],
136    _ty: PhantomData<T>,
137    b64_buf: Vec<u8>,
138}
139
140impl<'a, T: PemObject> SliceIter<'a, T> {
141    pub fn new(current: &'a [u8]) -> Self {
143        Self {
144            current,
145            _ty: PhantomData,
146            b64_buf: Vec::with_capacity(1024),
147        }
148    }
149
150    fn read_section(&mut self) -> Result<Option<(SectionKind, Vec<u8>)>, Error> {
157        self.b64_buf.clear();
158        let mut section = None;
159        loop {
160            let next_line = if let Some(index) = self
161                .current
162                .iter()
163                .position(|byte| *byte == b'\n' || *byte == b'\r')
164            {
165                let (line, newline_plus_remainder) = self.current.split_at(index);
166                self.current = &newline_plus_remainder[1..];
167                Some(line)
168            } else if !self.current.is_empty() {
169                let next_line = self.current;
170                self.current = &[];
171                Some(next_line)
172            } else {
173                None
174            };
175
176            match read(next_line, &mut section, &mut self.b64_buf)? {
177                ControlFlow::Continue(()) => continue,
178                ControlFlow::Break(item) => return Ok(item),
179            }
180        }
181    }
182
183    #[doc(hidden)]
188    pub fn remainder(&self) -> &'a [u8] {
189        self.current
190    }
191}
192
193impl<T: PemObject> Iterator for SliceIter<'_, T> {
194    type Item = Result<T, Error>;
195
196    fn next(&mut self) -> Option<Self::Item> {
197        loop {
198            return match self.read_section() {
199                Ok(Some((sec, item))) => match T::from_pem(sec, item) {
200                    Some(res) => Some(Ok(res)),
201                    None => continue,
202                },
203                Ok(None) => return None,
204                Err(err) => Some(Err(err)),
205            };
206        }
207    }
208}
209
210impl PemObject for (SectionKind, Vec<u8>) {
211    fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self> {
212        Some((kind, der))
213    }
214}
215
216#[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 line = Vec::with_capacity(80);
225    from_buf_inner(rd, &mut line, &mut b64buf)
226}
227
228#[cfg(feature = "std")]
229fn from_buf_inner(
230    rd: &mut dyn io::BufRead,
231    line: &mut Vec<u8>,
232    b64buf: &mut Vec<u8>,
233) -> Result<Option<(SectionKind, Vec<u8>)>, Error> {
234    let mut section = None;
235    loop {
236        line.clear();
237        let len = read_until_newline(rd, line).map_err(Error::Io)?;
238
239        let next_line = if len == 0 {
240            None
241        } else {
242            Some(line.as_slice())
243        };
244
245        match read(next_line, &mut section, b64buf) {
246            Ok(ControlFlow::Break(opt)) => return Ok(opt),
247            Ok(ControlFlow::Continue(())) => continue,
248            Err(e) => return Err(e),
249        }
250    }
251}
252
253#[allow(clippy::type_complexity)]
254fn read(
255    next_line: Option<&[u8]>,
256    section: &mut Option<SectionLabel>,
257    b64buf: &mut Vec<u8>,
258) -> Result<ControlFlow<Option<(SectionKind, Vec<u8>)>, ()>, Error> {
259    let line = if let Some(line) = next_line {
260        line
261    } else {
262        return match section.take() {
264            Some(label) => Err(Error::MissingSectionEnd {
265                end_marker: label.as_ref().to_vec(),
266            }),
267            None => Ok(ControlFlow::Break(None)),
268        };
269    };
270
271    if line.starts_with(b"-----BEGIN ") {
272        let (mut trailer, mut pos) = (0, line.len());
273        for (i, &b) in line.iter().enumerate().rev() {
274            match b {
275                b'-' => {
276                    trailer += 1;
277                    pos = i;
278                }
279                b'\n' | b'\r' | b' ' => continue,
280                _ => break,
281            }
282        }
283
284        if trailer != 5 {
285            return Err(Error::IllegalSectionStart {
286                line: line.to_vec(),
287            });
288        }
289
290        let ty = &line[11..pos];
291        *section = Some(SectionLabel::from(ty));
292        return Ok(ControlFlow::Continue(()));
293    }
294
295    if let Some(label) = section.as_ref() {
296        if label.is_end(line) {
297            let kind = match label {
298                SectionLabel::Known(kind) => *kind,
299                SectionLabel::Unknown(_) => {
301                    *section = None;
302                    b64buf.clear();
303                    return Ok(ControlFlow::Continue(()));
304                }
305            };
306
307            let mut der = vec![0u8; base64::decoded_length(b64buf.len())];
308            let der_len = match kind.secret() {
309                true => base64::decode_secret(b64buf, &mut der),
310                false => base64::decode_public(b64buf, &mut der),
311            }
312            .map_err(|err| Error::Base64Decode(format!("{err:?}")))?
313            .len();
314
315            der.truncate(der_len);
316
317            return Ok(ControlFlow::Break(Some((kind, der))));
318        }
319    }
320
321    if section.is_some() {
322        b64buf.extend(line);
323    }
324
325    Ok(ControlFlow::Continue(()))
326}
327
328enum SectionLabel {
329    Known(SectionKind),
330    Unknown(Vec<u8>),
331}
332
333impl SectionLabel {
334    fn is_end(&self, line: &[u8]) -> bool {
335        let rest = match line.strip_prefix(b"-----END ") {
336            Some(rest) => rest,
337            None => return false,
338        };
339
340        let ty = match self {
341            Self::Known(kind) => kind.as_slice(),
342            Self::Unknown(ty) => ty,
343        };
344
345        let rest = match rest.strip_prefix(ty) {
346            Some(rest) => rest,
347            None => return false,
348        };
349
350        rest.starts_with(b"-----")
351    }
352}
353
354impl From<&[u8]> for SectionLabel {
355    fn from(value: &[u8]) -> Self {
356        match SectionKind::try_from(value) {
357            Ok(kind) => Self::Known(kind),
358            Err(_) => Self::Unknown(value.to_vec()),
359        }
360    }
361}
362
363impl AsRef<[u8]> for SectionLabel {
364    fn as_ref(&self) -> &[u8] {
365        match self {
366            Self::Known(kind) => kind.as_slice(),
367            Self::Unknown(ty) => ty,
368        }
369    }
370}
371
372#[non_exhaustive]
374#[derive(Clone, Copy, Debug, PartialEq)]
375pub enum SectionKind {
376    Certificate,
380
381    PublicKey,
385
386    RsaPrivateKey,
390
391    PrivateKey,
395
396    EcPrivateKey,
400
401    Crl,
405
406    Csr,
410
411    EchConfigList,
416}
417
418impl SectionKind {
419    const fn secret(&self) -> bool {
420        match self {
421            Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true,
422            Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => {
423                false
424            }
425        }
426    }
427
428    fn as_slice(&self) -> &'static [u8] {
429        match self {
430            Self::Certificate => b"CERTIFICATE",
431            Self::PublicKey => b"PUBLIC KEY",
432            Self::RsaPrivateKey => b"RSA PRIVATE KEY",
433            Self::PrivateKey => b"PRIVATE KEY",
434            Self::EcPrivateKey => b"EC PRIVATE KEY",
435            Self::Crl => b"X509 CRL",
436            Self::Csr => b"CERTIFICATE REQUEST",
437            Self::EchConfigList => b"ECHCONFIG",
438        }
439    }
440}
441
442impl TryFrom<&[u8]> for SectionKind {
443    type Error = ();
444
445    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
446        Ok(match value {
447            b"CERTIFICATE" => Self::Certificate,
448            b"PUBLIC KEY" => Self::PublicKey,
449            b"RSA PRIVATE KEY" => Self::RsaPrivateKey,
450            b"PRIVATE KEY" => Self::PrivateKey,
451            b"EC PRIVATE KEY" => Self::EcPrivateKey,
452            b"X509 CRL" => Self::Crl,
453            b"CERTIFICATE REQUEST" => Self::Csr,
454            b"ECHCONFIG" => Self::EchConfigList,
455            _ => return Err(()),
456        })
457    }
458}
459
460#[non_exhaustive]
462#[derive(Debug)]
463pub enum Error {
464    MissingSectionEnd {
466        end_marker: Vec<u8>,
468    },
469
470    IllegalSectionStart {
472        line: Vec<u8>,
474    },
475
476    Base64Decode(String),
478
479    #[cfg(feature = "std")]
481    Io(io::Error),
482
483    NoItemsFound,
485}
486
487impl fmt::Display for Error {
488    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489        match self {
490            Self::MissingSectionEnd { end_marker } => {
491                write!(f, "missing section end marker: {end_marker:?}")
492            }
493            Self::IllegalSectionStart { line } => {
494                write!(f, "illegal section start: {line:?}")
495            }
496            Self::Base64Decode(e) => write!(f, "base64 decode error: {e}"),
497            #[cfg(feature = "std")]
498            Self::Io(e) => write!(f, "I/O error: {e}"),
499            Self::NoItemsFound => write!(f, "no items found"),
500        }
501    }
502}
503
504#[cfg(feature = "std")]
505impl std::error::Error for Error {}
506
507#[cfg(feature = "std")]
510fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> io::Result<usize> {
511    let mut read = 0;
512    loop {
513        let (done, used) = {
514            let available = match r.fill_buf() {
515                Ok(n) => n,
516                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
517                Err(e) => return Err(e),
518            };
519            match available
520                .iter()
521                .copied()
522                .position(|b| b == b'\n' || b == b'\r')
523            {
524                Some(i) => {
525                    buf.extend_from_slice(&available[..=i]);
526                    (true, i + 1)
527                }
528                None => {
529                    buf.extend_from_slice(available);
530                    (false, available.len())
531                }
532            }
533        };
534        r.consume(used);
535        read += used;
536        if done || used == 0 {
537            return Ok(read);
538        }
539    }
540}