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}