1#![deny(
102 missing_docs,
103 missing_debug_implementations,
104 missing_copy_implementations,
105 trivial_casts,
106 trivial_numeric_casts,
107 unsafe_code,
108 unstable_features,
109 unused_import_braces,
110 unused_qualifications
111)]
112#![cfg_attr(not(feature = "std"), no_std)]
113
114#[cfg(not(any(feature = "std", test)))]
115extern crate alloc;
116#[cfg(not(any(feature = "std", test)))]
117use alloc::{
118 format,
119 string::{String, ToString},
120 vec::Vec,
121};
122
123mod errors;
124mod parser;
125use parser::{parse_captures, parse_captures_iter, Captures};
126
127pub use crate::errors::{PemError, Result};
128use base64::Engine as _;
129use core::fmt::Write;
130use core::{fmt, slice, str};
131
132const LINE_WRAP: usize = 64;
134
135#[derive(Debug, Clone, Copy)]
137pub enum LineEnding {
138 CRLF,
140 LF,
142}
143
144#[derive(Debug, Clone, Copy)]
146pub struct EncodeConfig {
147 line_ending: LineEnding,
149
150 line_wrap: usize,
152}
153
154#[derive(PartialEq, Debug, Clone)]
156pub struct Pem {
157 tag: String,
158 headers: HeaderMap,
159 contents: Vec<u8>,
160}
161
162#[derive(Clone, Debug, Default, PartialEq)]
164pub struct HeaderMap(Vec<String>);
165
166fn decode_data(raw_data: &str) -> Result<Vec<u8>> {
167 let data: String = raw_data.chars().filter(|c| !c.is_whitespace()).collect();
170
171 let contents = base64::engine::general_purpose::STANDARD
173 .decode(data)
174 .map_err(PemError::InvalidData)?;
175
176 Ok(contents)
177}
178
179#[derive(Debug)]
181pub struct HeadersIter<'a> {
182 cur: slice::Iter<'a, String>,
183}
184
185impl<'a> Iterator for HeadersIter<'a> {
186 type Item = (&'a str, &'a str);
187
188 fn next(&mut self) -> Option<Self::Item> {
189 self.cur.next().and_then(HeaderMap::split_header)
190 }
191}
192
193impl<'a> DoubleEndedIterator for HeadersIter<'a> {
194 fn next_back(&mut self) -> Option<Self::Item> {
195 self.cur.next_back().and_then(HeaderMap::split_header)
196 }
197}
198
199impl HeaderMap {
200 #[allow(clippy::ptr_arg)]
201 fn split_header(header: &String) -> Option<(&str, &str)> {
202 header
203 .split_once(':')
204 .map(|(key, value)| (key.trim(), value.trim()))
205 }
206
207 fn parse(headers: Vec<String>) -> Result<HeaderMap> {
208 headers.iter().try_for_each(|hline| {
209 Self::split_header(hline)
210 .map(|_| ())
211 .ok_or_else(|| PemError::InvalidHeader(hline.to_string()))
212 })?;
213 Ok(HeaderMap(headers))
214 }
215
216 pub fn iter(&self) -> HeadersIter<'_> {
218 HeadersIter { cur: self.0.iter() }
219 }
220
221 pub fn get(&self, key: &str) -> Option<&str> {
223 self.iter().rev().find(|(k, _)| *k == key).map(|(_, v)| v)
224 }
225
226 pub fn add(&mut self, key: &str, value: &str) -> Result<()> {
228 ensure!(
229 !(key.contains(':') || key.contains('\n')),
230 PemError::InvalidHeader(key.to_string())
231 );
232 ensure!(
233 !(value.contains(':') || value.contains('\n')),
234 PemError::InvalidHeader(value.to_string())
235 );
236 self.0.push(format!("{}: {}", key.trim(), value.trim()));
237 Ok(())
238 }
239}
240
241impl EncodeConfig {
242 pub const fn new() -> Self {
244 Self {
245 line_ending: LineEnding::CRLF,
246 line_wrap: LINE_WRAP,
247 }
248 }
249
250 pub const fn set_line_ending(mut self, line_ending: LineEnding) -> Self {
252 self.line_ending = line_ending;
253 self
254 }
255
256 pub const fn set_line_wrap(mut self, line_wrap: usize) -> Self {
258 self.line_wrap = line_wrap;
259 self
260 }
261}
262
263impl Default for EncodeConfig {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268
269impl Pem {
270 pub fn new(tag: impl ToString, contents: impl Into<Vec<u8>>) -> Pem {
272 Pem {
273 tag: tag.to_string(),
274 headers: HeaderMap::default(),
275 contents: contents.into(),
276 }
277 }
278
279 pub fn tag(&self) -> &str {
281 &self.tag
282 }
283
284 pub fn contents(&self) -> &[u8] {
286 &self.contents
287 }
288
289 pub fn into_contents(self) -> Vec<u8> {
291 self.contents
292 }
293
294 pub fn headers(&self) -> &HeaderMap {
296 &self.headers
297 }
298
299 pub fn headers_mut(&mut self) -> &mut HeaderMap {
301 &mut self.headers
302 }
303
304 fn new_from_captures(caps: Captures) -> Result<Pem> {
305 fn as_utf8(bytes: &[u8]) -> Result<&str> {
306 str::from_utf8(bytes).map_err(PemError::NotUtf8)
307 }
308
309 let tag = as_utf8(caps.begin)?;
311 if tag.is_empty() {
312 return Err(PemError::MissingBeginTag);
313 }
314
315 let tag_end = as_utf8(caps.end)?;
317 if tag_end.is_empty() {
318 return Err(PemError::MissingEndTag);
319 }
320
321 if tag != tag_end {
323 return Err(PemError::MismatchedTags(tag.into(), tag_end.into()));
324 }
325
326 let raw_data = as_utf8(caps.data)?;
328 let contents = decode_data(raw_data)?;
329 let headers: Vec<String> = as_utf8(caps.headers)?.lines().map(str::to_string).collect();
330 let headers = HeaderMap::parse(headers)?;
331
332 let mut file = Pem::new(tag, contents);
333 file.headers = headers;
334
335 Ok(file)
336 }
337}
338
339impl str::FromStr for Pem {
340 type Err = PemError;
341
342 fn from_str(s: &str) -> Result<Pem> {
343 parse(s)
344 }
345}
346
347impl TryFrom<&[u8]> for Pem {
348 type Error = PemError;
349
350 fn try_from(s: &[u8]) -> Result<Pem> {
351 parse(s)
352 }
353}
354
355impl fmt::Display for Pem {
356 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
357 write!(f, "{}", encode(self))
358 }
359}
360
361pub fn parse<B: AsRef<[u8]>>(input: B) -> Result<Pem> {
405 parse_captures(input.as_ref())
406 .ok_or(PemError::MalformedFraming)
407 .and_then(Pem::new_from_captures)
408}
409
410pub fn parse_many<B: AsRef<[u8]>>(input: B) -> Result<Vec<Pem>> {
480 parse_captures_iter(input.as_ref())
482 .map(Pem::new_from_captures)
483 .collect()
484}
485
486pub fn encode(pem: &Pem) -> String {
496 encode_config(pem, EncodeConfig::default())
497}
498
499pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String {
510 let line_ending = match config.line_ending {
511 LineEnding::CRLF => "\r\n",
512 LineEnding::LF => "\n",
513 };
514
515 let mut output = String::new();
516
517 let contents = if pem.contents.is_empty() {
518 String::from("")
519 } else {
520 base64::engine::general_purpose::STANDARD.encode(&pem.contents)
521 };
522
523 write!(output, "-----BEGIN {}-----{}", pem.tag, line_ending).unwrap();
524 if !pem.headers.0.is_empty() {
525 for line in &pem.headers.0 {
526 write!(output, "{}{}", line.trim(), line_ending).unwrap();
527 }
528 output.push_str(line_ending);
529 }
530 for c in contents.as_bytes().chunks(config.line_wrap) {
531 write!(output, "{}{}", str::from_utf8(c).unwrap(), line_ending).unwrap();
532 }
533 write!(output, "-----END {}-----{}", pem.tag, line_ending).unwrap();
534
535 output
536}
537
538pub fn encode_many(pems: &[Pem]) -> String {
551 pems.iter()
552 .map(encode)
553 .collect::<Vec<String>>()
554 .join("\r\n")
555}
556
557pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String {
573 let line_ending = match config.line_ending {
574 LineEnding::CRLF => "\r\n",
575 LineEnding::LF => "\n",
576 };
577 pems.iter()
578 .map(|value| encode_config(value, config))
579 .collect::<Vec<String>>()
580 .join(line_ending)
581}
582
583#[cfg(feature = "serde")]
584mod serde_impl {
585 use super::{encode, parse, Pem};
586 use core::fmt;
587 use serde::{
588 de::{Error, Visitor},
589 Deserialize, Serialize,
590 };
591
592 impl Serialize for Pem {
593 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
594 where
595 S: serde::Serializer,
596 {
597 serializer.serialize_str(&encode(self))
598 }
599 }
600
601 struct PemVisitor;
602
603 impl<'de> Visitor<'de> for PemVisitor {
604 type Value = Pem;
605
606 fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
607 Ok(())
608 }
609
610 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
611 where
612 E: Error,
613 {
614 parse(v).map_err(Error::custom)
615 }
616 }
617
618 impl<'de> Deserialize<'de> for Pem {
619 fn deserialize<D>(deserializer: D) -> Result<Pem, D::Error>
620 where
621 D: serde::Deserializer<'de>,
622 {
623 deserializer.deserialize_str(PemVisitor)
624 }
625 }
626}
627
628#[cfg(test)]
629mod test {
630 use super::*;
631 use proptest::prelude::*;
632 use std::error::Error;
633
634 const SAMPLE_CRLF: &str = "-----BEGIN RSA PRIVATE KEY-----\r
635MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
636dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
6372gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
638AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
639DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
640TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
641ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
642-----END RSA PRIVATE KEY-----\r
643\r
644-----BEGIN RSA PUBLIC KEY-----\r
645MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
646QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
647RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
648sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
649ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
650/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
651RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
652-----END RSA PUBLIC KEY-----\r
653";
654
655 const SAMPLE_LF: &str = "-----BEGIN RSA PRIVATE KEY-----
656MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
657dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
6582gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
659AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
660DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
661TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
662ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
663-----END RSA PRIVATE KEY-----
664
665-----BEGIN RSA PUBLIC KEY-----
666MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
667QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
668RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
669sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
670ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
671/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
672RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
673-----END RSA PUBLIC KEY-----
674";
675
676 const SAMPLE_WS: &str = "-----BEGIN RSA PRIVATE KEY-----
677MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc \
678dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO \
6792gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei \
680AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un \
681DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT \
682TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh \
683ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
684-----END RSA PRIVATE KEY-----
685
686-----BEGIN RSA PUBLIC KEY-----
687MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo \
688QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 \
689RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI \
690sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk \
691ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 \
692/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g \
693RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
694-----END RSA PUBLIC KEY-----
695";
696
697 const SAMPLE_DEFAULT_LINE_WRAP: &str = "-----BEGIN TEST-----\r
698AQIDBA==\r
699-----END TEST-----\r
700";
701
702 const SAMPLE_CUSTOM_LINE_WRAP_4: &str = "-----BEGIN TEST-----\r
703AQID\r
704BA==\r
705-----END TEST-----\r
706";
707
708 #[test]
709 fn test_parse_works() {
710 let pem = parse(SAMPLE_CRLF).unwrap();
711 assert_eq!(pem.tag(), "RSA PRIVATE KEY");
712 }
713
714 #[test]
715 fn test_parse_empty_space() {
716 let pem = parse(SAMPLE_WS).unwrap();
717 assert_eq!(pem.tag(), "RSA PRIVATE KEY");
718 }
719
720 #[test]
721 fn test_parse_invalid_framing() {
722 let input = "--BEGIN data-----
723 -----END data-----";
724 assert_eq!(parse(input), Err(PemError::MalformedFraming));
725 }
726
727 #[test]
728 fn test_parse_invalid_begin() {
729 let input = "-----BEGIN -----
730MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
731QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
732RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
733sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
734ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
735/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
736RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
737-----END RSA PUBLIC KEY-----";
738 assert_eq!(parse(input), Err(PemError::MissingBeginTag));
739 }
740
741 #[test]
742 fn test_parse_invalid_end() {
743 let input = "-----BEGIN DATA-----
744MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
745QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
746RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
747sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
748ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
749/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
750RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
751-----END -----";
752 assert_eq!(parse(input), Err(PemError::MissingEndTag));
753 }
754
755 #[test]
756 fn test_parse_invalid_data() {
757 let input = "-----BEGIN DATA-----
758MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY?
759QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
760RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
761sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
762ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
763/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
764RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
765-----END DATA-----";
766 match parse(input) {
767 Err(e @ PemError::InvalidData(_)) => {
768 assert_eq!(
769 &format!("{}", e.source().unwrap()),
770 "Invalid symbol 63, offset 63."
771 );
772 }
773 _ => unreachable!(),
774 }
775 }
776
777 #[test]
778 fn test_parse_empty_data() {
779 let input = "-----BEGIN DATA-----
780-----END DATA-----";
781 let pem = parse(input).unwrap();
782 assert_eq!(pem.contents().len(), 0);
783 }
784
785 #[test]
786 fn test_parse_many_works() {
787 let pems = parse_many(SAMPLE_CRLF).unwrap();
788 assert_eq!(pems.len(), 2);
789 assert_eq!(pems[0].tag(), "RSA PRIVATE KEY");
790 assert_eq!(pems[1].tag(), "RSA PUBLIC KEY");
791 }
792
793 #[test]
794 fn test_parse_many_errors_on_invalid_section() {
795 let input = SAMPLE_LF.to_owned() + "-----BEGIN -----\n-----END -----";
796 assert_eq!(parse_many(input), Err(PemError::MissingBeginTag));
797 }
798
799 #[test]
800 fn test_encode_default_line_wrap() {
801 let pem = Pem::new("TEST", vec![1, 2, 3, 4]);
802 assert_eq!(encode(&pem), SAMPLE_DEFAULT_LINE_WRAP);
803 }
804
805 #[test]
806 fn test_encode_custom_line_wrap_4() {
807 let pem = Pem::new("TEST", vec![1, 2, 3, 4]);
808 assert_eq!(
809 encode_config(&pem, EncodeConfig::default().set_line_wrap(4)),
810 SAMPLE_CUSTOM_LINE_WRAP_4
811 );
812 }
813 #[test]
814 fn test_encode_empty_contents() {
815 let pem = Pem::new("FOO", vec![]);
816 let encoded = encode(&pem);
817 assert!(!encoded.is_empty());
818
819 let pem_out = parse(&encoded).unwrap();
820 assert_eq!(&pem, &pem_out);
821 }
822
823 #[test]
824 fn test_encode_contents() {
825 let pem = Pem::new("FOO", [1, 2, 3, 4]);
826 let encoded = encode(&pem);
827 assert!(!encoded.is_empty());
828
829 let pem_out = parse(&encoded).unwrap();
830 assert_eq!(&pem, &pem_out);
831 }
832
833 #[test]
834 fn test_encode_many() {
835 let pems = parse_many(SAMPLE_CRLF).unwrap();
836 let encoded = encode_many(&pems);
837
838 assert_eq!(SAMPLE_CRLF, encoded);
839 }
840
841 #[test]
842 fn test_encode_config_contents() {
843 let pem = Pem::new("FOO", [1, 2, 3, 4]);
844 let config = EncodeConfig::default().set_line_ending(LineEnding::LF);
845 let encoded = encode_config(&pem, config);
846 assert!(!encoded.is_empty());
847
848 let pem_out = parse(&encoded).unwrap();
849 assert_eq!(&pem, &pem_out);
850 }
851
852 #[test]
853 fn test_encode_many_config() {
854 let pems = parse_many(SAMPLE_LF).unwrap();
855 let config = EncodeConfig::default().set_line_ending(LineEnding::LF);
856 let encoded = encode_many_config(&pems, config);
857
858 assert_eq!(SAMPLE_LF, encoded);
859 }
860
861 #[cfg(feature = "serde")]
862 #[test]
863 fn test_serde() {
864 let pem = Pem::new("Mock tag", "Mock contents".as_bytes());
865 let value = serde_json::to_string_pretty(&pem).unwrap();
866 let result = serde_json::from_str(&value).unwrap();
867 assert_eq!(pem, result);
868 }
869
870 const HEADER_CRLF: &str = "-----BEGIN CERTIFICATE-----\r
871MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
872dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
8732gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
874AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
875DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
876TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
877ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r
878-----END CERTIFICATE-----\r
879-----BEGIN RSA PRIVATE KEY-----\r
880Proc-Type: 4,ENCRYPTED\r
881DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r
882\r
883MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
884QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
885RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
886sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
887ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
888/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
889RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r
890-----END RSA PRIVATE KEY-----\r
891";
892 const HEADER_CRLF_DATA: [&str; 2] = [
893 "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r
894dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r
8952gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r
896AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r
897DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r
898TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r
899ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r",
900 "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r
901QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r
902RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r
903sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r
904ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r
905/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r
906RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r",
907 ];
908
909 const HEADER_LF: &str = "-----BEGIN CERTIFICATE-----
910MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
911dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
9122gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
913AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
914DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
915TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
916ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ
917-----END CERTIFICATE-----
918-----BEGIN RSA PRIVATE KEY-----
919Proc-Type: 4,ENCRYPTED
920DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6
921
922MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
923QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
924RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
925sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
926ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
927/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
928RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
929-----END RSA PRIVATE KEY-----
930";
931 const HEADER_LF_DATA: [&str; 2] = [
932 "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
933dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
9342gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei
935AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un
936DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT
937TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh
938ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ",
939 "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
940QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
941RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
942sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
943ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
944/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
945RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg",
946 ];
947
948 fn cmp_data(left: &[u8], right: &[u8]) -> bool {
949 if left.len() != right.len() {
950 false
951 } else {
952 left.iter()
953 .zip(right.iter())
954 .all(|(left, right)| left == right)
955 }
956 }
957
958 #[test]
959 fn test_parse_many_with_headers_crlf() {
960 let pems = parse_many(HEADER_CRLF).unwrap();
961 assert_eq!(pems.len(), 2);
962 assert_eq!(pems[0].tag(), "CERTIFICATE");
963 assert!(cmp_data(
964 pems[0].contents(),
965 &decode_data(HEADER_CRLF_DATA[0]).unwrap()
966 ));
967 assert_eq!(pems[1].tag(), "RSA PRIVATE KEY");
968 assert!(cmp_data(
969 pems[1].contents(),
970 &decode_data(HEADER_CRLF_DATA[1]).unwrap()
971 ));
972 }
973
974 #[test]
975 fn test_parse_many_with_headers_lf() {
976 let pems = parse_many(HEADER_LF).unwrap();
977 assert_eq!(pems.len(), 2);
978 assert_eq!(pems[0].tag(), "CERTIFICATE");
979 assert!(cmp_data(
980 pems[0].contents(),
981 &decode_data(HEADER_LF_DATA[0]).unwrap()
982 ));
983 assert_eq!(pems[1].tag(), "RSA PRIVATE KEY");
984 assert!(cmp_data(
985 pems[1].contents(),
986 &decode_data(HEADER_LF_DATA[1]).unwrap()
987 ));
988 }
989
990 proptest! {
991 #[test]
992 fn test_str_parse_and_display(tag in "[A-Z ]+", contents in prop::collection::vec(0..255u8, 0..200)) {
993 let pem = Pem::new(tag, contents);
994 prop_assert_eq!(&pem, &pem.to_string().parse::<Pem>().unwrap());
995 }
996
997 #[test]
998 fn test_str_parse_and_display_with_headers(tag in "[A-Z ]+",
999 key in "[a-zA-Z]+",
1000 value in "[a-zA-A]+",
1001 contents in prop::collection::vec(0..255u8, 0..200)) {
1002 let mut pem = Pem::new(tag, contents);
1003 pem.headers_mut().add(&key, &value).unwrap();
1004 prop_assert_eq!(&pem, &pem.to_string().parse::<Pem>().unwrap());
1005 }
1006 }
1007
1008 #[test]
1009 fn test_extract_headers() {
1010 let pems = parse_many(HEADER_CRLF).unwrap();
1011 let headers = pems[1].headers().iter().collect::<Vec<_>>();
1012 assert_eq!(headers.len(), 2);
1013 assert_eq!(headers[0].0, "Proc-Type");
1014 assert_eq!(headers[0].1, "4,ENCRYPTED");
1015 assert_eq!(headers[1].0, "DEK-Info");
1016 assert_eq!(headers[1].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6");
1017
1018 let headers = pems[1].headers().iter().rev().collect::<Vec<_>>();
1019 assert_eq!(headers.len(), 2);
1020 assert_eq!(headers[1].0, "Proc-Type");
1021 assert_eq!(headers[1].1, "4,ENCRYPTED");
1022 assert_eq!(headers[0].0, "DEK-Info");
1023 assert_eq!(headers[0].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6");
1024 }
1025
1026 #[test]
1027 fn test_get_header() {
1028 let pems = parse_many(HEADER_CRLF).unwrap();
1029 let headers = pems[1].headers();
1030 assert_eq!(headers.get("Proc-Type"), Some("4,ENCRYPTED"));
1031 assert_eq!(
1032 headers.get("DEK-Info"),
1033 Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6")
1034 );
1035 }
1036
1037 #[test]
1038 fn test_only_get_latest() {
1039 const LATEST: &str = "-----BEGIN RSA PRIVATE KEY-----
1040Proc-Type: 4,ENCRYPTED
1041DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6
1042Proc-Type: 42,DECRYPTED
1043
1044MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo
1045QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0
1046RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI
1047sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk
1048ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6
1049/+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g
1050RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg
1051-----END RSA PRIVATE KEY-----
1052";
1053 let pem = parse(LATEST).unwrap();
1054 let headers = pem.headers();
1055 assert_eq!(headers.get("Proc-Type"), Some("42,DECRYPTED"));
1056 assert_eq!(
1057 headers.get("DEK-Info"),
1058 Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6")
1059 );
1060 }
1061}