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 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: 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 done: bool,
101}
102
103#[cfg(feature = "std")]
104impl<R: io::BufRead, T: PemObject> ReadIter<R, T> {
105 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
144pub 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 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 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 #[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#[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 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 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
342const 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#[non_exhaustive]
392#[derive(Clone, Copy, Debug, PartialEq)]
393pub enum SectionKind {
394 Certificate,
398
399 PublicKey,
403
404 RsaPrivateKey,
408
409 PrivateKey,
413
414 EcPrivateKey,
418
419 Crl,
423
424 Csr,
428
429 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#[non_exhaustive]
480#[derive(Debug)]
481pub enum Error {
482 MissingSectionEnd {
484 end_marker: Vec<u8>,
486 },
487
488 IllegalSectionStart {
490 line: Vec<u8>,
492 },
493
494 Base64Decode(String),
496
497 #[cfg(feature = "std")]
499 Io(io::Error),
500
501 NoItemsFound,
503
504 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#[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}