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