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
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 {
31 current: pem,
32 _ty: PhantomData,
33 }
34 }
35
36 #[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 #[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 #[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 #[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 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#[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 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
135pub struct SliceIter<'a, T> {
137 current: &'a [u8],
138 _ty: PhantomData<T>,
139}
140
141impl<'a, T: PemObject> SliceIter<'a, T> {
142 pub fn new(current: &'a [u8]) -> Self {
144 Self {
145 current,
146 _ty: PhantomData,
147 }
148 }
149
150 #[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#[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#[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 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(§ion_label[..]) {
292 Ok(kind) => kind,
293 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#[non_exhaustive]
324#[derive(Clone, Copy, Debug, PartialEq)]
325pub enum SectionKind {
326 Certificate,
330
331 PublicKey,
335
336 RsaPrivateKey,
340
341 PrivateKey,
345
346 EcPrivateKey,
350
351 Crl,
355
356 Csr,
360
361 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#[non_exhaustive]
399#[derive(Debug)]
400pub enum Error {
401 MissingSectionEnd {
403 end_marker: Vec<u8>,
405 },
406
407 IllegalSectionStart {
409 line: Vec<u8>,
411 },
412
413 Base64Decode(String),
415
416 #[cfg(feature = "std")]
418 Io(io::Error),
419
420 NoItemsFound,
422}
423
424#[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}