Coverage Report

Created: 2026-04-14 06:46

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