pem/
lib.rs

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)))]
115extern crate alloc;
116#[cfg(not(any(feature = "std", test)))]
117use alloc::{
118    format,
119    string::{String, ToString},
120    vec::Vec,
121};
122
123mod errors;
124mod parser;
125use parser::{parse_captures, parse_captures_iter, Captures};
126
127pub use crate::errors::{PemError, Result};
128use base64::Engine as _;
129use core::fmt::Write;
130use core::{fmt, slice, str};
131
132/// The line length for PEM encoding
133const LINE_WRAP: usize = 64;
134
135/// Enum describing line endings
136#[derive(Debug, Clone, Copy)]
137pub 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)]
146pub 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)]
156pub 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)]
164pub struct HeaderMap(Vec<String>);
165
166fn 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    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    let contents = base64::engine::general_purpose::STANDARD
173        .decode(data)
174        .map_err(PemError::InvalidData)?;
175
176    Ok(contents)
177}
178
179/// Iterator across all headers in the Pem-encoded data
180#[derive(Debug)]
181pub struct HeadersIter<'a> {
182    cur: slice::Iter<'a, String>,
183}
184
185impl<'a> Iterator for HeadersIter<'a> {
186    type Item = (&'a str, &'a str);
187
188    fn next(&mut self) -> Option<Self::Item> {
189        self.cur.next().and_then(HeaderMap::split_header)
190    }
191}
192
193impl<'a> DoubleEndedIterator for HeadersIter<'a> {
194    fn next_back(&mut self) -> Option<Self::Item> {
195        self.cur.next_back().and_then(HeaderMap::split_header)
196    }
197}
198
199impl HeaderMap {
200    #[allow(clippy::ptr_arg)]
201    fn split_header(header: &String) -> Option<(&str, &str)> {
202        header
203            .split_once(':')
204            .map(|(key, value)| (key.trim(), value.trim()))
205    }
206
207    fn parse(headers: Vec<String>) -> Result<HeaderMap> {
208        headers.iter().try_for_each(|hline| {
209            Self::split_header(hline)
210                .map(|_| ())
211                .ok_or_else(|| PemError::InvalidHeader(hline.to_string()))
212        })?;
213        Ok(HeaderMap(headers))
214    }
215
216    /// Get an iterator across all header key-value pairs
217    pub fn iter(&self) -> HeadersIter<'_> {
218        HeadersIter { cur: self.0.iter() }
219    }
220
221    /// Get the last set value corresponding to the header key
222    pub fn get(&self, key: &str) -> Option<&str> {
223        self.iter().rev().find(|(k, _)| *k == key).map(|(_, v)| v)
224    }
225
226    /// Get the last set value corresponding to the header key
227    pub fn add(&mut self, key: &str, value: &str) -> Result<()> {
228        ensure!(
229            !(key.contains(':') || key.contains('\n')),
230            PemError::InvalidHeader(key.to_string())
231        );
232        ensure!(
233            !(value.contains(':') || value.contains('\n')),
234            PemError::InvalidHeader(value.to_string())
235        );
236        self.0.push(format!("{}: {}", key.trim(), value.trim()));
237        Ok(())
238    }
239}
240
241impl EncodeConfig {
242    /// Create a new encode config with default values.
243    pub const fn new() -> Self {
244        Self {
245            line_ending: LineEnding::CRLF,
246            line_wrap: LINE_WRAP,
247        }
248    }
249
250    /// Set the line ending to use for the encoding.
251    pub const fn set_line_ending(mut self, line_ending: LineEnding) -> Self {
252        self.line_ending = line_ending;
253        self
254    }
255
256    /// Set the line length to use for the encoding.
257    pub const fn set_line_wrap(mut self, line_wrap: usize) -> Self {
258        self.line_wrap = line_wrap;
259        self
260    }
261}
262
263impl Default for EncodeConfig {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269impl Pem {
270    /// Create a new Pem struct
271    pub fn new(tag: impl ToString, contents: impl Into<Vec<u8>>) -> Pem {
272        Pem {
273            tag: tag.to_string(),
274            headers: HeaderMap::default(),
275            contents: contents.into(),
276        }
277    }
278
279    /// Get the tag extracted from the Pem-encoded data
280    pub fn tag(&self) -> &str {
281        &self.tag
282    }
283
284    /// Get the binary contents extracted from the Pem-encoded data
285    pub fn contents(&self) -> &[u8] {
286        &self.contents
287    }
288
289    /// Consume the Pem struct to get an owned copy of the binary contents
290    pub fn into_contents(self) -> Vec<u8> {
291        self.contents
292    }
293
294    /// Get the header map for the headers in the Pem-encoded data
295    pub fn headers(&self) -> &HeaderMap {
296        &self.headers
297    }
298
299    /// Get the header map for modification
300    pub fn headers_mut(&mut self) -> &mut HeaderMap {
301        &mut self.headers
302    }
303
304    fn new_from_captures(caps: Captures) -> Result<Pem> {
305        fn as_utf8(bytes: &[u8]) -> Result<&str> {
306            str::from_utf8(bytes).map_err(PemError::NotUtf8)
307        }
308
309        // Verify that the begin section exists
310        let tag = as_utf8(caps.begin)?;
311        if tag.is_empty() {
312            return Err(PemError::MissingBeginTag);
313        }
314
315        // as well as the end section
316        let tag_end = as_utf8(caps.end)?;
317        if tag_end.is_empty() {
318            return Err(PemError::MissingEndTag);
319        }
320
321        // The beginning and the end sections must match
322        if tag != tag_end {
323            return Err(PemError::MismatchedTags(tag.into(), tag_end.into()));
324        }
325
326        // If they did, then we can grab the data section
327        let raw_data = as_utf8(caps.data)?;
328        let contents = decode_data(raw_data)?;
329        let headers: Vec<String> = as_utf8(caps.headers)?.lines().map(str::to_string).collect();
330        let headers = HeaderMap::parse(headers)?;
331
332        let mut file = Pem::new(tag, contents);
333        file.headers = headers;
334
335        Ok(file)
336    }
337}
338
339impl str::FromStr for Pem {
340    type Err = PemError;
341
342    fn from_str(s: &str) -> Result<Pem> {
343        parse(s)
344    }
345}
346
347impl TryFrom<&[u8]> for Pem {
348    type Error = PemError;
349
350    fn try_from(s: &[u8]) -> Result<Pem> {
351        parse(s)
352    }
353}
354
355impl fmt::Display for Pem {
356    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
357        write!(f, "{}", encode(self))
358    }
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/// ```
404pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> {
405    parse_captures(input.as_ref())
406        .ok_or(PemError::MalformedFraming)
407        .and_then(Pem::new_from_captures)
408}
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/// ```
479pub 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    parse_captures_iter(input.as_ref())
482        .map(Pem::new_from_captures)
483        .collect()
484}
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/// ```
495pub fn encode(pem: &Pem) -> String {
496    encode_config(pem, EncodeConfig::default())
497}
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/// ```
509pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String {
510    let line_ending = match config.line_ending {
511        LineEnding::CRLF => "\r\n",
512        LineEnding::LF => "\n",
513    };
514
515    let mut output = String::new();
516
517    let contents = if pem.contents.is_empty() {
518        String::from("")
519    } else {
520        base64::engine::general_purpose::STANDARD.encode(&pem.contents)
521    };
522
523    write!(output, "-----BEGIN {}-----{}", pem.tag, line_ending).unwrap();
524    if !pem.headers.0.is_empty() {
525        for line in &pem.headers.0 {
526            write!(output, "{}{}", line.trim(), line_ending).unwrap();
527        }
528        output.push_str(line_ending);
529    }
530    for c in contents.as_bytes().chunks(config.line_wrap) {
531        write!(output, "{}{}", str::from_utf8(c).unwrap(), line_ending).unwrap();
532    }
533    write!(output, "-----END {}-----{}", pem.tag, line_ending).unwrap();
534
535    output
536}
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/// ```
550pub fn encode_many(pems: &[Pem]) -> String {
551    pems.iter()
552        .map(encode)
553        .collect::<Vec<String>>()
554        .join("\r\n")
555}
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/// ```
572pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String {
573    let line_ending = match config.line_ending {
574        LineEnding::CRLF => "\r\n",
575        LineEnding::LF => "\n",
576    };
577    pems.iter()
578        .map(|value| encode_config(value, config))
579        .collect::<Vec<String>>()
580        .join(line_ending)
581}
582
583#[cfg(feature = "serde")]
584mod serde_impl {
585    use super::{encode, parse, Pem};
586    use core::fmt;
587    use serde::{
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::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::Deserializer<'de>,
622        {
623            deserializer.deserialize_str(PemVisitor)
624        }
625    }
626}
627
628#[cfg(test)]
629mod test {
630    use super::*;
631    use proptest::prelude::*;
632    use std::error::Error;
633
634    const SAMPLE_CRLF: &str = "-----BEGIN RSA PRIVATE KEY-----\r
635MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
636dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
6372gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
638AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
639DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
640TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
641ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
642-----END RSA PRIVATE KEY-----\r
643\r
644-----BEGIN RSA PUBLIC KEY-----\r
645MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
646QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
647RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
648sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
649ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
650/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
651RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
652-----END RSA PUBLIC KEY-----\r
653";
654
655    const SAMPLE_LF: &str = "-----BEGIN RSA PRIVATE KEY-----
656MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
657dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
6582gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
659AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
660DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
661TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
662ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
663-----END RSA PRIVATE KEY-----
664
665-----BEGIN RSA PUBLIC KEY-----
666MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
667QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
668RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
669sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
670ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
671/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
672RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
673-----END RSA PUBLIC KEY-----
674";
675
676    const SAMPLE_WS: &str = "-----BEGIN RSA PRIVATE KEY-----
677MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc \
678dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO \
6792gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei \
680AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un \
681DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT \
682TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh \
683ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
684-----END RSA PRIVATE KEY-----
685
686-----BEGIN RSA PUBLIC KEY-----
687MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo \
688QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 \
689RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI \
690sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk \
691ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 \
692/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g \
693RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
694-----END RSA PUBLIC KEY-----
695";
696
697    const SAMPLE_DEFAULT_LINE_WRAP: &str = "-----BEGIN TEST-----\r
698AQIDBA==\r
699-----END TEST-----\r
700";
701
702    const SAMPLE_CUSTOM_LINE_WRAP_4: &str = "-----BEGIN TEST-----\r
703AQID\r
704BA==\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 -----
730MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
731QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
732RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
733sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
734ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
735/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
736RzHX0lkJl9Stshd/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-----
744MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
745QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
746RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
747sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
748ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
749/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
750RzHX0lkJl9Stshd/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-----
758MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY?
759QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
760RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
761sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
762ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
763/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
764RzHX0lkJl9Stshd/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
871MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
872dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
8732gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
874AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
875DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
876TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
877ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
878-----END CERTIFICATE-----\r
879-----BEGIN RSA PRIVATE KEY-----\r
880Proc-Type: 4,ENCRYPTED\r
881DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r
882\r
883MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
884QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
885RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
886sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
887ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
888/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
889RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
890-----END RSA PRIVATE KEY-----\r
891";
892    const HEADER_CRLF_DATA: [&str; 2] = [
893        "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
894dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
8952gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
896AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
897DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
898TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
899ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r",
900        "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
901QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
902RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
903sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
904ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
905/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
906RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r",
907    ];
908
909    const HEADER_LF: &str = "-----BEGIN CERTIFICATE-----
910MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
911dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
9122gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
913AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
914DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
915TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
916ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
917-----END CERTIFICATE-----
918-----BEGIN RSA PRIVATE KEY-----
919Proc-Type: 4,ENCRYPTED
920DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6
921
922MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
923QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
924RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
925sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
926ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
927/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
928RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
929-----END RSA PRIVATE KEY-----
930";
931    const HEADER_LF_DATA: [&str; 2] = [
932        "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
933dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
9342gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
935AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
936DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
937TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
938ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ",
939        "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
940QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
941RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
942sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
943ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
944/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
945RzHX0lkJl9Stshd/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-----
1040Proc-Type: 4,ENCRYPTED
1041DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6
1042Proc-Type: 42,DECRYPTED
1043
1044MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
1045QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
1046RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
1047sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
1048ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
1049/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
1050RzHX0lkJl9Stshd/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}