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 = "0.8"
23//! ```
24//!
25//! and this to your crate root:
26//!
27//! ```rust
28//! extern crate pem;
29//! ```
30//!
31//! # Example: parse a single chunk of PEM-encoded text
32//!
33//! Generally, PEM-encoded files contain a single chunk of PEM-encoded
34//! text. Commonly, this is in some sort of a key file or an x.509
35//! certificate.
36//!
37//! ```rust
38//!
39//! use pem::parse;
40//!
41//! const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
42//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
43//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
44//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
45//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
46//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
47//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
48//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
49//! -----END RSA PRIVATE KEY-----
50//! ";
51//!
52//!  let pem = parse(SAMPLE).unwrap();
53//!  assert_eq!(pem.tag, "RSA PRIVATE KEY");
54//! ```
55//!
56//! # Example: parse a set of PEM-encoded test
57//!
58//! Sometimes, PEM-encoded files contain multiple chunks of PEM-encoded
59//! text. You might see this if you have an x.509 certificate file that
60//! also includes intermediate certificates.
61//!
62//! ```rust
63//!
64//! use pem::parse_many;
65//!
66//! const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
67//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
68//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
69//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
70//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
71//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
72//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
73//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
74//! -----END INTERMEDIATE CERT-----
75//!
76//! -----BEGIN CERTIFICATE-----
77//! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
78//! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
79//! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
80//! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
81//! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
82//! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
83//! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
84//! -----END CERTIFICATE-----
85//! ";
86//!
87//!  let pems = parse_many(SAMPLE);
88//!  assert_eq!(pems.len(), 2);
89//!  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
90//!  assert_eq!(pems[1].tag, "CERTIFICATE");
91//! ```
92
93#![recursion_limit = "1024"]
94#![deny(
95    missing_docs,
96    missing_debug_implementations,
97    missing_copy_implementations,
98    trivial_casts,
99    trivial_numeric_casts,
100    unsafe_code,
101    unstable_features,
102    unused_import_braces,
103    unused_qualifications
104)]
105
106mod errors;
107
108pub use crate::errors::{PemError, Result};
109use once_cell::sync::Lazy;
110use regex::bytes::{Captures, Regex};
111use std::str;
112
113const REGEX_STR: &str =
114    r"(?s)-----BEGIN (?P<begin>.*?)-----[ \t\n\r]*(?P<data>.*?)-----END (?P<end>.*?)-----[ \t\n\r]*";
115
116/// The line length for PEM encoding
117const LINE_WRAP: usize = 64;
118
119static ASCII_ARMOR: Lazy<Regex> = Lazy::new(|| {
120    Regex::new(REGEX_STR).unwrap()
121});
122
123/// Enum describing line endings
124#[derive(Debug, Clone, Copy)]
125pub enum LineEnding {
126    /// Windows-like (`\r\n`)
127    CRLF,
128    /// Unix-like (`\n`)
129    LF,
130}
131
132/// Configuration for Pem encoding
133#[derive(Debug, Clone, Copy)]
134pub struct EncodeConfig {
135    /// Line ending to use during encoding
136    pub line_ending: LineEnding,
137}
138
139/// A representation of Pem-encoded data
140#[derive(PartialEq, Debug, Clone)]
141pub struct Pem {
142    /// The tag extracted from the Pem-encoded data
143    pub tag: String,
144    /// The binary contents of the Pem-encoded data
145    pub contents: Vec<u8>,
146}
147
148impl Pem {
149    fn new_from_captures(caps: Captures) -> Result<Pem> {
150        fn as_utf8<'a>(bytes: &'a [u8]) -> Result<&'a str> {
151            Ok(str::from_utf8(bytes).map_err(PemError::NotUtf8)?)
152        }
153
154        // Verify that the begin section exists
155        let tag = as_utf8(
156            caps.name("begin")
157                .ok_or_else(|| PemError::MissingBeginTag)?
158                .as_bytes(),
159        )?;
160        if tag.is_empty() {
161            return Err(PemError::MissingBeginTag);
162        }
163
164        // as well as the end section
165        let tag_end = as_utf8(
166            caps.name("end")
167                .ok_or_else(|| PemError::MissingEndTag)?
168                .as_bytes(),
169        )?;
170        if tag_end.is_empty() {
171            return Err(PemError::MissingEndTag);
172        }
173
174        // The beginning and the end sections must match
175        if tag != tag_end {
176            return Err(PemError::MismatchedTags(tag.into(), tag_end.into()));
177        }
178
179        // If they did, then we can grab the data section
180        let raw_data = as_utf8(
181            caps.name("data")
182                .ok_or_else(|| PemError::MissingData)?
183                .as_bytes(),
184        )?;
185
186        // We need to get rid of newlines for base64::decode
187        // As base64 requires an AsRef<[u8]>, this must involve a copy
188        let data: String = raw_data.lines().map(str::trim_end).collect();
189
190        // And decode it from Base64 into a vector of u8
191        let contents =
192            base64::decode_config(&data, base64::STANDARD).map_err(PemError::InvalidData)?;
193
194        Ok(Pem {
195            tag: tag.to_owned(),
196            contents,
197        })
198    }
199}
200
201/// Parses a single PEM-encoded data from a data-type that can be dereferenced as a [u8].
202///
203/// # Example: parse PEM-encoded data from a Vec<u8>
204/// ```rust
205///
206/// use pem::parse;
207///
208/// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
209/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
210/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
211/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
212/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
213/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
214/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
215/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
216/// -----END RSA PRIVATE KEY-----
217/// ";
218/// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into();
219///
220///  let pem = parse(SAMPLE_BYTES).unwrap();
221///  assert_eq!(pem.tag, "RSA PRIVATE KEY");
222/// ```
223///
224/// # Example: parse PEM-encoded data from a String
225/// ```rust
226///
227/// use pem::parse;
228///
229/// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----
230/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
231/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
232/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
233/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
234/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
235/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
236/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
237/// -----END RSA PRIVATE KEY-----
238/// ";
239/// let SAMPLE_STRING: String = SAMPLE.into();
240///
241///  let pem = parse(SAMPLE_STRING).unwrap();
242///  assert_eq!(pem.tag, "RSA PRIVATE KEY");
243/// ```
244pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> {
245    ASCII_ARMOR
246        .captures(&input.as_ref())
247        .ok_or_else(|| PemError::MalformedFraming)
248        .and_then(Pem::new_from_captures)
249}
250
251/// Parses a set of PEM-encoded data from a data-type that can be dereferenced as a [u8].
252///
253/// # Example: parse a set of PEM-encoded data from a Vec<u8>
254///
255/// ```rust
256///
257/// use pem::parse_many;
258///
259/// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
260/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
261/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
262/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
263/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
264/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
265/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
266/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
267/// -----END INTERMEDIATE CERT-----
268///
269/// -----BEGIN CERTIFICATE-----
270/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
271/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
272/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
273/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
274/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
275/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
276/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
277/// -----END CERTIFICATE-----
278/// ";
279/// let SAMPLE_BYTES: Vec<u8> = SAMPLE.into();
280///
281///  let pems = parse_many(SAMPLE_BYTES);
282///  assert_eq!(pems.len(), 2);
283///  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
284///  assert_eq!(pems[1].tag, "CERTIFICATE");
285/// ```
286///
287/// # Example: parse a set of PEM-encoded data from a String
288///
289/// ```rust
290///
291/// use pem::parse_many;
292///
293/// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT-----
294/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
295/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
296/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
297/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
298/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
299/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
300/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
301/// -----END INTERMEDIATE CERT-----
302///
303/// -----BEGIN CERTIFICATE-----
304/// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
305/// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
306/// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
307/// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
308/// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
309/// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
310/// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
311/// -----END CERTIFICATE-----
312/// ";
313///  let SAMPLE_STRING: Vec<u8> = SAMPLE.into();
314///
315///  let pems = parse_many(SAMPLE_STRING);
316///  assert_eq!(pems.len(), 2);
317///  assert_eq!(pems[0].tag, "INTERMEDIATE CERT");
318///  assert_eq!(pems[1].tag, "CERTIFICATE");
319/// ```
320pub fn parse_many<B: AsRef<[u8]>>(input: B) -> Vec<Pem> {
321    // Each time our regex matches a PEM section, we need to decode it.
322    ASCII_ARMOR
323        .captures_iter(&input.as_ref())
324        .filter_map(|caps| Pem::new_from_captures(caps).ok())
325        .collect()
326}
327
328/// Encode a PEM struct into a PEM-encoded data string
329///
330/// # Example
331/// ```rust
332///  use pem::{Pem, encode};
333///
334///  let pem = Pem {
335///     tag: String::from("FOO"),
336///     contents: vec![1, 2, 3, 4],
337///   };
338///   encode(&pem);
339/// ```
340pub fn encode(pem: &Pem) -> String {
341    encode_config(
342        pem,
343        EncodeConfig {
344            line_ending: LineEnding::CRLF,
345        },
346    )
347}
348
349/// Encode a PEM struct into a PEM-encoded data string with additional
350/// configuration options
351///
352/// # Example
353/// ```rust
354///  use pem::{Pem, encode_config, EncodeConfig, LineEnding};
355///
356///  let pem = Pem {
357///     tag: String::from("FOO"),
358///     contents: vec![1, 2, 3, 4],
359///   };
360///   encode_config(&pem, EncodeConfig { line_ending: LineEnding::LF });
361/// ```
362pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String {
363    let line_ending = match config.line_ending {
364        LineEnding::CRLF => "\r\n",
365        LineEnding::LF => "\n",
366    };
367
368    let mut output = String::new();
369
370    let contents = if pem.contents.is_empty() {
371        String::from("")
372    } else {
373        base64::encode_config(
374            &pem.contents,
375            base64::Config::new(base64::CharacterSet::Standard, true),
376        )
377    };
378
379    output.push_str(&format!("-----BEGIN {}-----{}", pem.tag, line_ending));
380    for c in contents.as_bytes().chunks(LINE_WRAP) {
381        output.push_str(&format!("{}{}", str::from_utf8(c).unwrap(), line_ending));
382    }
383    output.push_str(&format!("-----END {}-----{}", pem.tag, line_ending));
384
385    output
386}
387
388/// Encode multiple PEM structs into a PEM-encoded data string
389///
390/// # Example
391/// ```rust
392///  use pem::{Pem, encode_many};
393///
394///  let data = vec![
395///     Pem {
396///         tag: String::from("FOO"),
397///         contents: vec![1, 2, 3, 4],
398///     },
399///     Pem {
400///         tag: String::from("BAR"),
401///         contents: vec![5, 6, 7, 8],
402///     },
403///   ];
404///   encode_many(&data);
405/// ```
406pub fn encode_many(pems: &[Pem]) -> String {
407    pems.iter()
408        .map(encode)
409        .collect::<Vec<String>>()
410        .join("\r\n")
411}
412
413/// Encode multiple PEM structs into a PEM-encoded data string with additional
414/// configuration options
415///
416/// Same config will be used for each PEM struct.
417///
418/// # Example
419/// ```rust
420///  use pem::{Pem, encode_many_config, EncodeConfig, LineEnding};
421///
422///  let data = vec![
423///     Pem {
424///         tag: String::from("FOO"),
425///         contents: vec![1, 2, 3, 4],
426///     },
427///     Pem {
428///         tag: String::from("BAR"),
429///         contents: vec![5, 6, 7, 8],
430///     },
431///   ];
432///   encode_many_config(&data, EncodeConfig { line_ending: LineEnding::LF });
433/// ```
434pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String {
435    let line_ending = match config.line_ending {
436        LineEnding::CRLF => "\r\n",
437        LineEnding::LF => "\n",
438    };
439    pems.iter()
440        .map(|value| encode_config(value, config))
441        .collect::<Vec<String>>()
442        .join(line_ending)
443}
444
445#[cfg(test)]
446mod test {
447    use super::*;
448    use std::error::Error;
449
450    const SAMPLE_CRLF: &'static str = "-----BEGIN RSA PRIVATE KEY-----\r
451MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
452dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
4532gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
454AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
455DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
456TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
457ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
458-----END RSA PRIVATE KEY-----\r
459\r
460-----BEGIN RSA PUBLIC KEY-----\r
461MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
462QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
463RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
464sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
465ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
466/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
467RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
468-----END RSA PUBLIC KEY-----\r
469";
470
471    const SAMPLE_LF: &'static str = "-----BEGIN RSA PRIVATE KEY-----
472MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
473dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
4742gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
475AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
476DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
477TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
478ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
479-----END RSA PRIVATE KEY-----
480
481-----BEGIN RSA PUBLIC KEY-----
482MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
483QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
484RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
485sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
486ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
487/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
488RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
489-----END RSA PUBLIC KEY-----
490";
491
492    #[test]
493    fn test_parse_works() {
494        let pem = parse(SAMPLE_CRLF).unwrap();
495        assert_eq!(pem.tag, "RSA PRIVATE KEY");
496    }
497
498    #[test]
499    fn test_parse_invalid_framing() {
500        let input = "--BEGIN data-----
501        -----END data-----";
502        assert_eq!(parse(&input), Err(PemError::MalformedFraming));
503    }
504
505    #[test]
506    fn test_parse_invalid_begin() {
507        let input = "-----BEGIN -----
508MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
509QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
510RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
511sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
512ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
513/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
514RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
515-----END RSA PUBLIC KEY-----";
516        assert_eq!(parse(&input), Err(PemError::MissingBeginTag));
517    }
518
519    #[test]
520    fn test_parse_invalid_end() {
521        let input = "-----BEGIN DATA-----
522MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
523QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
524RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
525sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
526ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
527/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
528RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
529-----END -----";
530        assert_eq!(parse(&input), Err(PemError::MissingEndTag));
531    }
532
533    #[test]
534    fn test_parse_invalid_data() {
535        let input = "-----BEGIN DATA-----
536MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY?
537QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
538RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
539sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
540ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
541/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
542RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
543-----END DATA-----";
544        match parse(&input) {
545            Err(e @ PemError::InvalidData(_)) => {
546                assert_eq!(
547                    &format!("{}", e.source().unwrap()),
548                    "Invalid byte 63, offset 63."
549                );
550            }
551            _ => assert!(false),
552        }
553    }
554
555    #[test]
556    fn test_parse_empty_data() {
557        let input = "-----BEGIN DATA-----
558-----END DATA-----";
559        let pem = parse(&input).unwrap();
560        assert_eq!(pem.contents.len(), 0);
561    }
562
563    #[test]
564    fn test_parse_many_works() {
565        let pems = parse_many(SAMPLE_CRLF);
566        assert_eq!(pems.len(), 2);
567        assert_eq!(pems[0].tag, "RSA PRIVATE KEY");
568        assert_eq!(pems[1].tag, "RSA PUBLIC KEY");
569    }
570
571    #[test]
572    fn test_encode_empty_contents() {
573        let pem = Pem {
574            tag: String::from("FOO"),
575            contents: vec![],
576        };
577        let encoded = encode(&pem);
578        assert!(encoded != "");
579
580        let pem_out = parse(&encoded).unwrap();
581        assert_eq!(&pem, &pem_out);
582    }
583
584    #[test]
585    fn test_encode_contents() {
586        let pem = Pem {
587            tag: String::from("FOO"),
588            contents: vec![1, 2, 3, 4],
589        };
590        let encoded = encode(&pem);
591        assert!(encoded != "");
592
593        let pem_out = parse(&encoded).unwrap();
594        assert_eq!(&pem, &pem_out);
595    }
596
597    #[test]
598    fn test_encode_many() {
599        let pems = parse_many(SAMPLE_CRLF);
600        let encoded = encode_many(&pems);
601
602        assert_eq!(SAMPLE_CRLF, encoded);
603    }
604
605    #[test]
606    fn test_encode_config_contents() {
607        let pem = Pem {
608            tag: String::from("FOO"),
609            contents: vec![1, 2, 3, 4],
610        };
611        let config = EncodeConfig {
612            line_ending: LineEnding::LF,
613        };
614        let encoded = encode_config(&pem, config);
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_many_config() {
623        let pems = parse_many(SAMPLE_LF);
624        let config = EncodeConfig {
625            line_ending: LineEnding::LF,
626        };
627        let encoded = encode_many_config(&pems, config);
628
629        assert_eq!(SAMPLE_LF, encoded);
630    }
631}