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 = "1"
23//! ```
24//!
25//! and this to your crate root:
26//!
27//! ```rust
28//! extern crate pem;
29//! ```
30//!
31//! Using the `serde` feature will implement the serde traits for
32//! the `Pem` struct.
33//!
34//! # Example: parse a single chunk of PEM-encoded text
35//!
36//! Generally, PEM-encoded files contain a single chunk of PEM-encoded
37//! text. Commonly, this is in some sort of a key file or an x.509
38//! certificate.
39//!
40//! ```rust
41//!
42//! use pem::parse;
43//!
44//! const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
45//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
46//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
47//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
48//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
49//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
50//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
51//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
52//! -----END RSA PRIVATE KEY-----
53//! ";
54//!
55//!  let pem = parse(SAMPLE).unwrap();
56//!  assert_eq!(pem.tag, "RSA PRIVATE KEY");
57//! ```
58//!
59//! # Example: parse a set of PEM-encoded test
60//!
61//! Sometimes, PEM-encoded files contain multiple chunks of PEM-encoded
62//! text. You might see this if you have an x.509 certificate file that
63//! also includes intermediate certificates.
64//!
65//! ```rust
66//!
67//! use pem::parse_many;
68//!
69//! const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
70//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
71//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
72//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
73//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
74//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
75//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
76//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
77//! -----END INTERMEDIATE CERT-----
78//!
79//! -----BEGIN CERTIFICATE-----
80//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
81//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
82//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
83//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
84//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
85//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
86//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
87//! -----END CERTIFICATE-----
88//! ";
89//!
90//!  let pems = parse_many(SAMPLE).unwrap();
91//!  assert_eq!(pems.len(), 2);
92//!  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
93//!  assert_eq!(pems[1].tag, "CERTIFICATE");
94//! ```
95
96#![recursion_limit = "1024"]
97#![deny(
98    missing_docs,
99    missing_debug_implementations,
100    missing_copy_implementations,
101    trivial_casts,
102    trivial_numeric_casts,
103    unsafe_code,
104    unstable_features,
105    unused_import_braces,
106    unused_qualifications
107)]
108
109mod errors;
110mod parser;
111use parser::{parse_captures, parse_captures_iter, Captures};
112
113pub use crate::errors::{PemError, Result};
114use std::str;
115
116/// The line length for PEM encoding
117const LINE_WRAP: usize = 64;
118
119/// Enum describing line endings
120#[derive(Debug, Clone, Copy)]
121pub enum LineEnding {
122    /// Windows-like (`\r\n`)
123    CRLF,
124    /// Unix-like (`\n`)
125    LF,
126}
127
128/// Configuration for Pem encoding
129#[derive(Debug, Clone, Copy)]
130pub struct EncodeConfig {
131    /// Line ending to use during encoding
132    pub line_ending: LineEnding,
133}
134
135/// A representation of Pem-encoded data
136#[derive(PartialEq, Debug, Clone)]
137pub struct Pem {
138    /// The tag extracted from the Pem-encoded data
139    pub tag: String,
140    /// The binary contents of the Pem-encoded data
141    pub contents: Vec<u8>,
142}
143
144fn decode_data(raw_data: &str) -> Result<Vec<u8>> {
145    // We need to get rid of newlines for base64::decode
146    // As base64 requires an AsRef<[u8]>, this must involve a copy
147    let data: String = raw_data.lines().map(str::trim_end).collect();
148
149    // And decode it from Base64 into a vector of u8
150    let contents = base64::decode_config(&data, base64::STANDARD).map_err(PemError::InvalidData)?;
151
152    Ok(contents)
153}
154
155impl Pem {
156    fn new_from_captures(caps: Captures) -> Result<Pem> {
157        fn as_utf8<'a>(bytes: &'a [u8]) -> Result<&'a str> {
158            Ok(str::from_utf8(bytes).map_err(PemError::NotUtf8)?)
159        }
160
161        // Verify that the begin section exists
162        let tag = as_utf8(caps.begin)?;
163        if tag.is_empty() {
164            return Err(PemError::MissingBeginTag);
165        }
166
167        // as well as the end section
168        let tag_end = as_utf8(caps.end)?;
169        if tag_end.is_empty() {
170            return Err(PemError::MissingEndTag);
171        }
172
173        // The beginning and the end sections must match
174        if tag != tag_end {
175            return Err(PemError::MismatchedTags(tag.into(), tag_end.into()));
176        }
177
178        // If they did, then we can grab the data section
179        let raw_data = as_utf8(caps.data)?;
180        let contents = decode_data(raw_data)?;
181
182        Ok(Pem {
183            tag: tag.to_owned(),
184            contents,
185        })
186    }
187}
188
189/// Parses a single PEM-encoded data from a data-type that can be dereferenced as a [u8].
190///
191/// # Example: parse PEM-encoded data from a Vec<u8>
192/// ```rust
193///
194/// use pem::parse;
195///
196/// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
197/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
198/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
199/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
200/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
201/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
202/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
203/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
204/// -----END RSA PRIVATE KEY-----
205/// ";
206/// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into();
207///
208///  let pem = parse(SAMPLE_BYTES).unwrap();
209///  assert_eq!(pem.tag, "RSA PRIVATE KEY");
210/// ```
211///
212/// # Example: parse PEM-encoded data from a String
213/// ```rust
214///
215/// use pem::parse;
216///
217/// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
218/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
219/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
220/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
221/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
222/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
223/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
224/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
225/// -----END RSA PRIVATE KEY-----
226/// ";
227/// let SAMPLE_STRING: String = SAMPLE.into();
228///
229///  let pem = parse(SAMPLE_STRING).unwrap();
230///  assert_eq!(pem.tag, "RSA PRIVATE KEY");
231/// ```
232pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> {
233    parse_captures(&input.as_ref())
234        .ok_or_else(|| PemError::MalformedFraming)
235        .and_then(Pem::new_from_captures)
236}
237
238/// Parses a set of PEM-encoded data from a data-type that can be dereferenced as a [u8].
239///
240/// # Example: parse a set of PEM-encoded data from a Vec<u8>
241///
242/// ```rust
243///
244/// use pem::parse_many;
245///
246/// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
247/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
248/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
249/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
250/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
251/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
252/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
253/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
254/// -----END INTERMEDIATE CERT-----
255///
256/// -----BEGIN CERTIFICATE-----
257/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
258/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
259/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
260/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
261/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
262/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
263/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
264/// -----END CERTIFICATE-----
265/// ";
266/// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into();
267///
268///  let pems = parse_many(SAMPLE_BYTES).unwrap();
269///  assert_eq!(pems.len(), 2);
270///  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
271///  assert_eq!(pems[1].tag, "CERTIFICATE");
272/// ```
273///
274/// # Example: parse a set of PEM-encoded data from a String
275///
276/// ```rust
277///
278/// use pem::parse_many;
279///
280/// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
281/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
282/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
283/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
284/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
285/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
286/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
287/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
288/// -----END INTERMEDIATE CERT-----
289///
290/// -----BEGIN CERTIFICATE-----
291/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
292/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
293/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
294/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
295/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
296/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
297/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
298/// -----END CERTIFICATE-----
299/// ";
300///  let SAMPLE_STRING: Vec<u8> = SAMPLE.into();
301///
302///  let pems = parse_many(SAMPLE_STRING).unwrap();
303///  assert_eq!(pems.len(), 2);
304///  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
305///  assert_eq!(pems[1].tag, "CERTIFICATE");
306/// ```
307pub fn parse_many<B: AsRef<[u8]>>(input: B) -> Result<Vec<Pem>> {
308    // Each time our regex matches a PEM section, we need to decode it.
309    parse_captures_iter(&input.as_ref())
310        .map(|caps| Pem::new_from_captures(caps))
311        .collect()
312}
313
314/// Encode a PEM struct into a PEM-encoded data string
315///
316/// # Example
317/// ```rust
318///  use pem::{Pem, encode};
319///
320///  let pem = Pem {
321///     tag: String::from("FOO"),
322///     contents: vec![1, 2, 3, 4],
323///   };
324///   encode(&pem);
325/// ```
326pub fn encode(pem: &Pem) -> String {
327    encode_config(
328        pem,
329        EncodeConfig {
330            line_ending: LineEnding::CRLF,
331        },
332    )
333}
334
335/// Encode a PEM struct into a PEM-encoded data string with additional
336/// configuration options
337///
338/// # Example
339/// ```rust
340///  use pem::{Pem, encode_config, EncodeConfig, LineEnding};
341///
342///  let pem = Pem {
343///     tag: String::from("FOO"),
344///     contents: vec![1, 2, 3, 4],
345///   };
346///   encode_config(&pem, EncodeConfig { line_ending: LineEnding::LF });
347/// ```
348pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String {
349    let line_ending = match config.line_ending {
350        LineEnding::CRLF => "\r\n",
351        LineEnding::LF => "\n",
352    };
353
354    let mut output = String::new();
355
356    let contents = if pem.contents.is_empty() {
357        String::from("")
358    } else {
359        base64::encode_config(
360            &pem.contents,
361            base64::Config::new(base64::CharacterSet::Standard, true),
362        )
363    };
364
365    output.push_str(&format!("-----BEGIN {}-----{}", pem.tag, line_ending));
366    for c in contents.as_bytes().chunks(LINE_WRAP) {
367        output.push_str(&format!("{}{}", str::from_utf8(c).unwrap(), line_ending));
368    }
369    output.push_str(&format!("-----END {}-----{}", pem.tag, line_ending));
370
371    output
372}
373
374/// Encode multiple PEM structs into a PEM-encoded data string
375///
376/// # Example
377/// ```rust
378///  use pem::{Pem, encode_many};
379///
380///  let data = vec![
381///     Pem {
382///         tag: String::from("FOO"),
383///         contents: vec![1, 2, 3, 4],
384///     },
385///     Pem {
386///         tag: String::from("BAR"),
387///         contents: vec![5, 6, 7, 8],
388///     },
389///   ];
390///   encode_many(&data);
391/// ```
392pub fn encode_many(pems: &[Pem]) -> String {
393    pems.iter()
394        .map(encode)
395        .collect::<Vec<String>>()
396        .join("\r\n")
397}
398
399/// Encode multiple PEM structs into a PEM-encoded data string with additional
400/// configuration options
401///
402/// Same config will be used for each PEM struct.
403///
404/// # Example
405/// ```rust
406///  use pem::{Pem, encode_many_config, EncodeConfig, LineEnding};
407///
408///  let data = vec![
409///     Pem {
410///         tag: String::from("FOO"),
411///         contents: vec![1, 2, 3, 4],
412///     },
413///     Pem {
414///         tag: String::from("BAR"),
415///         contents: vec![5, 6, 7, 8],
416///     },
417///   ];
418///   encode_many_config(&data, EncodeConfig { line_ending: LineEnding::LF });
419/// ```
420pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String {
421    let line_ending = match config.line_ending {
422        LineEnding::CRLF => "\r\n",
423        LineEnding::LF => "\n",
424    };
425    pems.iter()
426        .map(|value| encode_config(value, config))
427        .collect::<Vec<String>>()
428        .join(line_ending)
429}
430
431#[cfg(feature = "serde")]
432mod serde_impl {
433    use super::{encode, parse, Pem};
434    use serde::{
435        de::{Error, Visitor},
436        Deserialize, Serialize,
437    };
438    use std::fmt;
439
440    impl Serialize for Pem {
441        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
442        where
443            S: serde::Serializer,
444        {
445            serializer.serialize_str(&encode(self))
446        }
447    }
448
449    struct PemVisitor;
450
451    impl<'de> Visitor<'de> for PemVisitor {
452        type Value = Pem;
453
454        fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
455            Ok(())
456        }
457
458        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
459        where
460            E: Error,
461        {
462            parse(v).map_err(Error::custom)
463        }
464    }
465
466    impl<'de> Deserialize<'de> for Pem {
467        fn deserialize<D>(deserializer: D) -> Result<Pem, D::Error>
468        where
469            D: serde::Deserializer<'de>,
470        {
471            deserializer.deserialize_str(PemVisitor)
472        }
473    }
474}
475
476#[cfg(test)]
477mod test {
478    use super::*;
479    use std::error::Error;
480
481    const SAMPLE_CRLF: &'static str = "-----BEGIN RSA PRIVATE KEY-----\r
482MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
483dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
4842gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
485AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
486DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
487TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
488ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
489-----END RSA PRIVATE KEY-----\r
490\r
491-----BEGIN RSA PUBLIC KEY-----\r
492MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
493QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
494RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
495sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
496ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
497/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
498RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
499-----END RSA PUBLIC KEY-----\r
500";
501
502    const SAMPLE_LF: &'static str = "-----BEGIN RSA PRIVATE KEY-----
503MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
504dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
5052gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
506AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
507DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
508TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
509ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
510-----END RSA PRIVATE KEY-----
511
512-----BEGIN RSA PUBLIC KEY-----
513MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
514QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
515RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
516sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
517ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
518/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
519RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
520-----END RSA PUBLIC KEY-----
521";
522
523    #[test]
524    fn test_parse_works() {
525        let pem = parse(SAMPLE_CRLF).unwrap();
526        assert_eq!(pem.tag, "RSA PRIVATE KEY");
527    }
528
529    #[test]
530    fn test_parse_invalid_framing() {
531        let input = "--BEGIN data-----
532        -----END data-----";
533        assert_eq!(parse(&input), Err(PemError::MalformedFraming));
534    }
535
536    #[test]
537    fn test_parse_invalid_begin() {
538        let input = "-----BEGIN -----
539MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
540QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
541RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
542sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
543ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
544/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
545RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
546-----END RSA PUBLIC KEY-----";
547        assert_eq!(parse(&input), Err(PemError::MissingBeginTag));
548    }
549
550    #[test]
551    fn test_parse_invalid_end() {
552        let input = "-----BEGIN DATA-----
553MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
554QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
555RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
556sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
557ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
558/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
559RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
560-----END -----";
561        assert_eq!(parse(&input), Err(PemError::MissingEndTag));
562    }
563
564    #[test]
565    fn test_parse_invalid_data() {
566        let input = "-----BEGIN DATA-----
567MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY?
568QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
569RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
570sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
571ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
572/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
573RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
574-----END DATA-----";
575        match parse(&input) {
576            Err(e @ PemError::InvalidData(_)) => {
577                assert_eq!(
578                    &format!("{}", e.source().unwrap()),
579                    "Invalid byte 63, offset 63."
580                );
581            }
582            _ => assert!(false),
583        }
584    }
585
586    #[test]
587    fn test_parse_empty_data() {
588        let input = "-----BEGIN DATA-----
589-----END DATA-----";
590        let pem = parse(&input).unwrap();
591        assert_eq!(pem.contents.len(), 0);
592    }
593
594    #[test]
595    fn test_parse_many_works() {
596        let pems = parse_many(SAMPLE_CRLF).unwrap();
597        assert_eq!(pems.len(), 2);
598        assert_eq!(pems[0].tag, "RSA PRIVATE KEY");
599        assert_eq!(pems[1].tag, "RSA PUBLIC KEY");
600    }
601
602    #[test]
603    fn test_parse_many_errors_on_invalid_section() {
604        let input = SAMPLE_LF.to_owned() + "-----BEGIN -----\n-----END -----";
605        assert_eq!(parse_many(&input), Err(PemError::MissingBeginTag));
606    }
607
608    #[test]
609    fn test_encode_empty_contents() {
610        let pem = Pem {
611            tag: String::from("FOO"),
612            contents: vec![],
613        };
614        let encoded = encode(&pem);
615        assert!(encoded != "");
616
617        let pem_out = parse(&encoded).unwrap();
618        assert_eq!(&pem, &pem_out);
619    }
620
621    #[test]
622    fn test_encode_contents() {
623        let pem = Pem {
624            tag: String::from("FOO"),
625            contents: vec![1, 2, 3, 4],
626        };
627        let encoded = encode(&pem);
628        assert!(encoded != "");
629
630        let pem_out = parse(&encoded).unwrap();
631        assert_eq!(&pem, &pem_out);
632    }
633
634    #[test]
635    fn test_encode_many() {
636        let pems = parse_many(SAMPLE_CRLF).unwrap();
637        let encoded = encode_many(&pems);
638
639        assert_eq!(SAMPLE_CRLF, encoded);
640    }
641
642    #[test]
643    fn test_encode_config_contents() {
644        let pem = Pem {
645            tag: String::from("FOO"),
646            contents: vec![1, 2, 3, 4],
647        };
648        let config = EncodeConfig {
649            line_ending: LineEnding::LF,
650        };
651        let encoded = encode_config(&pem, config);
652        assert!(encoded != "");
653
654        let pem_out = parse(&encoded).unwrap();
655        assert_eq!(&pem, &pem_out);
656    }
657
658    #[test]
659    fn test_encode_many_config() {
660        let pems = parse_many(SAMPLE_LF).unwrap();
661        let config = EncodeConfig {
662            line_ending: LineEnding::LF,
663        };
664        let encoded = encode_many_config(&pems, config);
665
666        assert_eq!(SAMPLE_LF, encoded);
667    }
668
669    #[cfg(feature = "serde")]
670    #[test]
671    fn test_serde() {
672        let pem = Pem {
673            tag: String::from("Mock tag"),
674            contents: "Mock contents".as_bytes().to_vec(),
675        };
676        let value = serde_json::to_string_pretty(&pem).unwrap();
677        let result = serde_json::from_str(&value).unwrap();
678        assert_eq!(pem, result);
679    }
680
681    const HEADER_CRLF: &'static str = "-----BEGIN CERTIFICATE-----\r
682MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
683dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
6842gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
685AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
686DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
687TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
688ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
689-----END CERTIFICATE-----\r
690-----BEGIN RSA PRIVATE KEY-----\r
691Proc-Type: 4,ENCRYPTED\r
692DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r
693\r
694MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
695QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
696RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
697sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
698ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
699/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
700RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
701-----END RSA PRIVATE KEY-----\r
702";
703    const HEADER_CRLF_DATA: [&'static str; 2] = [
704        "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
705dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
7062gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
707AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
708DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
709TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
710ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r",
711        "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
712QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
713RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
714sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
715ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
716/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
717RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r",
718    ];
719
720    const HEADER_LF: &'static str = "-----BEGIN CERTIFICATE-----
721MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
722dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
7232gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
724AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
725DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
726TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
727ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
728-----END CERTIFICATE-----
729-----BEGIN RSA PRIVATE KEY-----
730Proc-Type: 4,ENCRYPTED
731DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6
732
733MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
734QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
735RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
736sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
737ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
738/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
739RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
740-----END RSA PRIVATE KEY-----
741";
742    const HEADER_LF_DATA: [&'static str; 2] = [
743        "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
744dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
7452gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
746AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
747DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
748TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
749ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ",
750        "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
751QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
752RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
753sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
754ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
755/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
756RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg",
757    ];
758
759    fn cmp_data(left: &[u8], right: &[u8]) -> bool {
760        if left.len() != right.len() {
761            false
762        } else {
763            left.iter()
764                .zip(right.iter())
765                .all(|(left, right)| left == right)
766        }
767    }
768
769    #[test]
770    fn test_parse_many_with_headers_crlf() {
771        let pems = parse_many(HEADER_CRLF).unwrap();
772        assert_eq!(pems.len(), 2);
773        assert_eq!(pems[0].tag, "CERTIFICATE");
774        assert!(cmp_data(
775            &pems[0].contents,
776            &decode_data(HEADER_CRLF_DATA[0]).unwrap()
777        ));
778        assert_eq!(pems[1].tag, "RSA PRIVATE KEY");
779        assert!(cmp_data(
780            &pems[1].contents,
781            &decode_data(HEADER_CRLF_DATA[1]).unwrap()
782        ));
783    }
784
785    #[test]
786    fn test_parse_many_with_headers_lf() {
787        let pems = parse_many(HEADER_LF).unwrap();
788        assert_eq!(pems.len(), 2);
789        assert_eq!(pems[0].tag, "CERTIFICATE");
790        assert!(cmp_data(
791            &pems[0].contents,
792            &decode_data(HEADER_LF_DATA[0]).unwrap()
793        ));
794        assert_eq!(pems[1].tag, "RSA PRIVATE KEY");
795        assert!(cmp_data(
796            &pems[1].contents,
797            &decode_data(HEADER_LF_DATA[1]).unwrap()
798        ));
799    }
800}