| 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 | |