| 1 | use alloc::format; |
| 2 | use alloc::string::String; |
| 3 | use alloc::vec::Vec; |
| 4 | #[cfg (feature = "std" )] |
| 5 | use core::iter; |
| 6 | #[cfg (feature = "std" )] |
| 7 | use std::io::{self, ErrorKind}; |
| 8 | |
| 9 | use pki_types::{ |
| 10 | pem, CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer, |
| 11 | PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer, |
| 12 | }; |
| 13 | |
| 14 | /// The contents of a single recognised block in a PEM file. |
| 15 | #[non_exhaustive ] |
| 16 | #[derive (Debug, PartialEq)] |
| 17 | pub enum Item { |
| 18 | /// A DER-encoded x509 certificate. |
| 19 | /// |
| 20 | /// Appears as "CERTIFICATE" in PEM files. |
| 21 | X509Certificate(CertificateDer<'static>), |
| 22 | |
| 23 | /// A DER-encoded Subject Public Key Info; as specified in RFC 7468. |
| 24 | /// |
| 25 | /// Appears as "PUBLIC KEY" in PEM files. |
| 26 | SubjectPublicKeyInfo(SubjectPublicKeyInfoDer<'static>), |
| 27 | |
| 28 | /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447 |
| 29 | /// |
| 30 | /// Appears as "RSA PRIVATE KEY" in PEM files. |
| 31 | Pkcs1Key(PrivatePkcs1KeyDer<'static>), |
| 32 | |
| 33 | /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958 |
| 34 | /// |
| 35 | /// Appears as "PRIVATE KEY" in PEM files. |
| 36 | Pkcs8Key(PrivatePkcs8KeyDer<'static>), |
| 37 | |
| 38 | /// A Sec1-encoded plaintext private key; as specified in RFC 5915 |
| 39 | /// |
| 40 | /// Appears as "EC PRIVATE KEY" in PEM files. |
| 41 | Sec1Key(PrivateSec1KeyDer<'static>), |
| 42 | |
| 43 | /// A Certificate Revocation List; as specified in RFC 5280 |
| 44 | /// |
| 45 | /// Appears as "X509 CRL" in PEM files. |
| 46 | Crl(CertificateRevocationListDer<'static>), |
| 47 | |
| 48 | /// A Certificate Signing Request; as specified in RFC 2986 |
| 49 | /// |
| 50 | /// Appears as "CERTIFICATE REQUEST" in PEM files. |
| 51 | Csr(CertificateSigningRequestDer<'static>), |
| 52 | } |
| 53 | |
| 54 | impl Item { |
| 55 | #[cfg (feature = "std" )] |
| 56 | fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<Self>, pem::Error> { |
| 57 | loop { |
| 58 | match pem::from_buf(rd)? { |
| 59 | Some((kind, data)) => match Self::from_kind(kind, data) { |
| 60 | Some(item) => return Ok(Some(item)), |
| 61 | None => continue, |
| 62 | }, |
| 63 | |
| 64 | None => return Ok(None), |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | fn from_slice(pem: &[u8]) -> Result<Option<(Self, &[u8])>, pem::Error> { |
| 70 | let mut iter = <(pem::SectionKind, Vec<u8>) as pem::PemObject>::pem_slice_iter(pem); |
| 71 | |
| 72 | for found in iter.by_ref() { |
| 73 | match found { |
| 74 | Ok((kind, data)) => match Self::from_kind(kind, data) { |
| 75 | Some(item) => return Ok(Some((item, iter.remainder()))), |
| 76 | None => continue, |
| 77 | }, |
| 78 | Err(err) => return Err(err.into()), |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | Ok(None) |
| 83 | } |
| 84 | |
| 85 | fn from_kind(kind: pem::SectionKind, data: Vec<u8>) -> Option<Self> { |
| 86 | use pem::SectionKind::*; |
| 87 | match kind { |
| 88 | Certificate => Some(Self::X509Certificate(data.into())), |
| 89 | PublicKey => Some(Self::SubjectPublicKeyInfo(data.into())), |
| 90 | RsaPrivateKey => Some(Self::Pkcs1Key(data.into())), |
| 91 | PrivateKey => Some(Self::Pkcs8Key(data.into())), |
| 92 | EcPrivateKey => Some(Self::Sec1Key(data.into())), |
| 93 | Crl => Some(Self::Crl(data.into())), |
| 94 | Csr => Some(Self::Csr(data.into())), |
| 95 | _ => None, |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | /// Errors that may arise when parsing the contents of a PEM file |
| 101 | /// |
| 102 | /// This differs from [`rustls_pki_types::pem::Error`] because it is `PartialEq`; |
| 103 | /// it is retained for compatibility. |
| 104 | #[derive (Debug, PartialEq)] |
| 105 | pub enum Error { |
| 106 | /// a section is missing its "END marker" line |
| 107 | MissingSectionEnd { |
| 108 | /// the expected "END marker" line that was not found |
| 109 | end_marker: Vec<u8>, |
| 110 | }, |
| 111 | |
| 112 | /// syntax error found in the line that starts a new section |
| 113 | IllegalSectionStart { |
| 114 | /// line that contains the syntax error |
| 115 | line: Vec<u8>, |
| 116 | }, |
| 117 | |
| 118 | /// base64 decode error |
| 119 | Base64Decode(String), |
| 120 | } |
| 121 | |
| 122 | #[cfg (feature = "std" )] |
| 123 | impl From<Error> for io::Error { |
| 124 | fn from(error: Error) -> Self { |
| 125 | match error { |
| 126 | Error::MissingSectionEnd { end_marker: Vec } => io::Error::new( |
| 127 | kind:ErrorKind::InvalidData, |
| 128 | error:format!( |
| 129 | "section end {:?} missing" , |
| 130 | String::from_utf8_lossy(&end_marker) |
| 131 | ), |
| 132 | ), |
| 133 | |
| 134 | Error::IllegalSectionStart { line: Vec } => io::Error::new( |
| 135 | kind:ErrorKind::InvalidData, |
| 136 | error:format!( |
| 137 | "illegal section start: {:?}" , |
| 138 | String::from_utf8_lossy(&line) |
| 139 | ), |
| 140 | ), |
| 141 | |
| 142 | Error::Base64Decode(err: String) => io::Error::new(kind:ErrorKind::InvalidData, error:err), |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | impl From<pem::Error> for Error { |
| 148 | fn from(error: pem::Error) -> Self { |
| 149 | match error { |
| 150 | pem::Error::MissingSectionEnd { end_marker: Vec } => Error::MissingSectionEnd { end_marker }, |
| 151 | pem::Error::IllegalSectionStart { line: Vec } => Error::IllegalSectionStart { line }, |
| 152 | pem::Error::Base64Decode(str: String) => Error::Base64Decode(str), |
| 153 | |
| 154 | // this is a necessary bodge to funnel any new errors into our existing type |
| 155 | // (to which we can add no new variants) |
| 156 | other: Error => Error::Base64Decode(format!(" {other:?}" )), |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /// Extract and decode the next PEM section from `input` |
| 162 | /// |
| 163 | /// - `Ok(None)` is returned if there is no PEM section to read from `input` |
| 164 | /// - Syntax errors and decoding errors produce a `Err(...)` |
| 165 | /// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where |
| 166 | /// `remainder` is the part of the `input` that follows the returned section |
| 167 | pub fn read_one_from_slice(input: &[u8]) -> Result<Option<(Item, &[u8])>, Error> { |
| 168 | Item::from_slice(input).map_err(op:Into::into) |
| 169 | } |
| 170 | |
| 171 | /// Extract and decode the next PEM section from `rd`. |
| 172 | /// |
| 173 | /// - Ok(None) is returned if there is no PEM section read from `rd`. |
| 174 | /// - Underlying IO errors produce a `Err(...)` |
| 175 | /// - Otherwise each decoded section is returned with a `Ok(Some(Item::...))` |
| 176 | /// |
| 177 | /// You can use this function to build an iterator, for example: |
| 178 | /// `for item in iter::from_fn(|| read_one(rd).transpose()) { ... }` |
| 179 | #[cfg (feature = "std" )] |
| 180 | pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> { |
| 181 | Item::from_buf(rd).map_err(|err: Error| match err { |
| 182 | pem::Error::Io(io: Error) => io, |
| 183 | other: Error => Error::from(other).into(), |
| 184 | }) |
| 185 | } |
| 186 | |
| 187 | /// Extract and return all PEM sections by reading `rd`. |
| 188 | #[cfg (feature = "std" )] |
| 189 | pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator<Item = Result<Item, io::Error>> + '_ { |
| 190 | iter::from_fn(move || read_one(rd).transpose()) |
| 191 | } |
| 192 | |