1 | use alloc::borrow::ToOwned; |
2 | use alloc::format; |
3 | use alloc::string::String; |
4 | use alloc::vec; |
5 | use alloc::vec::Vec; |
6 | use core::fmt; |
7 | use core::marker::PhantomData; |
8 | use core::ops::ControlFlow; |
9 | #[cfg (feature = "std" )] |
10 | use std::fs::File; |
11 | #[cfg (feature = "std" )] |
12 | use std::io::{self, ErrorKind}; |
13 | |
14 | use crate::base64; |
15 | |
16 | /// Items that can be decoded from PEM data. |
17 | pub trait PemObject: Sized { |
18 | /// Decode the first section of this type from PEM contained in |
19 | /// a byte slice. |
20 | /// |
21 | /// [`Error::NoItemsFound`] is returned if no such items are found. |
22 | 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 | /// Iterate over all sections of this type from PEM contained in |
29 | /// a byte slice. |
30 | fn pem_slice_iter(pem: &[u8]) -> SliceIter<'_, Self> { |
31 | SliceIter { |
32 | current: pem, |
33 | _ty: PhantomData, |
34 | } |
35 | } |
36 | |
37 | /// Decode the first section of this type from the PEM contents of the named file. |
38 | /// |
39 | /// [`Error::NoItemsFound`] is returned if no such items are found. |
40 | #[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 | /// Iterate over all sections of this type from the PEM contents of the named file. |
48 | /// |
49 | /// This reports errors in two phases: |
50 | /// |
51 | /// - errors opening the file are reported from this function directly, |
52 | /// - errors reading from the file are reported from the returned iterator, |
53 | #[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 | /// Decode the first section of this type from PEM read from an [`io::Read`]. |
64 | #[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 | /// Iterate over all sections of this type from PEM present in an [`io::Read`]. |
72 | #[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 | /// Conversion from a PEM [`SectionKind`] and body data. |
81 | /// |
82 | /// This inspects `kind`, and if it matches this type's PEM section kind, |
83 | /// converts `der` into this type. |
84 | fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self>; |
85 | } |
86 | |
87 | pub(crate) trait PemObjectFilter: PemObject + From<Vec<u8>> { |
88 | const KIND: SectionKind; |
89 | } |
90 | |
91 | impl<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 | /// Extract and return all PEM sections by reading `rd`. |
101 | #[cfg (feature = "std" )] |
102 | pub struct ReadIter<R, T> { |
103 | rd: R, |
104 | _ty: PhantomData<T>, |
105 | } |
106 | |
107 | #[cfg (feature = "std" )] |
108 | impl<R: io::BufRead, T: PemObject> ReadIter<R, T> { |
109 | /// Create a new iterator. |
110 | pub fn new(rd: R) -> Self { |
111 | Self { |
112 | rd, |
113 | _ty: PhantomData, |
114 | } |
115 | } |
116 | } |
117 | |
118 | #[cfg (feature = "std" )] |
119 | impl<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: SectionKind, item: Vec))) => match T::from_pem(kind:sec, der:item) { |
126 | Some(res: T) => Some(Ok(res)), |
127 | None => continue, |
128 | }, |
129 | Ok(None) => return None, |
130 | Err(err: Error) => Some(Err(err)), |
131 | }; |
132 | } |
133 | } |
134 | } |
135 | |
136 | /// Iterator over all PEM sections in a `&[u8]` slice. |
137 | pub struct SliceIter<'a, T> { |
138 | current: &'a [u8], |
139 | _ty: PhantomData<T>, |
140 | } |
141 | |
142 | impl<'a, T: PemObject> SliceIter<'a, T> { |
143 | /// Create a new iterator. |
144 | pub fn new(current: &'a [u8]) -> Self { |
145 | Self { |
146 | current, |
147 | _ty: PhantomData, |
148 | } |
149 | } |
150 | |
151 | /// Returns the rest of the unparsed data. |
152 | /// |
153 | /// This is the slice immediately following the most |
154 | /// recently returned item from `next()`. |
155 | #[doc (hidden)] |
156 | pub fn remainder(&self) -> &'a [u8] { |
157 | self.current |
158 | } |
159 | } |
160 | |
161 | impl<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: SectionKind, item: Vec), rest: &[u8]))) => { |
168 | self.current = rest; |
169 | match T::from_pem(kind:sec, der:item) { |
170 | Some(res: T) => Some(Ok(res)), |
171 | None => continue, |
172 | } |
173 | } |
174 | Ok(None) => return None, |
175 | Err(err: Error) => Some(Err(err)), |
176 | }; |
177 | } |
178 | } |
179 | } |
180 | |
181 | impl PemObject for (SectionKind, Vec<u8>) { |
182 | fn from_pem(kind: SectionKind, der: Vec<u8>) -> Option<Self> { |
183 | Some((kind, der)) |
184 | } |
185 | } |
186 | |
187 | /// Extract and decode the next supported PEM section from `input` |
188 | /// |
189 | /// - `Ok(None)` is returned if there is no PEM section to read from `input` |
190 | /// - Syntax errors and decoding errors produce a `Err(...)` |
191 | /// - Otherwise each decoded section is returned with a `Ok(Some((..., remainder)))` where |
192 | /// `remainder` is the part of the `input` that follows the returned section |
193 | #[allow (clippy::type_complexity)] |
194 | fn 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 if !input.is_empty() { |
207 | let next_line = input; |
208 | input = &[]; |
209 | Some(next_line) |
210 | } else { |
211 | None |
212 | }; |
213 | |
214 | match read(next_line, &mut section, &mut b64buf)? { |
215 | ControlFlow::Continue(()) => continue, |
216 | ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))), |
217 | } |
218 | } |
219 | } |
220 | |
221 | /// Extract and decode the next supported PEM section from `rd`. |
222 | /// |
223 | /// - Ok(None) is returned if there is no PEM section read from `rd`. |
224 | /// - Underlying IO errors produce a `Err(...)` |
225 | /// - Otherwise each decoded section is returned with a `Ok(Some(...))` |
226 | #[cfg (feature = "std" )] |
227 | pub fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<(SectionKind, Vec<u8>)>, Error> { |
228 | let mut b64buf: Vec = Vec::with_capacity(1024); |
229 | let mut section: Option<(Vec, Vec)> = None::<(Vec<_>, Vec<_>)>; |
230 | let mut line: Vec = Vec::with_capacity(80); |
231 | |
232 | loop { |
233 | line.clear(); |
234 | let len: usize = read_until_newline(rd, &mut line).map_err(op:Error::Io)?; |
235 | |
236 | let next_line: Option<&[u8]> = if len == 0 { |
237 | None |
238 | } else { |
239 | Some(line.as_slice()) |
240 | }; |
241 | |
242 | match read(next_line, &mut section, &mut b64buf) { |
243 | Ok(ControlFlow::Break(opt: Option<(SectionKind, Vec<…>)>)) => return Ok(opt), |
244 | Ok(ControlFlow::Continue(())) => continue, |
245 | Err(e: Error) => return Err(e), |
246 | } |
247 | } |
248 | } |
249 | |
250 | #[allow (clippy::type_complexity)] |
251 | fn read( |
252 | next_line: Option<&[u8]>, |
253 | section: &mut Option<(Vec<u8>, Vec<u8>)>, |
254 | b64buf: &mut Vec<u8>, |
255 | ) -> Result<ControlFlow<Option<(SectionKind, Vec<u8>)>, ()>, Error> { |
256 | let line = if let Some(line) = next_line { |
257 | line |
258 | } else { |
259 | // EOF |
260 | return match section.take() { |
261 | Some((_, end_marker)) => Err(Error::MissingSectionEnd { end_marker }), |
262 | None => Ok(ControlFlow::Break(None)), |
263 | }; |
264 | }; |
265 | |
266 | if line.starts_with(b"-----BEGIN " ) { |
267 | let (mut trailer, mut pos) = (0, line.len()); |
268 | for (i, &b) in line.iter().enumerate().rev() { |
269 | match b { |
270 | b'-' => { |
271 | trailer += 1; |
272 | pos = i; |
273 | } |
274 | b' \n' | b' \r' | b' ' => continue, |
275 | _ => break, |
276 | } |
277 | } |
278 | |
279 | if trailer != 5 { |
280 | return Err(Error::IllegalSectionStart { |
281 | line: line.to_vec(), |
282 | }); |
283 | } |
284 | |
285 | let ty = &line[11..pos]; |
286 | let mut end = Vec::with_capacity(10 + 4 + ty.len()); |
287 | end.extend_from_slice(b"-----END " ); |
288 | end.extend_from_slice(ty); |
289 | end.extend_from_slice(b"-----" ); |
290 | *section = Some((ty.to_owned(), end)); |
291 | return Ok(ControlFlow::Continue(())); |
292 | } |
293 | |
294 | if let Some((section_label, end_marker)) = section.as_ref() { |
295 | if line.starts_with(end_marker) { |
296 | let kind = match SectionKind::try_from(§ion_label[..]) { |
297 | Ok(kind) => kind, |
298 | // unhandled section: have caller try again |
299 | Err(()) => { |
300 | *section = None; |
301 | b64buf.clear(); |
302 | return Ok(ControlFlow::Continue(())); |
303 | } |
304 | }; |
305 | |
306 | let mut der = vec![0u8; base64::decoded_length(b64buf.len())]; |
307 | let der_len = match kind.secret() { |
308 | true => base64::decode_secret(b64buf, &mut der), |
309 | false => base64::decode_public(b64buf, &mut der), |
310 | } |
311 | .map_err(|err| Error::Base64Decode(format!(" {err:?}" )))? |
312 | .len(); |
313 | |
314 | der.truncate(der_len); |
315 | |
316 | return Ok(ControlFlow::Break(Some((kind, der)))); |
317 | } |
318 | } |
319 | |
320 | if section.is_some() { |
321 | b64buf.extend(line); |
322 | } |
323 | |
324 | Ok(ControlFlow::Continue(())) |
325 | } |
326 | |
327 | /// A single recognised section in a PEM file. |
328 | #[non_exhaustive ] |
329 | #[derive (Clone, Copy, Debug, PartialEq)] |
330 | pub enum SectionKind { |
331 | /// A DER-encoded x509 certificate. |
332 | /// |
333 | /// Appears as "CERTIFICATE" in PEM files. |
334 | Certificate, |
335 | |
336 | /// A DER-encoded Subject Public Key Info; as specified in RFC 7468. |
337 | /// |
338 | /// Appears as "PUBLIC KEY" in PEM files. |
339 | PublicKey, |
340 | |
341 | /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447 |
342 | /// |
343 | /// Appears as "RSA PRIVATE KEY" in PEM files. |
344 | RsaPrivateKey, |
345 | |
346 | /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958 |
347 | /// |
348 | /// Appears as "PRIVATE KEY" in PEM files. |
349 | PrivateKey, |
350 | |
351 | /// A Sec1-encoded plaintext private key; as specified in RFC 5915 |
352 | /// |
353 | /// Appears as "EC PRIVATE KEY" in PEM files. |
354 | EcPrivateKey, |
355 | |
356 | /// A Certificate Revocation List; as specified in RFC 5280 |
357 | /// |
358 | /// Appears as "X509 CRL" in PEM files. |
359 | Crl, |
360 | |
361 | /// A Certificate Signing Request; as specified in RFC 2986 |
362 | /// |
363 | /// Appears as "CERTIFICATE REQUEST" in PEM files. |
364 | Csr, |
365 | |
366 | /// An EchConfigList structure, as specified in |
367 | /// <https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-05.html>. |
368 | /// |
369 | /// Appears as "ECHCONFIG" in PEM files. |
370 | EchConfigList, |
371 | } |
372 | |
373 | impl SectionKind { |
374 | fn secret(&self) -> bool { |
375 | match self { |
376 | Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true, |
377 | Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => { |
378 | false |
379 | } |
380 | } |
381 | } |
382 | } |
383 | |
384 | impl TryFrom<&[u8]> for SectionKind { |
385 | type Error = (); |
386 | |
387 | fn try_from(value: &[u8]) -> Result<Self, Self::Error> { |
388 | Ok(match value { |
389 | b"CERTIFICATE" => Self::Certificate, |
390 | b"PUBLIC KEY" => Self::PublicKey, |
391 | b"RSA PRIVATE KEY" => Self::RsaPrivateKey, |
392 | b"PRIVATE KEY" => Self::PrivateKey, |
393 | b"EC PRIVATE KEY" => Self::EcPrivateKey, |
394 | b"X509 CRL" => Self::Crl, |
395 | b"CERTIFICATE REQUEST" => Self::Csr, |
396 | b"ECHCONFIG" => Self::EchConfigList, |
397 | _ => return Err(()), |
398 | }) |
399 | } |
400 | } |
401 | |
402 | /// Errors that may arise when parsing the contents of a PEM file |
403 | #[non_exhaustive ] |
404 | #[derive (Debug)] |
405 | pub enum Error { |
406 | /// a section is missing its "END marker" line |
407 | MissingSectionEnd { |
408 | /// the expected "END marker" line that was not found |
409 | end_marker: Vec<u8>, |
410 | }, |
411 | |
412 | /// syntax error found in the line that starts a new section |
413 | IllegalSectionStart { |
414 | /// line that contains the syntax error |
415 | line: Vec<u8>, |
416 | }, |
417 | |
418 | /// base64 decode error |
419 | Base64Decode(String), |
420 | |
421 | /// I/O errors, from APIs that accept `std::io` types. |
422 | #[cfg (feature = "std" )] |
423 | Io(io::Error), |
424 | |
425 | /// No items found of desired type |
426 | NoItemsFound, |
427 | } |
428 | |
429 | impl fmt::Display for Error { |
430 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
431 | match self { |
432 | Self::MissingSectionEnd { end_marker: &Vec } => { |
433 | write!(f, "missing section end marker: {end_marker:?}" ) |
434 | } |
435 | Self::IllegalSectionStart { line: &Vec } => { |
436 | write!(f, "illegal section start: {line:?}" ) |
437 | } |
438 | Self::Base64Decode(e: &String) => write!(f, "base64 decode error: {e}" ), |
439 | #[cfg (feature = "std" )] |
440 | Self::Io(e: &Error) => write!(f, "I/O error: {e}" ), |
441 | Self::NoItemsFound => write!(f, "no items found" ), |
442 | } |
443 | } |
444 | } |
445 | |
446 | #[cfg (feature = "std" )] |
447 | impl std::error::Error for Error {} |
448 | |
449 | // Ported from https://github.com/rust-lang/rust/blob/91cfcb021935853caa06698b759c293c09d1e96a/library/std/src/io/mod.rs#L1990 and |
450 | // modified to look for our accepted newlines. |
451 | #[cfg (feature = "std" )] |
452 | fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> io::Result<usize> { |
453 | let mut read = 0; |
454 | loop { |
455 | let (done, used) = { |
456 | let available = match r.fill_buf() { |
457 | Ok(n) => n, |
458 | Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, |
459 | Err(e) => return Err(e), |
460 | }; |
461 | match available |
462 | .iter() |
463 | .copied() |
464 | .position(|b| b == b' \n' || b == b' \r' ) |
465 | { |
466 | Some(i) => { |
467 | buf.extend_from_slice(&available[..=i]); |
468 | (true, i + 1) |
469 | } |
470 | None => { |
471 | buf.extend_from_slice(available); |
472 | (false, available.len()) |
473 | } |
474 | } |
475 | }; |
476 | r.consume(used); |
477 | read += used; |
478 | if done || used == 0 { |
479 | return Ok(read); |
480 | } |
481 | } |
482 | } |
483 | |