/rust/registry/src/index.crates.io-1949cf8c6b5b557f/x509-parser-0.15.1/src/pem.rs
Line | Count | Source |
1 | | //! Decoding functions for PEM-encoded data |
2 | | //! |
3 | | //! A PEM object is a container, which can store (amongst other formats) a public X.509 |
4 | | //! Certificate, or a CRL, etc. It contains only printable characters. |
5 | | //! PEM-encoded binary data is essentially a beginning and matching end tag that encloses |
6 | | //! base64-encoded binary data (see: |
7 | | //! <https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail>). |
8 | | //! |
9 | | //! # Examples |
10 | | //! |
11 | | //! To parse a certificate in PEM format, first create the `Pem` object, then decode |
12 | | //! contents: |
13 | | //! |
14 | | //! ```rust,no_run |
15 | | //! use x509_parser::pem::Pem; |
16 | | //! use x509_parser::x509::X509Version; |
17 | | //! |
18 | | //! static IGCA_PEM: &str = "../assets/IGC_A.pem"; |
19 | | //! |
20 | | //! # fn main() { |
21 | | //! let data = std::fs::read(IGCA_PEM).expect("Could not read file"); |
22 | | //! for pem in Pem::iter_from_buffer(&data) { |
23 | | //! let pem = pem.expect("Reading next PEM block failed"); |
24 | | //! let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); |
25 | | //! assert_eq!(x509.tbs_certificate.version, X509Version::V3); |
26 | | //! } |
27 | | //! # } |
28 | | //! ``` |
29 | | //! |
30 | | //! This is the most direct method to parse PEM data. |
31 | | //! |
32 | | //! Another method to parse the certificate is to use `parse_x509_pem`: |
33 | | //! |
34 | | //! ```rust,no_run |
35 | | //! use x509_parser::pem::parse_x509_pem; |
36 | | //! use x509_parser::parse_x509_certificate; |
37 | | //! |
38 | | //! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); |
39 | | //! |
40 | | //! # fn main() { |
41 | | //! let res = parse_x509_pem(IGCA_PEM); |
42 | | //! match res { |
43 | | //! Ok((rem, pem)) => { |
44 | | //! assert!(rem.is_empty()); |
45 | | //! // |
46 | | //! assert_eq!(pem.label, String::from("CERTIFICATE")); |
47 | | //! // |
48 | | //! let res_x509 = parse_x509_certificate(&pem.contents); |
49 | | //! assert!(res_x509.is_ok()); |
50 | | //! }, |
51 | | //! _ => panic!("PEM parsing failed: {:?}", res), |
52 | | //! } |
53 | | //! # } |
54 | | //! ``` |
55 | | //! |
56 | | //! Note that all methods require to store the `Pem` object in a variable, mainly because decoding |
57 | | //! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will |
58 | | //! be bound to these buffers. |
59 | | |
60 | | use crate::certificate::X509Certificate; |
61 | | use crate::error::{PEMError, X509Error}; |
62 | | use crate::parse_x509_certificate; |
63 | | use nom::{Err, IResult}; |
64 | | use std::io::{BufRead, Cursor, Seek}; |
65 | | |
66 | | /// Representation of PEM data |
67 | | #[derive(Clone, PartialEq, Eq, Debug)] |
68 | | pub struct Pem { |
69 | | /// The PEM label |
70 | | pub label: String, |
71 | | /// The PEM decoded data |
72 | | pub contents: Vec<u8>, |
73 | | } |
74 | | |
75 | | #[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")] |
76 | 0 | pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> { |
77 | 0 | parse_x509_pem(i) |
78 | 0 | } |
79 | | |
80 | | /// Read a PEM-encoded structure, and decode the base64 data |
81 | | /// |
82 | | /// Return a structure describing the PEM object: the enclosing tag, and the data. |
83 | | /// Allocates a new buffer for the decoded data. |
84 | | /// |
85 | | /// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data, |
86 | | /// use [`Pem::iter_from_buffer`]. |
87 | | /// |
88 | | /// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the |
89 | | /// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`. |
90 | 0 | pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> { |
91 | 0 | let reader = Cursor::new(i); |
92 | 0 | let res = Pem::read(reader); |
93 | 0 | match res { |
94 | 0 | Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)), |
95 | 0 | Err(e) => Err(Err::Error(e)), |
96 | | } |
97 | 0 | } |
98 | | |
99 | | impl Pem { |
100 | | /// Read the next PEM-encoded structure, and decode the base64 data |
101 | | /// |
102 | | /// Returns the certificate (encoded in DER) and the number of bytes read. |
103 | | /// Allocates a new buffer for the decoded data. |
104 | | /// |
105 | | /// Note that a PEM file can contain multiple PEM blocks. This function returns the |
106 | | /// *first* decoded object, starting from the current reader position. |
107 | | /// To get all objects, call this function repeatedly until `PEMError::MissingHeader` |
108 | | /// is returned. |
109 | | /// |
110 | | /// # Examples |
111 | | /// ``` |
112 | | /// let file = std::fs::File::open("assets/certificate.pem").unwrap(); |
113 | | /// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file)) |
114 | | /// .unwrap().0 |
115 | | /// .parse_x509().unwrap() |
116 | | /// .tbs_certificate.subject.to_string(); |
117 | | /// assert_eq!(subject, "CN=lists.for-our.info"); |
118 | | /// ``` |
119 | 0 | pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> { |
120 | 0 | let mut line = String::new(); |
121 | 0 | let label = loop { |
122 | 0 | let num_bytes = r.read_line(&mut line)?; |
123 | 0 | if num_bytes == 0 { |
124 | | // EOF |
125 | 0 | return Err(PEMError::MissingHeader); |
126 | 0 | } |
127 | 0 | if !line.starts_with("-----BEGIN ") { |
128 | 0 | line.clear(); |
129 | 0 | continue; |
130 | 0 | } |
131 | 0 | let v: Vec<&str> = line.split("-----").collect(); |
132 | 0 | if v.len() < 3 || !v[0].is_empty() { |
133 | 0 | return Err(PEMError::InvalidHeader); |
134 | 0 | } |
135 | 0 | let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?; |
136 | 0 | break label; |
137 | | }; |
138 | 0 | let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?; |
139 | 0 | let mut s = String::new(); |
140 | | loop { |
141 | 0 | let mut l = String::new(); |
142 | 0 | let num_bytes = r.read_line(&mut l)?; |
143 | 0 | if num_bytes == 0 { |
144 | 0 | return Err(PEMError::IncompletePEM); |
145 | 0 | } |
146 | 0 | if l.starts_with("-----END ") { |
147 | | // finished reading |
148 | 0 | break; |
149 | 0 | } |
150 | 0 | s.push_str(l.trim_end()); |
151 | | } |
152 | | |
153 | 0 | let contents = data_encoding::BASE64 |
154 | 0 | .decode(s.as_bytes()) |
155 | 0 | .or(Err(PEMError::Base64DecodeError))?; |
156 | 0 | let pem = Pem { |
157 | 0 | label: label.to_string(), |
158 | 0 | contents, |
159 | 0 | }; |
160 | 0 | Ok((pem, r.stream_position()? as usize)) |
161 | 0 | } |
162 | | |
163 | | /// Decode the PEM contents into a X.509 object |
164 | 0 | pub fn parse_x509(&self) -> Result<X509Certificate, ::nom::Err<X509Error>> { |
165 | 0 | parse_x509_certificate(&self.contents).map(|(_, x509)| x509) |
166 | 0 | } |
167 | | |
168 | | /// Returns an iterator over the PEM-encapsulated parts of a buffer |
169 | | /// |
170 | | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` |
171 | | /// and ending with `-----END xxx-----` will be considered. |
172 | | /// Lines before, between or after such blocks will be ignored. |
173 | | /// |
174 | | /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. |
175 | | /// An error indicates a block is present but invalid. |
176 | | /// |
177 | | /// If the buffer does not contain any block, iterator will be empty. |
178 | 0 | pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> { |
179 | 0 | let reader = Cursor::new(i); |
180 | 0 | PemIterator { reader } |
181 | 0 | } |
182 | | |
183 | | /// Returns an iterator over the PEM-encapsulated parts of a reader |
184 | | /// |
185 | | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` |
186 | | /// and ending with `-----END xxx-----` will be considered. |
187 | | /// Lines before, between or after such blocks will be ignored. |
188 | | /// |
189 | | /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. |
190 | | /// An error indicates a block is present but invalid. |
191 | | /// |
192 | | /// If the reader does not contain any block, iterator will be empty. |
193 | 0 | pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> { |
194 | 0 | PemIterator { reader } |
195 | 0 | } |
196 | | } |
197 | | |
198 | | /// Iterator over PEM-encapsulated blocks |
199 | | /// |
200 | | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` |
201 | | /// and ending with `-----END xxx-----` will be considered. |
202 | | /// Lines before, between or after such blocks will be ignored. |
203 | | /// |
204 | | /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. |
205 | | /// An error indicates a block is present but invalid. |
206 | | /// |
207 | | /// If the buffer does not contain any block, iterator will be empty. |
208 | | #[allow(missing_debug_implementations)] |
209 | | pub struct PemIterator<Reader: BufRead + Seek> { |
210 | | reader: Reader, |
211 | | } |
212 | | |
213 | | impl<R: BufRead + Seek> Iterator for PemIterator<R> { |
214 | | type Item = Result<Pem, PEMError>; |
215 | | |
216 | 0 | fn next(&mut self) -> Option<Self::Item> { |
217 | 0 | if let Ok(&[]) = self.reader.fill_buf() { |
218 | 0 | return None; |
219 | 0 | } |
220 | 0 | let reader = self.reader.by_ref(); |
221 | 0 | let r = Pem::read(reader).map(|(pem, _)| pem); |
222 | 0 | if let Err(PEMError::MissingHeader) = r { |
223 | 0 | None |
224 | | } else { |
225 | 0 | Some(r) |
226 | | } |
227 | 0 | } |
228 | | } |
229 | | |
230 | | #[cfg(test)] |
231 | | mod tests { |
232 | | use super::*; |
233 | | |
234 | | #[test] |
235 | | fn read_pem_from_file() { |
236 | | let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap()); |
237 | | let subject = Pem::read(file) |
238 | | .unwrap() |
239 | | .0 |
240 | | .parse_x509() |
241 | | .unwrap() |
242 | | .tbs_certificate |
243 | | .subject |
244 | | .to_string(); |
245 | | assert_eq!(subject, "CN=lists.for-our.info"); |
246 | | } |
247 | | |
248 | | #[test] |
249 | | fn pem_multi_word_label() { |
250 | | const PEM_BYTES: &[u8] = |
251 | | b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----"; |
252 | | let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem"); |
253 | | assert_eq!(pem.label, "MULTI WORD LABEL"); |
254 | | } |
255 | | } |