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 }
335
336 Ok(ControlFlow::Continue(()))
337}
338
339enum SectionLabel {
340 Known(SectionKind),
341 Unknown(Vec<u8>),
342}
343
344impl SectionLabel {
345 fn is_end(&self, line: &[u8]) -> bool {
346 let rest = match line.strip_prefix(b"-----END ") {
347 Some(rest) => rest,
348 None => return false,
349 };
350
351 let ty = match self {
352 Self::Known(kind) => kind.as_slice(),
353 Self::Unknown(ty) => ty,
354 };
355
356 let rest = match rest.strip_prefix(ty) {
357 Some(rest) => rest,
358 None => return false,
359 };
360
361 rest.starts_with(b"-----")
362 }
363}
364
365impl From<&[u8]> for SectionLabel {
366 fn from(value: &[u8]) -> Self {
367 match SectionKind::try_from(value) {
368 Ok(kind) => Self::Known(kind),
369 Err(_) => Self::Unknown(value.to_vec()),
370 }
371 }
372}
373
374impl AsRef<[u8]> for SectionLabel {
375 fn as_ref(&self) -> &[u8] {
376 match self {
377 Self::Known(kind) => kind.as_slice(),
378 Self::Unknown(ty) => ty,
379 }
380 }
381}
382
383#[non_exhaustive]
385#[derive(Clone, Copy, Debug, PartialEq)]
386pub enum SectionKind {
387 Certificate,
391
392 PublicKey,
396
397 RsaPrivateKey,
401
402 PrivateKey,
406
407 EcPrivateKey,
411
412 Crl,
416
417 Csr,
421
422 EchConfigList,
427}
428
429impl SectionKind {
430 const fn secret(&self) -> bool {
431 match self {
432 Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true,
433 Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => {
434 false
435 }
436 }
437 }
438
439 fn as_slice(&self) -> &'static [u8] {
440 match self {
441 Self::Certificate => b"CERTIFICATE",
442 Self::PublicKey => b"PUBLIC KEY",
443 Self::RsaPrivateKey => b"RSA PRIVATE KEY",
444 Self::PrivateKey => b"PRIVATE KEY",
445 Self::EcPrivateKey => b"EC PRIVATE KEY",
446 Self::Crl => b"X509 CRL",
447 Self::Csr => b"CERTIFICATE REQUEST",
448 Self::EchConfigList => b"ECHCONFIG",
449 }
450 }
451}
452
453impl TryFrom<&[u8]> for SectionKind {
454 type Error = ();
455
456 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
457 Ok(match value {
458 b"CERTIFICATE" => Self::Certificate,
459 b"PUBLIC KEY" => Self::PublicKey,
460 b"RSA PRIVATE KEY" => Self::RsaPrivateKey,
461 b"PRIVATE KEY" => Self::PrivateKey,
462 b"EC PRIVATE KEY" => Self::EcPrivateKey,
463 b"X509 CRL" => Self::Crl,
464 b"CERTIFICATE REQUEST" => Self::Csr,
465 b"ECHCONFIG" => Self::EchConfigList,
466 _ => return Err(()),
467 })
468 }
469}
470
471#[non_exhaustive]
473#[derive(Debug)]
474pub enum Error {
475 MissingSectionEnd {
477 end_marker: Vec<u8>,
479 },
480
481 IllegalSectionStart {
483 line: Vec<u8>,
485 },
486
487 Base64Decode(String),
489
490 #[cfg(feature = "std")]
492 Io(io::Error),
493
494 NoItemsFound,
496}
497
498impl fmt::Display for Error {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 match self {
501 Self::MissingSectionEnd { end_marker } => {
502 write!(f, "missing section end marker: {end_marker:?}")
503 }
504 Self::IllegalSectionStart { line } => {
505 write!(f, "illegal section start: {line:?}")
506 }
507 Self::Base64Decode(e) => write!(f, "base64 decode error: {e}"),
508 #[cfg(feature = "std")]
509 Self::Io(e) => write!(f, "I/O error: {e}"),
510 Self::NoItemsFound => write!(f, "no items found"),
511 }
512 }
513}
514
515#[cfg(feature = "std")]
516impl std::error::Error for Error {}
517
518#[cfg(feature = "std")]
521fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> io::Result<usize> {
522 let mut read = 0;
523 loop {
524 let (done, used) = {
525 let available = match r.fill_buf() {
526 Ok(n) => n,
527 Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
528 Err(e) => return Err(e),
529 };
530 match available
531 .iter()
532 .copied()
533 .position(|b| b == b'\n' || b == b'\r')
534 {
535 Some(i) => {
536 buf.extend_from_slice(&available[..=i]);
537 (true, i + 1)
538 }
539 None => {
540 buf.extend_from_slice(available);
541 (false, available.len())
542 }
543 }
544 };
545 r.consume(used);
546 read += used;
547 if done || used == 0 {
548 return Ok(read);
549 }
550 }
551}