/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pem-3.0.6/src/lib.rs
Line | Count | Source |
1 | | // Copyright 2016-2017 Jonathan Creekmore |
2 | | // |
3 | | // Licensed under the MIT license <LICENSE.md or |
4 | | // http://opensource.org/licenses/MIT>. This file may not be |
5 | | // copied, modified, or distributed except according to those terms. |
6 | | |
7 | | //! This crate provides a parser and encoder for PEM-encoded binary data. |
8 | | //! PEM-encoded binary data is essentially a beginning and matching end |
9 | | //! tag that encloses base64-encoded binary data (see: |
10 | | //! https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail). |
11 | | //! |
12 | | //! This crate's documentation provides a few simple examples along with |
13 | | //! documentation on the public methods for the crate. |
14 | | //! |
15 | | //! # Usage |
16 | | //! |
17 | | //! This crate is [on crates.io](https://crates.io/crates/pem) and can be used |
18 | | //! by adding `pem` to your dependencies in your project's `Cargo.toml`. |
19 | | //! |
20 | | //! ```toml |
21 | | //! [dependencies] |
22 | | //! pem = "3" |
23 | | //! ``` |
24 | | //! |
25 | | //! Using the `serde` feature will implement the serde traits for |
26 | | //! the `Pem` struct. |
27 | | //! |
28 | | //! # Example: parse a single chunk of PEM-encoded text |
29 | | //! |
30 | | //! Generally, PEM-encoded files contain a single chunk of PEM-encoded |
31 | | //! text. Commonly, this is in some sort of a key file or an x.509 |
32 | | //! certificate. |
33 | | //! |
34 | | //! ```rust |
35 | | //! |
36 | | //! use pem::parse; |
37 | | //! |
38 | | //! const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- |
39 | | //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
40 | | //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
41 | | //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
42 | | //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
43 | | //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
44 | | //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
45 | | //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
46 | | //! -----END RSA PRIVATE KEY----- |
47 | | //! "; |
48 | | //! |
49 | | //! let pem = parse(SAMPLE).unwrap(); |
50 | | //! assert_eq!(pem.tag(), "RSA PRIVATE KEY"); |
51 | | //! ``` |
52 | | //! |
53 | | //! # Example: parse a set of PEM-encoded text |
54 | | //! |
55 | | //! Sometimes, PEM-encoded files contain multiple chunks of PEM-encoded |
56 | | //! text. You might see this if you have an x.509 certificate file that |
57 | | //! also includes intermediate certificates. |
58 | | //! |
59 | | //! ```rust |
60 | | //! |
61 | | //! use pem::parse_many; |
62 | | //! |
63 | | //! const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- |
64 | | //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
65 | | //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
66 | | //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
67 | | //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
68 | | //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
69 | | //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
70 | | //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
71 | | //! -----END INTERMEDIATE CERT----- |
72 | | //! |
73 | | //! -----BEGIN CERTIFICATE----- |
74 | | //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
75 | | //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
76 | | //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
77 | | //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
78 | | //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
79 | | //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
80 | | //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
81 | | //! -----END CERTIFICATE----- |
82 | | //! "; |
83 | | //! |
84 | | //! let pems = parse_many(SAMPLE).unwrap(); |
85 | | //! assert_eq!(pems.len(), 2); |
86 | | //! assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); |
87 | | //! assert_eq!(pems[1].tag(), "CERTIFICATE"); |
88 | | //! ``` |
89 | | //! |
90 | | //! # Features |
91 | | //! |
92 | | //! This crate supports two features: `std` and `serde`. |
93 | | //! |
94 | | //! The `std` feature is enabled by default. If you specify |
95 | | //! `default-features = false` to disable `std`, be aware that |
96 | | //! this crate still needs an allocator. |
97 | | //! |
98 | | //! The `serde` feature implements `serde::{Deserialize, Serialize}` |
99 | | //! for this crate's `Pem` struct. |
100 | | |
101 | | #![deny( |
102 | | missing_docs, |
103 | | missing_debug_implementations, |
104 | | missing_copy_implementations, |
105 | | trivial_casts, |
106 | | trivial_numeric_casts, |
107 | | unsafe_code, |
108 | | unstable_features, |
109 | | unused_import_braces, |
110 | | unused_qualifications |
111 | | )] |
112 | | #![cfg_attr(not(feature = "std"), no_std)] |
113 | | |
114 | | #[cfg(not(any(feature = "std", test)))] |
115 | | extern crate alloc; |
116 | | #[cfg(not(any(feature = "std", test)))] |
117 | | use alloc::{ |
118 | | format, |
119 | | string::{String, ToString}, |
120 | | vec::Vec, |
121 | | }; |
122 | | |
123 | | mod errors; |
124 | | mod parser; |
125 | | use parser::{parse_captures, parse_captures_iter, Captures}; |
126 | | |
127 | | pub use crate::errors::{PemError, Result}; |
128 | | use base64::Engine as _; |
129 | | use core::fmt::Write; |
130 | | use core::{fmt, slice, str}; |
131 | | |
132 | | /// The line length for PEM encoding |
133 | | const LINE_WRAP: usize = 64; |
134 | | |
135 | | /// Enum describing line endings |
136 | | #[derive(Debug, Clone, Copy)] |
137 | | pub enum LineEnding { |
138 | | /// Windows-like (`\r\n`) |
139 | | CRLF, |
140 | | /// Unix-like (`\n`) |
141 | | LF, |
142 | | } |
143 | | |
144 | | /// Configuration for Pem encoding |
145 | | #[derive(Debug, Clone, Copy)] |
146 | | pub struct EncodeConfig { |
147 | | /// Line ending to use during encoding |
148 | | line_ending: LineEnding, |
149 | | |
150 | | /// Line length to use during encoding |
151 | | line_wrap: usize, |
152 | | } |
153 | | |
154 | | /// A representation of Pem-encoded data |
155 | | #[derive(PartialEq, Debug, Clone)] |
156 | | pub struct Pem { |
157 | | tag: String, |
158 | | headers: HeaderMap, |
159 | | contents: Vec<u8>, |
160 | | } |
161 | | |
162 | | /// Provides access to the headers that might be found in a Pem-encoded file |
163 | | #[derive(Clone, Debug, Default, PartialEq)] |
164 | | pub struct HeaderMap(Vec<String>); |
165 | | |
166 | 0 | fn decode_data(raw_data: &str) -> Result<Vec<u8>> { |
167 | | // We need to get rid of newlines/whitespaces for base64::decode |
168 | | // As base64 requires an AsRef<[u8]>, this must involve a copy |
169 | 0 | let data: String = raw_data.chars().filter(|c| !c.is_whitespace()).collect(); |
170 | | |
171 | | // And decode it from Base64 into a vector of u8 |
172 | 0 | let contents = base64::engine::general_purpose::STANDARD |
173 | 0 | .decode(data) |
174 | 0 | .map_err(PemError::InvalidData)?; |
175 | | |
176 | 0 | Ok(contents) |
177 | 0 | } |
178 | | |
179 | | /// Iterator across all headers in the Pem-encoded data |
180 | | #[derive(Debug)] |
181 | | pub struct HeadersIter<'a> { |
182 | | cur: slice::Iter<'a, String>, |
183 | | } |
184 | | |
185 | | impl<'a> Iterator for HeadersIter<'a> { |
186 | | type Item = (&'a str, &'a str); |
187 | | |
188 | 0 | fn next(&mut self) -> Option<Self::Item> { |
189 | 0 | self.cur.next().and_then(HeaderMap::split_header) |
190 | 0 | } |
191 | | } |
192 | | |
193 | | impl<'a> DoubleEndedIterator for HeadersIter<'a> { |
194 | 0 | fn next_back(&mut self) -> Option<Self::Item> { |
195 | 0 | self.cur.next_back().and_then(HeaderMap::split_header) |
196 | 0 | } |
197 | | } |
198 | | |
199 | | impl HeaderMap { |
200 | | #[allow(clippy::ptr_arg)] |
201 | 0 | fn split_header(header: &String) -> Option<(&str, &str)> { |
202 | 0 | header |
203 | 0 | .split_once(':') |
204 | 0 | .map(|(key, value)| (key.trim(), value.trim())) |
205 | 0 | } |
206 | | |
207 | 0 | fn parse(headers: Vec<String>) -> Result<HeaderMap> { |
208 | 0 | headers.iter().try_for_each(|hline| { |
209 | 0 | Self::split_header(hline) |
210 | 0 | .map(|_| ()) |
211 | 0 | .ok_or_else(|| PemError::InvalidHeader(hline.to_string())) |
212 | 0 | })?; |
213 | 0 | Ok(HeaderMap(headers)) |
214 | 0 | } |
215 | | |
216 | | /// Get an iterator across all header key-value pairs |
217 | 0 | pub fn iter(&self) -> HeadersIter<'_> { |
218 | 0 | HeadersIter { cur: self.0.iter() } |
219 | 0 | } |
220 | | |
221 | | /// Get the last set value corresponding to the header key |
222 | 0 | pub fn get(&self, key: &str) -> Option<&str> { |
223 | 0 | self.iter().rev().find(|(k, _)| *k == key).map(|(_, v)| v) |
224 | 0 | } |
225 | | |
226 | | /// Get the last set value corresponding to the header key |
227 | 0 | pub fn add(&mut self, key: &str, value: &str) -> Result<()> { |
228 | 0 | ensure!( |
229 | 0 | !(key.contains(':') || key.contains('\n')), |
230 | 0 | PemError::InvalidHeader(key.to_string()) |
231 | | ); |
232 | 0 | ensure!( |
233 | 0 | !(value.contains(':') || value.contains('\n')), |
234 | 0 | PemError::InvalidHeader(value.to_string()) |
235 | | ); |
236 | 0 | self.0.push(format!("{}: {}", key.trim(), value.trim())); |
237 | 0 | Ok(()) |
238 | 0 | } |
239 | | } |
240 | | |
241 | | impl EncodeConfig { |
242 | | /// Create a new encode config with default values. |
243 | 0 | pub const fn new() -> Self { |
244 | 0 | Self { |
245 | 0 | line_ending: LineEnding::CRLF, |
246 | 0 | line_wrap: LINE_WRAP, |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | /// Set the line ending to use for the encoding. |
251 | 0 | pub const fn set_line_ending(mut self, line_ending: LineEnding) -> Self { |
252 | 0 | self.line_ending = line_ending; |
253 | 0 | self |
254 | 0 | } |
255 | | |
256 | | /// Set the line length to use for the encoding. |
257 | 0 | pub const fn set_line_wrap(mut self, line_wrap: usize) -> Self { |
258 | 0 | self.line_wrap = line_wrap; |
259 | 0 | self |
260 | 0 | } |
261 | | } |
262 | | |
263 | | impl Default for EncodeConfig { |
264 | 0 | fn default() -> Self { |
265 | 0 | Self::new() |
266 | 0 | } |
267 | | } |
268 | | |
269 | | impl Pem { |
270 | | /// Create a new Pem struct |
271 | 0 | pub fn new(tag: impl ToString, contents: impl Into<Vec<u8>>) -> Pem { |
272 | 0 | Pem { |
273 | 0 | tag: tag.to_string(), |
274 | 0 | headers: HeaderMap::default(), |
275 | 0 | contents: contents.into(), |
276 | 0 | } |
277 | 0 | } Unexecuted instantiation: <pem::Pem>::new::<&str, &[u8]> Unexecuted instantiation: <pem::Pem>::new::<&str, alloc::vec::Vec<u8>> |
278 | | |
279 | | /// Get the tag extracted from the Pem-encoded data |
280 | 0 | pub fn tag(&self) -> &str { |
281 | 0 | &self.tag |
282 | 0 | } |
283 | | |
284 | | /// Get the binary contents extracted from the Pem-encoded data |
285 | 0 | pub fn contents(&self) -> &[u8] { |
286 | 0 | &self.contents |
287 | 0 | } |
288 | | |
289 | | /// Consume the Pem struct to get an owned copy of the binary contents |
290 | 0 | pub fn into_contents(self) -> Vec<u8> { |
291 | 0 | self.contents |
292 | 0 | } |
293 | | |
294 | | /// Get the header map for the headers in the Pem-encoded data |
295 | 0 | pub fn headers(&self) -> &HeaderMap { |
296 | 0 | &self.headers |
297 | 0 | } |
298 | | |
299 | | /// Get the header map for modification |
300 | 0 | pub fn headers_mut(&mut self) -> &mut HeaderMap { |
301 | 0 | &mut self.headers |
302 | 0 | } |
303 | | |
304 | 0 | fn new_from_captures(caps: Captures) -> Result<Pem> { |
305 | 0 | fn as_utf8(bytes: &[u8]) -> Result<&str> { |
306 | 0 | str::from_utf8(bytes).map_err(PemError::NotUtf8) |
307 | 0 | } |
308 | | |
309 | | // Verify that the begin section exists |
310 | 0 | let tag = as_utf8(caps.begin)?; |
311 | 0 | if tag.is_empty() { |
312 | 0 | return Err(PemError::MissingBeginTag); |
313 | 0 | } |
314 | | |
315 | | // as well as the end section |
316 | 0 | let tag_end = as_utf8(caps.end)?; |
317 | 0 | if tag_end.is_empty() { |
318 | 0 | return Err(PemError::MissingEndTag); |
319 | 0 | } |
320 | | |
321 | | // The beginning and the end sections must match |
322 | 0 | if tag != tag_end { |
323 | 0 | return Err(PemError::MismatchedTags(tag.into(), tag_end.into())); |
324 | 0 | } |
325 | | |
326 | | // If they did, then we can grab the data section |
327 | 0 | let raw_data = as_utf8(caps.data)?; |
328 | 0 | let contents = decode_data(raw_data)?; |
329 | 0 | let headers: Vec<String> = as_utf8(caps.headers)?.lines().map(str::to_string).collect(); |
330 | 0 | let headers = HeaderMap::parse(headers)?; |
331 | | |
332 | 0 | let mut file = Pem::new(tag, contents); |
333 | 0 | file.headers = headers; |
334 | | |
335 | 0 | Ok(file) |
336 | 0 | } |
337 | | } |
338 | | |
339 | | impl str::FromStr for Pem { |
340 | | type Err = PemError; |
341 | | |
342 | 0 | fn from_str(s: &str) -> Result<Pem> { |
343 | 0 | parse(s) |
344 | 0 | } |
345 | | } |
346 | | |
347 | | impl TryFrom<&[u8]> for Pem { |
348 | | type Error = PemError; |
349 | | |
350 | 0 | fn try_from(s: &[u8]) -> Result<Pem> { |
351 | 0 | parse(s) |
352 | 0 | } |
353 | | } |
354 | | |
355 | | impl fmt::Display for Pem { |
356 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
357 | 0 | write!(f, "{}", encode(self)) |
358 | 0 | } |
359 | | } |
360 | | |
361 | | /// Parses a single PEM-encoded data from a data-type that can be dereferenced as a [u8]. |
362 | | /// |
363 | | /// # Example: parse PEM-encoded data from a Vec<u8> |
364 | | /// ```rust |
365 | | /// |
366 | | /// use pem::parse; |
367 | | /// |
368 | | /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- |
369 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
370 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
371 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
372 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
373 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
374 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
375 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
376 | | /// -----END RSA PRIVATE KEY----- |
377 | | /// "; |
378 | | /// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into(); |
379 | | /// |
380 | | /// let pem = parse(SAMPLE_BYTES).unwrap(); |
381 | | /// assert_eq!(pem.tag(), "RSA PRIVATE KEY"); |
382 | | /// ``` |
383 | | /// |
384 | | /// # Example: parse PEM-encoded data from a String |
385 | | /// ```rust |
386 | | /// |
387 | | /// use pem::parse; |
388 | | /// |
389 | | /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- |
390 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
391 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
392 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
393 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
394 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
395 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
396 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
397 | | /// -----END RSA PRIVATE KEY----- |
398 | | /// "; |
399 | | /// let SAMPLE_STRING: String = SAMPLE.into(); |
400 | | /// |
401 | | /// let pem = parse(SAMPLE_STRING).unwrap(); |
402 | | /// assert_eq!(pem.tag(), "RSA PRIVATE KEY"); |
403 | | /// ``` |
404 | 0 | pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> { |
405 | 0 | parse_captures(input.as_ref()) |
406 | 0 | .ok_or(PemError::MalformedFraming) |
407 | 0 | .and_then(Pem::new_from_captures) |
408 | 0 | } Unexecuted instantiation: pem::parse::<&[u8]> Unexecuted instantiation: pem::parse::<&str> |
409 | | |
410 | | /// Parses a set of PEM-encoded data from a data-type that can be dereferenced as a [u8]. |
411 | | /// |
412 | | /// # Example: parse a set of PEM-encoded data from a Vec<u8> |
413 | | /// |
414 | | /// ```rust |
415 | | /// |
416 | | /// use pem::parse_many; |
417 | | /// |
418 | | /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- |
419 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
420 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
421 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
422 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
423 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
424 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
425 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
426 | | /// -----END INTERMEDIATE CERT----- |
427 | | /// |
428 | | /// -----BEGIN CERTIFICATE----- |
429 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
430 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
431 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
432 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
433 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
434 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
435 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
436 | | /// -----END CERTIFICATE----- |
437 | | /// "; |
438 | | /// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into(); |
439 | | /// |
440 | | /// let pems = parse_many(SAMPLE_BYTES).unwrap(); |
441 | | /// assert_eq!(pems.len(), 2); |
442 | | /// assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); |
443 | | /// assert_eq!(pems[1].tag(), "CERTIFICATE"); |
444 | | /// ``` |
445 | | /// |
446 | | /// # Example: parse a set of PEM-encoded data from a String |
447 | | /// |
448 | | /// ```rust |
449 | | /// |
450 | | /// use pem::parse_many; |
451 | | /// |
452 | | /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- |
453 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
454 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
455 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
456 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
457 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
458 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
459 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
460 | | /// -----END INTERMEDIATE CERT----- |
461 | | /// |
462 | | /// -----BEGIN CERTIFICATE----- |
463 | | /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
464 | | /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
465 | | /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
466 | | /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
467 | | /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
468 | | /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
469 | | /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
470 | | /// -----END CERTIFICATE----- |
471 | | /// "; |
472 | | /// let SAMPLE_STRING: Vec<u8> = SAMPLE.into(); |
473 | | /// |
474 | | /// let pems = parse_many(SAMPLE_STRING).unwrap(); |
475 | | /// assert_eq!(pems.len(), 2); |
476 | | /// assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); |
477 | | /// assert_eq!(pems[1].tag(), "CERTIFICATE"); |
478 | | /// ``` |
479 | 0 | pub fn parse_many<B: AsRef<[u8]>>(input: B) -> Result<Vec<Pem>> { |
480 | | // Each time our regex matches a PEM section, we need to decode it. |
481 | 0 | parse_captures_iter(input.as_ref()) |
482 | 0 | .map(Pem::new_from_captures) |
483 | 0 | .collect() |
484 | 0 | } |
485 | | |
486 | | /// Encode a PEM struct into a PEM-encoded data string |
487 | | /// |
488 | | /// # Example |
489 | | /// ```rust |
490 | | /// use pem::{Pem, encode}; |
491 | | /// |
492 | | /// let pem = Pem::new("FOO", [1, 2, 3, 4]); |
493 | | /// encode(&pem); |
494 | | /// ``` |
495 | 0 | pub fn encode(pem: &Pem) -> String { |
496 | 0 | encode_config(pem, EncodeConfig::default()) |
497 | 0 | } |
498 | | |
499 | | /// Encode a PEM struct into a PEM-encoded data string with additional |
500 | | /// configuration options |
501 | | /// |
502 | | /// # Example |
503 | | /// ```rust |
504 | | /// use pem::{Pem, encode_config, EncodeConfig, LineEnding}; |
505 | | /// |
506 | | /// let pem = Pem::new("FOO", [1, 2, 3, 4]); |
507 | | /// encode_config(&pem, EncodeConfig::new().set_line_ending(LineEnding::LF)); |
508 | | /// ``` |
509 | 0 | pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String { |
510 | 0 | let line_ending = match config.line_ending { |
511 | 0 | LineEnding::CRLF => "\r\n", |
512 | 0 | LineEnding::LF => "\n", |
513 | | }; |
514 | | |
515 | 0 | let mut output = String::new(); |
516 | | |
517 | 0 | let contents = if pem.contents.is_empty() { |
518 | 0 | String::from("") |
519 | | } else { |
520 | 0 | base64::engine::general_purpose::STANDARD.encode(&pem.contents) |
521 | | }; |
522 | | |
523 | 0 | write!(output, "-----BEGIN {}-----{}", pem.tag, line_ending).unwrap(); |
524 | 0 | if !pem.headers.0.is_empty() { |
525 | 0 | for line in &pem.headers.0 { |
526 | 0 | write!(output, "{}{}", line.trim(), line_ending).unwrap(); |
527 | 0 | } |
528 | 0 | output.push_str(line_ending); |
529 | 0 | } |
530 | 0 | for c in contents.as_bytes().chunks(config.line_wrap) { |
531 | 0 | write!(output, "{}{}", str::from_utf8(c).unwrap(), line_ending).unwrap(); |
532 | 0 | } |
533 | 0 | write!(output, "-----END {}-----{}", pem.tag, line_ending).unwrap(); |
534 | | |
535 | 0 | output |
536 | 0 | } |
537 | | |
538 | | /// Encode multiple PEM structs into a PEM-encoded data string |
539 | | /// |
540 | | /// # Example |
541 | | /// ```rust |
542 | | /// use pem::{Pem, encode_many}; |
543 | | /// |
544 | | /// let data = vec![ |
545 | | /// Pem::new("FOO", [1, 2, 3, 4]), |
546 | | /// Pem::new("BAR", [5, 6, 7, 8]), |
547 | | /// ]; |
548 | | /// encode_many(&data); |
549 | | /// ``` |
550 | 0 | pub fn encode_many(pems: &[Pem]) -> String { |
551 | 0 | pems.iter() |
552 | 0 | .map(encode) |
553 | 0 | .collect::<Vec<String>>() |
554 | 0 | .join("\r\n") |
555 | 0 | } |
556 | | |
557 | | /// Encode multiple PEM structs into a PEM-encoded data string with additional |
558 | | /// configuration options |
559 | | /// |
560 | | /// Same config will be used for each PEM struct. |
561 | | /// |
562 | | /// # Example |
563 | | /// ```rust |
564 | | /// use pem::{Pem, encode_many_config, EncodeConfig, LineEnding}; |
565 | | /// |
566 | | /// let data = vec![ |
567 | | /// Pem::new("FOO", [1, 2, 3, 4]), |
568 | | /// Pem::new("BAR", [5, 6, 7, 8]), |
569 | | /// ]; |
570 | | /// encode_many_config(&data, EncodeConfig::new().set_line_ending(LineEnding::LF)); |
571 | | /// ``` |
572 | 0 | pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String { |
573 | 0 | let line_ending = match config.line_ending { |
574 | 0 | LineEnding::CRLF => "\r\n", |
575 | 0 | LineEnding::LF => "\n", |
576 | | }; |
577 | 0 | pems.iter() |
578 | 0 | .map(|value| encode_config(value, config)) |
579 | 0 | .collect::<Vec<String>>() |
580 | 0 | .join(line_ending) |
581 | 0 | } |
582 | | |
583 | | #[cfg(feature = "serde")] |
584 | | mod serde_impl { |
585 | | use super::{encode, parse, Pem}; |
586 | | use core::fmt; |
587 | | use serde_core::{ |
588 | | de::{Error, Visitor}, |
589 | | Deserialize, Serialize, |
590 | | }; |
591 | | |
592 | | impl Serialize for Pem { |
593 | | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
594 | | where |
595 | | S: serde_core::Serializer, |
596 | | { |
597 | | serializer.serialize_str(&encode(self)) |
598 | | } |
599 | | } |
600 | | |
601 | | struct PemVisitor; |
602 | | |
603 | | impl<'de> Visitor<'de> for PemVisitor { |
604 | | type Value = Pem; |
605 | | |
606 | | fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { |
607 | | Ok(()) |
608 | | } |
609 | | |
610 | | fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> |
611 | | where |
612 | | E: Error, |
613 | | { |
614 | | parse(v).map_err(Error::custom) |
615 | | } |
616 | | } |
617 | | |
618 | | impl<'de> Deserialize<'de> for Pem { |
619 | | fn deserialize<D>(deserializer: D) -> Result<Pem, D::Error> |
620 | | where |
621 | | D: serde_core::Deserializer<'de>, |
622 | | { |
623 | | deserializer.deserialize_str(PemVisitor) |
624 | | } |
625 | | } |
626 | | } |
627 | | |
628 | | #[cfg(test)] |
629 | | mod test { |
630 | | use super::*; |
631 | | use proptest::prelude::*; |
632 | | use std::error::Error; |
633 | | |
634 | | const SAMPLE_CRLF: &str = "-----BEGIN RSA PRIVATE KEY-----\r |
635 | | MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r |
636 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r |
637 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r |
638 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r |
639 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r |
640 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r |
641 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r |
642 | | -----END RSA PRIVATE KEY-----\r |
643 | | \r |
644 | | -----BEGIN RSA PUBLIC KEY-----\r |
645 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r |
646 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r |
647 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r |
648 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r |
649 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r |
650 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r |
651 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r |
652 | | -----END RSA PUBLIC KEY-----\r |
653 | | "; |
654 | | |
655 | | const SAMPLE_LF: &str = "-----BEGIN RSA PRIVATE KEY----- |
656 | | MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
657 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
658 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
659 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
660 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
661 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
662 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
663 | | -----END RSA PRIVATE KEY----- |
664 | | |
665 | | -----BEGIN RSA PUBLIC KEY----- |
666 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
667 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
668 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
669 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
670 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
671 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
672 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
673 | | -----END RSA PUBLIC KEY----- |
674 | | "; |
675 | | |
676 | | const SAMPLE_WS: &str = "-----BEGIN RSA PRIVATE KEY----- |
677 | | MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc \ |
678 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO \ |
679 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei \ |
680 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un \ |
681 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT \ |
682 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh \ |
683 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
684 | | -----END RSA PRIVATE KEY----- |
685 | | |
686 | | -----BEGIN RSA PUBLIC KEY----- |
687 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo \ |
688 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 \ |
689 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI \ |
690 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk \ |
691 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 \ |
692 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g \ |
693 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
694 | | -----END RSA PUBLIC KEY----- |
695 | | "; |
696 | | |
697 | | const SAMPLE_DEFAULT_LINE_WRAP: &str = "-----BEGIN TEST-----\r |
698 | | AQIDBA==\r |
699 | | -----END TEST-----\r |
700 | | "; |
701 | | |
702 | | const SAMPLE_CUSTOM_LINE_WRAP_4: &str = "-----BEGIN TEST-----\r |
703 | | AQID\r |
704 | | BA==\r |
705 | | -----END TEST-----\r |
706 | | "; |
707 | | |
708 | | #[test] |
709 | | fn test_parse_works() { |
710 | | let pem = parse(SAMPLE_CRLF).unwrap(); |
711 | | assert_eq!(pem.tag(), "RSA PRIVATE KEY"); |
712 | | } |
713 | | |
714 | | #[test] |
715 | | fn test_parse_empty_space() { |
716 | | let pem = parse(SAMPLE_WS).unwrap(); |
717 | | assert_eq!(pem.tag(), "RSA PRIVATE KEY"); |
718 | | } |
719 | | |
720 | | #[test] |
721 | | fn test_parse_invalid_framing() { |
722 | | let input = "--BEGIN data----- |
723 | | -----END data-----"; |
724 | | assert_eq!(parse(input), Err(PemError::MalformedFraming)); |
725 | | } |
726 | | |
727 | | #[test] |
728 | | fn test_parse_invalid_begin() { |
729 | | let input = "-----BEGIN ----- |
730 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
731 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
732 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
733 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
734 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
735 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
736 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
737 | | -----END RSA PUBLIC KEY-----"; |
738 | | assert_eq!(parse(input), Err(PemError::MissingBeginTag)); |
739 | | } |
740 | | |
741 | | #[test] |
742 | | fn test_parse_invalid_end() { |
743 | | let input = "-----BEGIN DATA----- |
744 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
745 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
746 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
747 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
748 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
749 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
750 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
751 | | -----END -----"; |
752 | | assert_eq!(parse(input), Err(PemError::MissingEndTag)); |
753 | | } |
754 | | |
755 | | #[test] |
756 | | fn test_parse_invalid_data() { |
757 | | let input = "-----BEGIN DATA----- |
758 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY? |
759 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
760 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
761 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
762 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
763 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
764 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
765 | | -----END DATA-----"; |
766 | | match parse(input) { |
767 | | Err(e @ PemError::InvalidData(_)) => { |
768 | | assert_eq!( |
769 | | &format!("{}", e.source().unwrap()), |
770 | | "Invalid symbol 63, offset 63." |
771 | | ); |
772 | | } |
773 | | _ => unreachable!(), |
774 | | } |
775 | | } |
776 | | |
777 | | #[test] |
778 | | fn test_parse_empty_data() { |
779 | | let input = "-----BEGIN DATA----- |
780 | | -----END DATA-----"; |
781 | | let pem = parse(input).unwrap(); |
782 | | assert_eq!(pem.contents().len(), 0); |
783 | | } |
784 | | |
785 | | #[test] |
786 | | fn test_parse_many_works() { |
787 | | let pems = parse_many(SAMPLE_CRLF).unwrap(); |
788 | | assert_eq!(pems.len(), 2); |
789 | | assert_eq!(pems[0].tag(), "RSA PRIVATE KEY"); |
790 | | assert_eq!(pems[1].tag(), "RSA PUBLIC KEY"); |
791 | | } |
792 | | |
793 | | #[test] |
794 | | fn test_parse_many_errors_on_invalid_section() { |
795 | | let input = SAMPLE_LF.to_owned() + "-----BEGIN -----\n-----END -----"; |
796 | | assert_eq!(parse_many(input), Err(PemError::MissingBeginTag)); |
797 | | } |
798 | | |
799 | | #[test] |
800 | | fn test_encode_default_line_wrap() { |
801 | | let pem = Pem::new("TEST", vec![1, 2, 3, 4]); |
802 | | assert_eq!(encode(&pem), SAMPLE_DEFAULT_LINE_WRAP); |
803 | | } |
804 | | |
805 | | #[test] |
806 | | fn test_encode_custom_line_wrap_4() { |
807 | | let pem = Pem::new("TEST", vec![1, 2, 3, 4]); |
808 | | assert_eq!( |
809 | | encode_config(&pem, EncodeConfig::default().set_line_wrap(4)), |
810 | | SAMPLE_CUSTOM_LINE_WRAP_4 |
811 | | ); |
812 | | } |
813 | | #[test] |
814 | | fn test_encode_empty_contents() { |
815 | | let pem = Pem::new("FOO", vec![]); |
816 | | let encoded = encode(&pem); |
817 | | assert!(!encoded.is_empty()); |
818 | | |
819 | | let pem_out = parse(&encoded).unwrap(); |
820 | | assert_eq!(&pem, &pem_out); |
821 | | } |
822 | | |
823 | | #[test] |
824 | | fn test_encode_contents() { |
825 | | let pem = Pem::new("FOO", [1, 2, 3, 4]); |
826 | | let encoded = encode(&pem); |
827 | | assert!(!encoded.is_empty()); |
828 | | |
829 | | let pem_out = parse(&encoded).unwrap(); |
830 | | assert_eq!(&pem, &pem_out); |
831 | | } |
832 | | |
833 | | #[test] |
834 | | fn test_encode_many() { |
835 | | let pems = parse_many(SAMPLE_CRLF).unwrap(); |
836 | | let encoded = encode_many(&pems); |
837 | | |
838 | | assert_eq!(SAMPLE_CRLF, encoded); |
839 | | } |
840 | | |
841 | | #[test] |
842 | | fn test_encode_config_contents() { |
843 | | let pem = Pem::new("FOO", [1, 2, 3, 4]); |
844 | | let config = EncodeConfig::default().set_line_ending(LineEnding::LF); |
845 | | let encoded = encode_config(&pem, config); |
846 | | assert!(!encoded.is_empty()); |
847 | | |
848 | | let pem_out = parse(&encoded).unwrap(); |
849 | | assert_eq!(&pem, &pem_out); |
850 | | } |
851 | | |
852 | | #[test] |
853 | | fn test_encode_many_config() { |
854 | | let pems = parse_many(SAMPLE_LF).unwrap(); |
855 | | let config = EncodeConfig::default().set_line_ending(LineEnding::LF); |
856 | | let encoded = encode_many_config(&pems, config); |
857 | | |
858 | | assert_eq!(SAMPLE_LF, encoded); |
859 | | } |
860 | | |
861 | | #[cfg(feature = "serde")] |
862 | | #[test] |
863 | | fn test_serde() { |
864 | | let pem = Pem::new("Mock tag", "Mock contents".as_bytes()); |
865 | | let value = serde_json::to_string_pretty(&pem).unwrap(); |
866 | | let result = serde_json::from_str(&value).unwrap(); |
867 | | assert_eq!(pem, result); |
868 | | } |
869 | | |
870 | | const HEADER_CRLF: &str = "-----BEGIN CERTIFICATE-----\r |
871 | | MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r |
872 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r |
873 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r |
874 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r |
875 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r |
876 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r |
877 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r |
878 | | -----END CERTIFICATE-----\r |
879 | | -----BEGIN RSA PRIVATE KEY-----\r |
880 | | Proc-Type: 4,ENCRYPTED\r |
881 | | DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r |
882 | | \r |
883 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r |
884 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r |
885 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r |
886 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r |
887 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r |
888 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r |
889 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r |
890 | | -----END RSA PRIVATE KEY-----\r |
891 | | "; |
892 | | const HEADER_CRLF_DATA: [&str; 2] = [ |
893 | | "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r |
894 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r |
895 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r |
896 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r |
897 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r |
898 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r |
899 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r", |
900 | | "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r |
901 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r |
902 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r |
903 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r |
904 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r |
905 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r |
906 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r", |
907 | | ]; |
908 | | |
909 | | const HEADER_LF: &str = "-----BEGIN CERTIFICATE----- |
910 | | MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
911 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
912 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
913 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
914 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
915 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
916 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ |
917 | | -----END CERTIFICATE----- |
918 | | -----BEGIN RSA PRIVATE KEY----- |
919 | | Proc-Type: 4,ENCRYPTED |
920 | | DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 |
921 | | |
922 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
923 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
924 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
925 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
926 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
927 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
928 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
929 | | -----END RSA PRIVATE KEY----- |
930 | | "; |
931 | | const HEADER_LF_DATA: [&str; 2] = [ |
932 | | "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc |
933 | | dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO |
934 | | 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei |
935 | | AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un |
936 | | DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT |
937 | | TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh |
938 | | ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ", |
939 | | "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
940 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
941 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
942 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
943 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
944 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
945 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg", |
946 | | ]; |
947 | | |
948 | | fn cmp_data(left: &[u8], right: &[u8]) -> bool { |
949 | | if left.len() != right.len() { |
950 | | false |
951 | | } else { |
952 | | left.iter() |
953 | | .zip(right.iter()) |
954 | | .all(|(left, right)| left == right) |
955 | | } |
956 | | } |
957 | | |
958 | | #[test] |
959 | | fn test_parse_many_with_headers_crlf() { |
960 | | let pems = parse_many(HEADER_CRLF).unwrap(); |
961 | | assert_eq!(pems.len(), 2); |
962 | | assert_eq!(pems[0].tag(), "CERTIFICATE"); |
963 | | assert!(cmp_data( |
964 | | pems[0].contents(), |
965 | | &decode_data(HEADER_CRLF_DATA[0]).unwrap() |
966 | | )); |
967 | | assert_eq!(pems[1].tag(), "RSA PRIVATE KEY"); |
968 | | assert!(cmp_data( |
969 | | pems[1].contents(), |
970 | | &decode_data(HEADER_CRLF_DATA[1]).unwrap() |
971 | | )); |
972 | | } |
973 | | |
974 | | #[test] |
975 | | fn test_parse_many_with_headers_lf() { |
976 | | let pems = parse_many(HEADER_LF).unwrap(); |
977 | | assert_eq!(pems.len(), 2); |
978 | | assert_eq!(pems[0].tag(), "CERTIFICATE"); |
979 | | assert!(cmp_data( |
980 | | pems[0].contents(), |
981 | | &decode_data(HEADER_LF_DATA[0]).unwrap() |
982 | | )); |
983 | | assert_eq!(pems[1].tag(), "RSA PRIVATE KEY"); |
984 | | assert!(cmp_data( |
985 | | pems[1].contents(), |
986 | | &decode_data(HEADER_LF_DATA[1]).unwrap() |
987 | | )); |
988 | | } |
989 | | |
990 | | proptest! { |
991 | | #[test] |
992 | | fn test_str_parse_and_display(tag in "[A-Z ]+", contents in prop::collection::vec(0..255u8, 0..200)) { |
993 | | let pem = Pem::new(tag, contents); |
994 | | prop_assert_eq!(&pem, &pem.to_string().parse::<Pem>().unwrap()); |
995 | | } |
996 | | |
997 | | #[test] |
998 | | fn test_str_parse_and_display_with_headers(tag in "[A-Z ]+", |
999 | | key in "[a-zA-Z]+", |
1000 | | value in "[a-zA-A]+", |
1001 | | contents in prop::collection::vec(0..255u8, 0..200)) { |
1002 | | let mut pem = Pem::new(tag, contents); |
1003 | | pem.headers_mut().add(&key, &value).unwrap(); |
1004 | | prop_assert_eq!(&pem, &pem.to_string().parse::<Pem>().unwrap()); |
1005 | | } |
1006 | | } |
1007 | | |
1008 | | #[test] |
1009 | | fn test_extract_headers() { |
1010 | | let pems = parse_many(HEADER_CRLF).unwrap(); |
1011 | | let headers = pems[1].headers().iter().collect::<Vec<_>>(); |
1012 | | assert_eq!(headers.len(), 2); |
1013 | | assert_eq!(headers[0].0, "Proc-Type"); |
1014 | | assert_eq!(headers[0].1, "4,ENCRYPTED"); |
1015 | | assert_eq!(headers[1].0, "DEK-Info"); |
1016 | | assert_eq!(headers[1].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6"); |
1017 | | |
1018 | | let headers = pems[1].headers().iter().rev().collect::<Vec<_>>(); |
1019 | | assert_eq!(headers.len(), 2); |
1020 | | assert_eq!(headers[1].0, "Proc-Type"); |
1021 | | assert_eq!(headers[1].1, "4,ENCRYPTED"); |
1022 | | assert_eq!(headers[0].0, "DEK-Info"); |
1023 | | assert_eq!(headers[0].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6"); |
1024 | | } |
1025 | | |
1026 | | #[test] |
1027 | | fn test_get_header() { |
1028 | | let pems = parse_many(HEADER_CRLF).unwrap(); |
1029 | | let headers = pems[1].headers(); |
1030 | | assert_eq!(headers.get("Proc-Type"), Some("4,ENCRYPTED")); |
1031 | | assert_eq!( |
1032 | | headers.get("DEK-Info"), |
1033 | | Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6") |
1034 | | ); |
1035 | | } |
1036 | | |
1037 | | #[test] |
1038 | | fn test_only_get_latest() { |
1039 | | const LATEST: &str = "-----BEGIN RSA PRIVATE KEY----- |
1040 | | Proc-Type: 4,ENCRYPTED |
1041 | | DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 |
1042 | | Proc-Type: 42,DECRYPTED |
1043 | | |
1044 | | MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo |
1045 | | QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 |
1046 | | RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI |
1047 | | sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk |
1048 | | ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 |
1049 | | /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g |
1050 | | RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg |
1051 | | -----END RSA PRIVATE KEY----- |
1052 | | "; |
1053 | | let pem = parse(LATEST).unwrap(); |
1054 | | let headers = pem.headers(); |
1055 | | assert_eq!(headers.get("Proc-Type"), Some("42,DECRYPTED")); |
1056 | | assert_eq!( |
1057 | | headers.get("DEK-Info"), |
1058 | | Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6") |
1059 | | ); |
1060 | | } |
1061 | | } |