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
16pub trait PemObject: Sized {
18 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 fn pem_slice_iter(pem: &[u8]) -> SliceIter<'_, Self> {
31 SliceIter {
32 current: pem,
33 _ty: PhantomData,
34 }
35 }
36
37 #[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 #[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 #[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 #[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 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#[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 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
136pub struct SliceIter<'a, T> {
138 current: &'a [u8],
139 _ty: PhantomData<T>,
140}
141
142impl<'a, T: PemObject> SliceIter<'a, T> {
143 pub fn new(current: &'a [u8]) -> Self {
145 Self {
146 current,
147 _ty: PhantomData,
148 }
149 }
150
151 #[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#[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#[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 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(§ion_label[..]) {
293 Ok(kind) => kind,
294 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#[non_exhaustive]
325#[derive(Clone, Copy, Debug, PartialEq)]
326pub enum SectionKind {
327 Certificate,
331
332 PublicKey,
336
337 RsaPrivateKey,
341
342 PrivateKey,
346
347 EcPrivateKey,
351
352 Crl,
356
357 Csr,
361
362 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#[non_exhaustive]
400#[derive(Debug)]
401pub enum Error {
402 MissingSectionEnd {
404 end_marker: Vec<u8>,
406 },
407
408 IllegalSectionStart {
410 line: Vec<u8>,
412 },
413
414 Base64Decode(String),
416
417 #[cfg(feature = "std")]
419 Io(io::Error),
420
421 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#[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}