1use alloc::{string::String, vec::Vec};
2use core::fmt;
3
4use memchr::{memchr, memchr2, memmem};
5
6const BEGIN_MARKER: &[u8] = b"-----BEGIN ";
7const END_MARKER: &[u8] = b"-----END ";
8const MARKER_END: &[u8] = b"-----";
9const LINE_WIDTH: usize = 64;
10
11#[derive(Clone, PartialEq, Eq)]
12pub struct Block<'a> {
13 pub r#type: &'a str,
14 pub headers: Headers,
15 pub contents: Vec<u8>,
16}
17
18impl fmt::Debug for Block<'_> {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 f.debug_struct("Block")
21 .field("type", &self.r#type)
22 .field("headers", &self.headers)
23 .field("contents.len", &self.contents.len())
24 .finish()
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum PemError<'a> {
30 InvalidEncoding(&'static str),
31 Base64(base64::DecodeError),
32 LabelMismatch { expected: &'a str, actual: &'a str },
33}
34
35impl<'a> From<base64::DecodeError> for PemError<'a> {
36 fn from(err: base64::DecodeError) -> Self {
37 PemError::Base64(err)
38 }
39}
40
41#[cfg(feature = "alloc")]
42impl core::fmt::Display for PemError<'_> {
43 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44 match self {
45 PemError::InvalidEncoding(str) => write!(f, "{str}"),
46 PemError::Base64(err) => write!(f, "base64 decode error: {err}"),
47 PemError::LabelMismatch {
48 expected,
49 actual,
50 } => {
51 write!(f, "label mismatch: expected '{expected}', got '{actual}'")
52 }
53 }
54 }
55}
56
57pub fn encode(blocks: &[Block<'_>]) -> Vec<u8> {
58 let mut capacity = 0usize;
59 let mut base64_capacity = 0;
60
61 for block in blocks {
62 capacity += 11 + block.r#type.len() + 6;
63 for (k, v) in block.headers.iter() {
64 capacity += k.len() + 2 + v.len() + 1;
65 }
66 if !block.headers.is_empty() {
67 capacity += 1;
68 }
69 let b64_len = base64::encoded_length(block.contents.len(), true).unwrap_or_default();
70 base64_capacity += b64_len;
71 capacity += b64_len + b64_len / 64 + 1;
72 capacity += 9 + block.r#type.len() + 6;
73 }
74
75 let mut output = Vec::with_capacity(capacity);
76 let mut b64_buf = String::with_capacity(base64_capacity);
77
78 for block in blocks {
79 output.extend_from_slice(b"-----BEGIN ");
80 output.extend_from_slice(block.r#type.as_bytes());
81 output.extend_from_slice(b"-----\n");
82
83 for (key, value) in block.headers.iter() {
84 output.extend_from_slice(key.as_bytes());
85 output.extend_from_slice(b": ");
86 output.extend_from_slice(value.as_bytes());
87 output.push(b'\n');
88 }
89
90 if !block.headers.is_empty() {
91 output.push(b'\n');
92 }
93
94 base64::encode_into_string(&mut b64_buf, &block.contents, base64::Alphabet::Standard);
95 for chunk in b64_buf.as_bytes().chunks(LINE_WIDTH) {
96 output.extend_from_slice(chunk);
97 output.push(b'\n');
98 }
99 b64_buf.clear();
100
101 output.extend_from_slice(b"-----END ");
102 output.extend_from_slice(block.r#type.as_bytes());
103 output.extend_from_slice(b"-----\n");
104 }
105 return output;
106}
107
108pub fn decode<'a>(pem: &'a [u8]) -> Blocks<'a> {
109 Blocks {
110 input: pem,
111 pos: 0,
112 }
113}
114
115pub struct Blocks<'a> {
116 input: &'a [u8],
117 pos: usize,
118}
119
120impl<'a> Iterator for Blocks<'a> {
121 type Item = Result<Block<'a>, PemError<'a>>;
122
123 fn next(&mut self) -> Option<Self::Item> {
124 if self.pos >= self.input.len() {
125 return None;
126 }
127
128 let result = parse_one_block(self.input, &mut self.pos);
129
130 return match result {
131 Ok(block) => Some(Ok(block)),
132 Err(err) => {
133 self.pos = self.input.len();
134 Some(Err(err))
135 }
136 };
137 }
138}
139
140#[derive(Clone, Copy)]
141struct Header {
142 key_start: usize,
143 key_len: usize,
144 value_start: usize,
145 value_len: usize,
146}
147
148#[derive(Clone)]
149pub struct Headers {
150 buf: String,
151 pairs: Vec<Header>,
152}
153
154impl Headers {
155 pub fn new() -> Self {
156 Headers {
157 buf: String::new(),
158 pairs: Vec::new(),
159 }
160 }
161
162 pub fn with_capacity(buf: usize, headers: usize) -> Self {
163 Headers {
164 buf: String::with_capacity(buf),
165 pairs: Vec::with_capacity(headers),
166 }
167 }
168
169 pub fn from_pairs(pairs: &[(&str, &str)]) -> Self {
170 let buf_capacity = pairs.iter().fold(0, |acc, pair| acc + pair.0.len() + pair.1.len());
171 let mut headers = Headers::with_capacity(buf_capacity, pairs.len());
172
173 for (k, v) in pairs {
174 headers.buf.reserve(k.len() + v.len());
175 let key_start = headers.buf.len();
176 headers.buf.push_str(k);
177 let value_start = headers.buf.len();
178 headers.buf.push_str(v);
179 headers.pairs.push(Header {
180 key_start,
181 key_len: value_start - key_start,
182 value_start,
183 value_len: headers.buf.len() - value_start,
184 });
185 }
186
187 headers
188 }
189
190 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
191 self.pairs.iter().map(move |span| {
192 let key = &self.buf[span.key_start..span.key_start + span.key_len];
193 let value = &self.buf[span.value_start..span.value_start + span.value_len];
194 (key, value)
195 })
196 }
197
198 pub fn len(&self) -> usize {
199 self.pairs.len()
200 }
201
202 pub fn is_empty(&self) -> bool {
203 self.pairs.is_empty()
204 }
205
206 pub fn push(&mut self, key: &str, value: &str) {
207 let key_start = self.buf.len();
208 self.buf.push_str(key);
209 let value_start = self.buf.len();
210 self.buf.push_str(value);
211 self.pairs.push(Header {
212 key_start,
213 key_len: value_start - key_start,
214 value_start,
215 value_len: self.buf.len() - value_start,
216 });
217 }
218}
219
220impl PartialEq for Headers {
221 fn eq(&self, other: &Self) -> bool {
222 self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
223 }
224}
225
226impl Eq for Headers {}
227
228impl fmt::Debug for Headers {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.debug_list().entries(self.iter()).finish()
231 }
232}
233
234fn parse_one_block<'a>(input: &'a [u8], pos: &mut usize) -> Result<Block<'a>, PemError<'a>> {
235 let remaining = &input[*pos..];
236 let begin_offset = find_pattern(remaining, BEGIN_MARKER).ok_or(PemError::InvalidEncoding("no BEGIN line found"))?;
237
238 let label_start = *pos + begin_offset + BEGIN_MARKER.len();
239 let (r#type, advance) = {
240 let remaining = &input[label_start..];
241 let line_end = find_line_end(remaining).ok_or(PemError::InvalidEncoding("unexpected end of PEM data"))?;
242 let label_bytes = &remaining[..line_end];
243
244 if label_bytes.len() < MARKER_END.len()
245 || label_bytes[label_bytes.len() - MARKER_END.len()..] != *MARKER_END
246 || (label_bytes.len() > MARKER_END.len() && label_bytes[label_bytes.len() - MARKER_END.len() - 1] == b'-')
247 {
248 return Err(PemError::InvalidEncoding("malformed BEGIN line"));
249 }
250
251 let label = &label_bytes[..label_bytes.len() - MARKER_END.len()];
252 let r#type = core::str::from_utf8(label).map_err(|_| PemError::InvalidEncoding("non-UTF-8 label"))?;
253 (r#type, line_advance(remaining))
254 };
255
256 let mut cursor = label_start + advance;
257
258 let mut header_buf = String::new();
259 let mut pairs: Vec<Header> = Vec::new();
260
261 loop {
262 if cursor >= input.len() {
263 return Err(PemError::InvalidEncoding("unexpected end of PEM data"));
264 }
265 let remaining = &input[cursor..];
266 let line_end = find_line_end(remaining).ok_or(PemError::InvalidEncoding("unexpected end of PEM data"))?;
267 let line = &remaining[..line_end];
268
269 if line.starts_with(END_MARKER) {
270 let contents = Vec::new();
271 *pos = cursor + line_advance(remaining);
272 return Ok(Block {
273 r#type,
274 headers: Headers {
275 buf: header_buf,
276 pairs,
277 },
278 contents,
279 });
280 }
281
282 if line.is_empty() || line.iter().all(|&b| b.is_ascii_whitespace()) {
283 cursor += line_advance(remaining);
284 break;
285 }
286
287 if let Some(colon_pos) = memchr(b':', line) {
288 let key = core::str::from_utf8(&line[..colon_pos])
289 .map_err(|_| PemError::InvalidEncoding("non-UTF-8 header key"))?;
290 let value_start = colon_pos + 1;
291 let value = if value_start < line.len() && line[value_start] == b' ' {
292 &line[value_start + 1..]
293 } else {
294 &line[value_start..]
295 };
296 let value_str =
297 core::str::from_utf8(value).map_err(|_| PemError::InvalidEncoding("non-UTF-8 header value"))?;
298
299 cursor += line_advance(remaining);
300
301 let key_start = header_buf.len();
302 header_buf.push_str(key);
303 let key_len = header_buf.len() - key_start;
304
305 let has_continuation = cursor < input.len() && {
306 let rest = &input[cursor..];
307 let cont_line_end = find_line_end(rest).unwrap_or(rest.len());
308 let cont_line = &rest[..cont_line_end];
309 !cont_line.is_empty() && (cont_line[0] == b' ' || cont_line[0] == b'\t')
310 };
311
312 if has_continuation {
313 let mut full_value = String::from(value_str);
314 loop {
315 if cursor >= input.len() {
316 break;
317 }
318 let rest = &input[cursor..];
319 let cont_line_end =
320 find_line_end(rest).ok_or(PemError::InvalidEncoding("unexpected end of PEM data"))?;
321 let cont_line = &rest[..cont_line_end];
322 if cont_line.is_empty() {
323 break;
324 }
325 if cont_line[0] != b' ' && cont_line[0] != b'\t' {
326 break;
327 }
328 let cont_trimmed = cont_line.trim_ascii_start();
329 if !cont_trimmed.is_empty() {
330 if !full_value.is_empty() {
331 full_value.push(' ');
332 }
333 if let Ok(s) = core::str::from_utf8(cont_trimmed) {
334 full_value.push_str(s);
335 }
336 }
337 cursor += line_advance(rest);
338 }
339 let val_start = header_buf.len();
340 header_buf.push_str(&full_value);
341 let val_len = header_buf.len() - val_start;
342 pairs.push(Header {
343 key_start,
344 key_len,
345 value_start: val_start,
346 value_len: val_len,
347 });
348 } else {
349 let val_start = header_buf.len();
350 header_buf.push_str(value_str);
351 let val_len = header_buf.len() - val_start;
352 pairs.push(Header {
353 key_start,
354 key_len,
355 value_start: val_start,
356 value_len: val_len,
357 });
358 }
359 } else {
360 break;
361 }
362 }
363
364 let base64_start = cursor;
365
366 let b64_data_end;
367 let mut search_pos = base64_start;
368
369 loop {
370 if search_pos >= input.len() {
371 return Err(PemError::InvalidEncoding("missing END line"));
372 }
373 let remaining = &input[search_pos..];
374 let end_offset = find_pattern(remaining, END_MARKER);
375 match end_offset {
376 Some(eo) => {
377 let candidate = search_pos + eo;
378 let end_rest = &input[candidate..];
379 let end_line_end =
380 find_line_end(end_rest).ok_or(PemError::InvalidEncoding("unexpected end of data"))?;
381 let end_line = &end_rest[..end_line_end];
382
383 let end_label_bytes = &end_line[END_MARKER.len()..];
384 if end_label_bytes.len() >= MARKER_END.len()
385 && end_label_bytes[end_label_bytes.len() - MARKER_END.len()..] == *MARKER_END
386 && (end_label_bytes.len() == MARKER_END.len()
387 || end_label_bytes[end_label_bytes.len() - MARKER_END.len() - 1] != b'-')
388 {
389 let end_label = &end_label_bytes[..end_label_bytes.len() - MARKER_END.len()];
390 if end_label == r#type.as_bytes() {
391 b64_data_end = candidate;
392 break;
393 }
394 let actual = core::str::from_utf8(end_label).unwrap_or("(invalid UTF-8)");
395 return Err(PemError::LabelMismatch {
396 expected: r#type,
397 actual,
398 });
399 }
400 search_pos = candidate + 1;
401 }
402 None => {
403 return Err(PemError::InvalidEncoding("missing END line"));
404 }
405 }
406 }
407
408 let b64_text = &input[base64_start..b64_data_end];
409
410 let end_remaining = &input[b64_data_end..];
411 *pos = b64_data_end + line_advance(end_remaining);
412
413 let b64_clean: Vec<u8> = b64_text
414 .iter()
415 .copied()
416 .filter(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/' || b == b'=')
417 .collect();
418
419 let contents = base64::decode(&b64_clean, base64::Alphabet::Standard)?;
420
421 return Ok(Block {
422 r#type,
423 headers: Headers {
424 buf: header_buf,
425 pairs,
426 },
427 contents,
428 });
429}
430
431fn find_pattern(data: &[u8], pattern: &[u8]) -> Option<usize> {
432 if pattern.is_empty() || data.len() < pattern.len() {
433 return None;
434 }
435 memmem::find(data, pattern)
436}
437
438fn find_line_end(data: &[u8]) -> Option<usize> {
439 if data.is_empty() {
440 return None;
441 }
442 Some(memchr2(b'\n', b'\r', data).unwrap_or(data.len()))
443}
444
445fn line_advance(data: &[u8]) -> usize {
446 if data.is_empty() {
447 return 0;
448 }
449 if let Some(i) = memchr2(b'\n', b'\r', data) {
450 if data[i] == b'\n' {
451 return i + 1;
452 }
453 if i + 1 < data.len() && data[i + 1] == b'\n' {
455 return i + 2;
456 }
457 return i + 1;
458 }
459 data.len()
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 fn roundtrip(blocks: &[Block<'_>]) {
467 let pem = encode(blocks);
468 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
469 for (i, result) in decoded.iter().enumerate() {
470 let decoded_block = result.as_ref().unwrap();
471 assert_eq!(decoded_block.r#type, blocks[i].r#type, "block {} type mismatch", i);
472 assert_eq!(decoded_block.contents, blocks[i].contents, "block {} contents mismatch", i);
473 assert_eq!(decoded_block.headers, blocks[i].headers, "block {} headers mismatch", i);
474 }
475 assert_eq!(decoded.len(), blocks.len(), "block count mismatch");
476 }
477
478 #[test]
479 fn empty_content() {
480 let blocks = [Block {
481 r#type: "CERTIFICATE".into(),
482 headers: Headers::new(),
483 contents: Vec::new(),
484 }];
485 roundtrip(&blocks);
486 }
487
488 #[test]
489 fn simple_certificate() {
490 let blocks = [Block {
491 r#type: "CERTIFICATE".into(),
492 headers: Headers::new(),
493 contents: b"hello world".to_vec(),
494 }];
495 roundtrip(&blocks);
496 }
497
498 #[test]
499 fn binary_content() {
500 let contents: Vec<u8> = (0u8..255).collect();
501 let blocks = [Block {
502 r#type: "PRIVATE KEY".into(),
503 headers: Headers::new(),
504 contents,
505 }];
506 roundtrip(&blocks);
507 }
508
509 #[test]
510 fn exact_48_bytes() {
511 let contents = b"1234567890abcdef1234567890abcdef1234567890abcdef"; let blocks = [Block {
513 r#type: "CERTIFICATE".into(),
514 headers: Headers::new(),
515 contents: contents.to_vec(),
516 }];
517 roundtrip(&blocks);
518 }
519
520 #[test]
521 fn multiple_blocks() {
522 let blocks = [
523 Block {
524 r#type: "CERTIFICATE".into(),
525 headers: Headers::new(),
526 contents: b"first certificate data".to_vec(),
527 },
528 Block {
529 r#type: "CERTIFICATE".into(),
530 headers: Headers::new(),
531 contents: b"second certificate data".to_vec(),
532 },
533 ];
534 roundtrip(&blocks);
535 }
536
537 #[test]
538 fn different_labels() {
539 let blocks = [
540 Block {
541 r#type: "CERTIFICATE".into(),
542 headers: Headers::new(),
543 contents: b"cert data".to_vec(),
544 },
545 Block {
546 r#type: "PRIVATE KEY".into(),
547 headers: Headers::new(),
548 contents: b"key data".to_vec(),
549 },
550 Block {
551 r#type: "PUBLIC KEY".into(),
552 headers: Headers::new(),
553 contents: b"pubkey data".to_vec(),
554 },
555 ];
556 roundtrip(&blocks);
557 }
558
559 #[test]
560 fn with_rfc1421_headers() {
561 let blocks = [Block {
562 r#type: "PRIVACY-ENHANCED MESSAGE".into(),
563 headers: Headers::from_pairs(&[
564 ("Proc-Type", "4,ENCRYPTED"),
565 ("Content-Domain", "RFC822"),
566 ("DEK-Info", "DES-CBC,F8143EDE5960C597"),
567 ]),
568 contents: b"encrypted message data".to_vec(),
569 }];
570 roundtrip(&blocks);
571 }
572
573 #[test]
574 fn header_with_folded_value() {
575 let pem: &[u8] = b"-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n\
576Proc-Type: 4,ENCRYPTED\n\
577Originator-Certificate:\n MIIBlTCCAScCAWUw\n\
578\n\
579SGVsbG8gV29ybGQ=\n\
580-----END PRIVACY-ENHANCED MESSAGE-----\n";
581
582 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
583 assert_eq!(decoded.len(), 1);
584 let block = decoded[0].as_ref().unwrap();
585 assert_eq!(block.r#type, "PRIVACY-ENHANCED MESSAGE");
586 assert!(block.headers.len() >= 1);
587
588 let proc_type = block.headers.iter().find(|&(k, _)| k == "Proc-Type");
589 assert!(proc_type.is_some(), "Proc-Type not found. Headers: {:?}", block.headers);
590 assert_eq!(proc_type.unwrap().1, "4,ENCRYPTED");
591
592 let originator = block.headers.iter().find(|&(k, _)| k == "Originator-Certificate");
593 assert!(
594 originator.is_some(),
595 "Originator-Certificate not found. Headers: {:?}",
596 block.headers
597 );
598 assert_eq!(
599 originator.unwrap().1,
600 "MIIBlTCCAScCAWUw",
601 "Originator-Certificate value mismatch. Got: '{}'",
602 originator.unwrap().1
603 );
604
605 assert_eq!(block.contents, b"Hello World");
606 }
607
608 #[test]
609 fn decode_from_rfc7468_certificate() {
610 let pem = b"-----BEGIN CERTIFICATE-----\n\
611 MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G\n\
612 A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y\n\
613 aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0\n\
614 ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw\n\
615 CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy\n\
616 dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu\n\
617 dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB\n\
618 BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X\n\
619 uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud\n\
620 DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG\n\
621 SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA\n\
622 l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo=\n\
623 -----END CERTIFICATE-----\n";
624
625 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
626 assert_eq!(decoded.len(), 1);
627 let block = decoded[0].as_ref().unwrap();
628 assert_eq!(block.r#type, "CERTIFICATE");
629 assert!(block.headers.is_empty());
630 assert!(!block.contents.is_empty());
631 }
632
633 #[test]
634 fn decode_rfc7468_private_key() {
635 let pem = b"-----BEGIN PRIVATE KEY-----\n\
636 MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgVcB/UNPxalR9zDYAjQIf\n\
637 jojUDiQuGnSJrFEEzZPT/92hRANCAASc7UJtgnF/abqWM60T3XNJEzBv5ez9TdwK\n\
638 H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ\n\
639 -----END PRIVATE KEY-----\n";
640
641 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
642 assert_eq!(decoded.len(), 1);
643 let block = decoded[0].as_ref().unwrap();
644 assert_eq!(block.r#type, "PRIVATE KEY");
645 assert!(!block.contents.is_empty());
646 }
647
648 #[test]
649 fn decode_crlf_line_endings() {
650 let pem = b"-----BEGIN CERTIFICATE-----\r\n\
651 SGVsbG8gV29ybGQ=\r\n\
652 -----END CERTIFICATE-----\r\n";
653
654 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
655 assert_eq!(decoded.len(), 1);
656 let block = decoded[0].as_ref().unwrap();
657 assert_eq!(block.r#type, "CERTIFICATE");
658 assert_eq!(block.contents, b"Hello World");
659 }
660
661 #[test]
662 fn decode_mac_line_endings() {
663 let pem = b"-----BEGIN CERTIFICATE-----\r\
664 SGVsbG8gV29ybGQ=\r\
665 -----END CERTIFICATE-----\r";
666
667 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
668 assert_eq!(decoded.len(), 1);
669 let block = decoded[0].as_ref().unwrap();
670 assert_eq!(block.contents, b"Hello World");
671 }
672
673 #[test]
674 fn decode_multiple_blocks() {
675 let pem = b"-----BEGIN CERTIFICATE-----\n\
676 Rmlyc3Q=\n\
677 -----END CERTIFICATE-----\n\
678 -----BEGIN CERTIFICATE-----\n\
679 U2Vjb25k\n\
680 -----END CERTIFICATE-----\n";
681
682 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
683 assert_eq!(decoded.len(), 2);
684 assert_eq!(decoded[0].as_ref().unwrap().contents, b"First");
685 assert_eq!(decoded[1].as_ref().unwrap().contents, b"Second");
686 }
687
688 #[test]
689 fn decode_with_leading_text() {
690 let pem = b"Some explanatory text\n\
691 Subject: CN=Test\n\
692 Issuer: CN=Test\n\
693 -----BEGIN CERTIFICATE-----\n\
694 SGVsbG8=\n\
695 -----END CERTIFICATE-----\n";
696
697 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
698 assert_eq!(decoded.len(), 1);
699 assert_eq!(decoded[0].as_ref().unwrap().contents, b"Hello");
700 }
701
702 #[test]
703 fn decode_empty_pem() {
704 let pem = b"";
705 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
706 assert!(decoded.is_empty());
707 }
708
709 #[test]
710 fn decode_label_mismatch() {
711 let pem = b"-----BEGIN CERTIFICATE-----\n\
712 SGVsbG8=\n\
713 -----END PRIVATE KEY-----\n";
714
715 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
716 assert_eq!(decoded.len(), 1);
717 assert!(decoded[0].is_err());
718 match &decoded[0] {
719 Err(PemError::LabelMismatch {
720 expected,
721 actual,
722 }) => {
723 assert_eq!(*expected, "CERTIFICATE");
724 assert_eq!(*actual, "PRIVATE KEY");
725 }
726 other => panic!("expected LabelMismatch, got {:?}", other),
727 }
728 }
729
730 #[test]
731 fn decode_missing_end_line() {
732 let pem = b"-----BEGIN CERTIFICATE-----\n\
733 SGVsbG8=\n";
734
735 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
736 assert_eq!(decoded.len(), 1);
737 assert!(decoded[0].is_err());
738 }
739
740 #[test]
741 fn decode_invalid_base64() {
742 let pem = b"-----BEGIN CERTIFICATE-----\n\
743 !!!invalid!!!\n\
744 -----END CERTIFICATE-----\n";
745
746 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
747 assert_eq!(decoded.len(), 1);
748 assert!(decoded[0].is_err());
749 }
750
751 #[test]
752 fn decode_multiline_base64() {
753 let contents = b"This is a test message that is long enough to span multiple lines when base64 encoded with 64 character line width";
754 let blocks = [Block {
755 r#type: "MESSAGE".into(),
756 headers: Headers::new(),
757 contents: contents.to_vec(),
758 }];
759 let pem = encode(&blocks);
760 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
761 assert_eq!(decoded.len(), 1);
762 assert_eq!(decoded[0].as_ref().unwrap().contents, contents);
763 }
764
765 #[test]
766 fn encode_produces_valid_pem() {
767 let block = Block {
768 r#type: "TEST".into(),
769 headers: Headers::new(),
770 contents: b"data".to_vec(),
771 };
772 let pem = encode(&[block]);
773 let pem_str = core::str::from_utf8(&pem).unwrap();
774 assert!(pem_str.starts_with("-----BEGIN TEST-----\n"));
775 assert!(pem_str.contains("\n-----END TEST-----\n"));
776 }
777
778 #[test]
779 fn encode_line_wrapping() {
780 let contents = vec![b'A'; 200];
781 let block = Block {
782 r#type: "DATA".into(),
783 headers: Headers::new(),
784 contents,
785 };
786 let pem = encode(&[block]);
787 let pem_str = core::str::from_utf8(&pem).unwrap();
788 let body = pem_str
789 .strip_prefix("-----BEGIN DATA-----\n")
790 .unwrap()
791 .strip_suffix("\n-----END DATA-----\n")
792 .unwrap();
793 for line in body.lines() {
794 if !line.is_empty() {
795 assert!(line.len() <= 64, "line too long: {} > 64", line.len());
796 }
797 }
798 }
799
800 #[test]
801 fn decode_no_trailing_newline() {
802 let pem = b"-----BEGIN CERTIFICATE-----\n\
803 SGVsbG8=\n\
804 -----END CERTIFICATE-----";
805 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
806 assert_eq!(decoded.len(), 1);
807 assert_eq!(decoded[0].as_ref().unwrap().contents, b"Hello");
808 }
809
810 #[test]
811 fn decode_whitespace_in_base64() {
812 let pem = b"-----BEGIN CERTIFICATE-----\n\
813 SGVs\tbG8g\n\
814 V29y bGQ=\n\
815 -----END CERTIFICATE-----\n";
816
817 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
818 assert_eq!(decoded.len(), 1);
819 assert_eq!(decoded[0].as_ref().unwrap().contents, b"Hello World");
820 }
821
822 #[test]
823 fn decode_interleaved_comment_lines() {
824 let pem = b"-----BEGIN CERTIFICATE-----\n\
825 Proc-Type: 4,ENCRYPTED\n\
826 \n\
827 SGVsbG8=\n\
828 -----END CERTIFICATE-----\n";
829
830 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
831 assert_eq!(decoded.len(), 1);
832 let block = decoded[0].as_ref().unwrap();
833 assert_eq!(block.contents, b"Hello");
834 assert_eq!(block.r#type, "CERTIFICATE");
835 }
836
837 #[test]
838 fn decode_header_no_space_after_colon() {
839 let pem = b"-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n\
840 Proc-Type:4,ENCRYPTED\n\
841 \n\
842 SGVsbG8=\n\
843 -----END PRIVACY-ENHANCED MESSAGE-----\n";
844
845 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
846 assert_eq!(decoded.len(), 1);
847 let block = decoded[0].as_ref().unwrap();
848 assert_eq!(block.contents, b"Hello");
849 assert_eq!(block.headers.iter().next().unwrap().1, "4,ENCRYPTED");
850 }
851
852 #[test]
853 fn openssl_test_vector() {
854 let der: Vec<u8> = (0u8..48).collect();
855
856 let b64 = base64::encode(&der, base64::Alphabet::Standard);
857 let mut lines = Vec::new();
858 for chunk in b64.as_bytes().chunks(64) {
859 lines.push(core::str::from_utf8(chunk).unwrap().to_string());
860 }
861
862 let mut pem = String::from("-----BEGIN CERTIFICATE-----\n");
863 for line in &lines {
864 pem.push_str(line);
865 pem.push('\n');
866 }
867 pem.push_str("-----END CERTIFICATE-----\n");
868
869 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem.as_bytes()).collect();
870 assert_eq!(decoded.len(), 1);
871 assert_eq!(decoded[0].as_ref().unwrap().contents, der);
872 }
873
874 #[test]
875 fn openssl_ec_private_key() {
876 let pem = b"-----BEGIN EC PRIVATE KEY-----\n\
877 MHQCAQEEIIm3VYFh8WkH4lA2KJ6tC3R0H3G7LgZc1Y0Z5Q7sZq6oBwYFK4EE\n\
878 AaahRANCAAQrR4q6kQ8V5lY6Lq3XZ0gG5f7J2sLvG8kH7X4KxVc5oBwYFK4E\n\
879 AaE=\n\
880 -----END EC PRIVATE KEY-----\n";
881
882 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
883 assert_eq!(decoded.len(), 1);
884 let block = decoded[0].as_ref().unwrap();
885 assert_eq!(block.r#type, "EC PRIVATE KEY");
886 assert!(!block.contents.is_empty());
887 }
888
889 #[test]
890 fn python_generated_vector() {
891 let data = b"Hello from Python!";
892 let b64 = base64::encode(data, base64::Alphabet::Standard);
893 let pem = alloc::format!("-----BEGIN PYTHON DATA-----\n{}\n-----END PYTHON DATA-----\n", b64);
894
895 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem.as_bytes()).collect();
896 assert_eq!(decoded.len(), 1);
897 assert_eq!(decoded[0].as_ref().unwrap().contents, data);
898 }
899
900 #[test]
901 fn python_generated_with_headers() {
902 let mut pem = String::from("-----BEGIN PYTHON DATA-----\n");
903 pem.push_str("Content-Type: application/octet-stream\n");
904 pem.push_str("Content-Transfer-Encoding: base64\n");
905 pem.push('\n');
906 let b64 = base64::encode(b"Python header data", base64::Alphabet::Standard);
907 pem.push_str(&b64);
908 pem.push('\n');
909 pem.push_str("-----END PYTHON DATA-----\n");
910
911 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem.as_bytes()).collect();
912 assert_eq!(decoded.len(), 1);
913 let block = decoded[0].as_ref().unwrap();
914 assert_eq!(block.r#type, "PYTHON DATA");
915 assert_eq!(block.contents, b"Python header data");
916 let h_vec: Vec<(&str, &str)> = block.headers.iter().collect();
917 assert_eq!(h_vec.len(), 2);
918 }
919
920 #[test]
921 fn label_with_spaces() {
922 let blocks = [Block {
923 r#type: "CERTIFICATE REQUEST".into(),
924 headers: Headers::new(),
925 contents: b"csr data".to_vec(),
926 }];
927 roundtrip(&blocks);
928 }
929
930 #[test]
931 fn label_empty() {
932 let blocks = [Block {
933 r#type: "",
934 headers: Headers::new(),
935 contents: b"data".to_vec(),
936 }];
937 roundtrip(&blocks);
938 }
939
940 #[test]
941 fn large_content() {
942 let contents = vec![0xABu8; 10000];
943 let blocks = [Block {
944 r#type: "LARGE DATA".into(),
945 headers: Headers::new(),
946 contents,
947 }];
948 roundtrip(&blocks);
949 }
950
951 #[test]
952 fn decode_block_without_base64() {
953 let pem = b"-----BEGIN EMPTY-----\n\
954 -----END EMPTY-----\n";
955
956 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
957 assert_eq!(decoded.len(), 1);
958 let block = decoded[0].as_ref().unwrap();
959 assert_eq!(block.r#type, "EMPTY");
960 assert!(block.contents.is_empty());
961 }
962
963 #[test]
964 fn roundtrip_rfc7468_certificate() {
965 let der: Vec<u8> = (0u8..128).collect();
966 let original = Block {
967 r#type: "CERTIFICATE".into(),
968 headers: Headers::new(),
969 contents: der,
970 };
971 roundtrip(&[original]);
972 }
973
974 #[test]
975 fn roundtrip_rfc1421_encrypted_message() {
976 let original = Block {
977 r#type: "PRIVACY-ENHANCED MESSAGE".into(),
978 headers: Headers::from_pairs(&[
979 ("Proc-Type", "4,ENCRYPTED"),
980 ("Content-Domain", "RFC822"),
981 ("DEK-Info", "DES-CBC,BFF968AA74691AC1"),
982 ]),
983 contents: b"encrypted content here".to_vec(),
984 };
985 roundtrip(&[original]);
986 }
987
988 #[test]
989 fn encode_skip_headers_when_empty() {
990 let block = Block {
991 r#type: "TEST".into(),
992 headers: Headers::new(),
993 contents: b"data".to_vec(),
994 };
995 let pem = encode(&[block]);
996 let pem_str = core::str::from_utf8(&pem).unwrap();
997 let lines: Vec<&str> = pem_str.lines().collect();
998 assert_eq!(lines[0], "-----BEGIN TEST-----");
999 assert_eq!(lines[1], "ZGF0YQ==");
1000 assert_eq!(lines[2], "-----END TEST-----");
1001 }
1002
1003 #[test]
1004 fn encode_include_headers() {
1005 let block = Block {
1006 r#type: "MESSAGE".into(),
1007 headers: Headers::from_pairs(&[("X-Custom", "value123")]),
1008 contents: b"data".to_vec(),
1009 };
1010 let pem = encode(&[block]);
1011 let pem_str = core::str::from_utf8(&pem).unwrap();
1012 assert!(pem_str.contains("X-Custom: value123\n"));
1013 assert!(pem_str.contains("\n\nZGF0YQ=="));
1014 }
1015
1016 #[test]
1017 fn decode_no_newline_before_end_marker() {
1018 let pem = b"-----BEGIN DATA-----\n\
1019 ZGF0YQ==\n\
1020 -----END DATA-----\n";
1021
1022 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1023 assert_eq!(decoded.len(), 1);
1024 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1025 }
1026
1027 #[test]
1028 fn decode_header_values_with_colons() {
1029 let pem = b"-----BEGIN MESSAGE-----\n\
1030 Proc-Type: 4,ENCRYPTED\n\
1031 Key-Info: RSA,abc123\n\
1032 \n\
1033 ZGF0YQ==\n\
1034 -----END MESSAGE-----\n";
1035
1036 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1037 assert_eq!(decoded.len(), 1);
1038 let block = decoded[0].as_ref().unwrap();
1039 let h_vec: Vec<(&str, &str)> = block.headers.iter().collect();
1040 assert_eq!(h_vec.len(), 2);
1041 assert_eq!(h_vec[1].0, "Key-Info");
1042 assert!(h_vec[1].1.contains("RSA,abc123"));
1043 }
1044
1045 #[test]
1046 fn decode_only_first_block_on_error() {
1047 let pem = b"-----BEGIN GOOD-----\n\
1048 R29vZEF0YQ==\n\
1049 -----END GOOD-----\n\
1050 -----BEGIN BAD-----\n\
1051 !!!invalid!!!\n\
1052 -----END BAD-----\n";
1053
1054 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1055 assert_eq!(decoded.len(), 2);
1056 assert!(decoded[0].is_ok());
1057 assert!(decoded[1].is_err());
1058 }
1059
1060 #[cfg(feature = "std")]
1061 #[test]
1062 fn match_openssl_output() {
1063 use std::process::Command;
1064
1065 let result = Command::new("sh").arg("-c").arg("which openssl").output();
1066
1067 if result.map(|r| r.status.success()).unwrap_or(false) {
1068 let output = Command::new("openssl")
1069 .args([
1070 "req",
1071 "-x509",
1072 "-newkey",
1073 "rsa:2048",
1074 "-keyout",
1075 "/dev/null",
1076 "-out",
1077 "/dev/stdout",
1078 "-days",
1079 "365",
1080 "-nodes",
1081 "-subj",
1082 "/CN=TestCert",
1083 ])
1084 .output()
1085 .expect("openssl failed");
1086
1087 assert!(output.status.success());
1088 let pem_bytes = output.stdout;
1089
1090 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem_bytes).collect();
1091 assert_eq!(decoded.len(), 1);
1092 let block = decoded[0].as_ref().unwrap();
1093 assert_eq!(block.r#type, "CERTIFICATE");
1094 assert!(!block.contents.is_empty());
1095
1096 let reencoded = encode(&[block.clone()]);
1097 let re_decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&reencoded).collect();
1098 assert_eq!(re_decoded.len(), 1);
1099 assert_eq!(re_decoded[0].as_ref().unwrap().contents, block.contents);
1100 }
1101 }
1102
1103 #[cfg(feature = "std")]
1104 #[test]
1105 fn match_python_output() {
1106 use std::process::Command;
1107
1108 let result = Command::new("sh").arg("-c").arg("which python3").output();
1109
1110 if result.map(|r| r.status.success()).unwrap_or(false) {
1111 let py_script = "\
1112import base64
1113data = b'Python PEM test data'
1114b64 = base64.b64encode(data).decode('ascii')
1115pem = f\"-----BEGIN PYTHON DATA-----\\n{b64}\\n-----END PYTHON DATA-----\\n\"
1116print(pem, end='')
1117";
1118 let output = Command::new("python3")
1119 .arg("-c")
1120 .arg(py_script)
1121 .output()
1122 .expect("python3 failed");
1123
1124 assert!(output.status.success());
1125 let pem_bytes = output.stdout;
1126
1127 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem_bytes).collect();
1128 assert_eq!(decoded.len(), 1);
1129 let block = decoded[0].as_ref().unwrap();
1130 assert_eq!(block.r#type, "PYTHON DATA");
1131 assert_eq!(block.contents, b"Python PEM test data");
1132 }
1133 }
1134
1135 #[cfg(feature = "std")]
1136 #[test]
1137 fn match_openssl_ec_key_roundtrip() {
1138 use std::process::Command;
1139
1140 let result = Command::new("sh").arg("-c").arg("which openssl").output();
1141
1142 if result.map(|r| r.status.success()).unwrap_or(false) {
1143 let output = Command::new("openssl")
1144 .args(["ecparam", "-genkey", "-name", "prime256v1", "-outform", "PEM"])
1145 .output()
1146 .expect("openssl ecparam failed");
1147
1148 assert!(output.status.success());
1149 let pem_bytes = output.stdout;
1150
1151 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem_bytes).collect();
1152 assert!(decoded.len() >= 1);
1153 let first_block = decoded[0].as_ref().unwrap();
1154 assert!(!first_block.contents.is_empty());
1155
1156 let reencoded = encode(&[first_block.clone()]);
1157 let re_decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&reencoded).collect();
1158 assert_eq!(re_decoded.len(), 1);
1159 assert_eq!(re_decoded[0].as_ref().unwrap().contents, first_block.contents);
1160 }
1161 }
1162
1163 #[test]
1164 fn encode_with_special_chars_in_label() {
1165 let label = "X.509 CERTIFICATE";
1166 let block = Block {
1167 r#type: label,
1168 headers: Headers::new(),
1169 contents: b"data".to_vec(),
1170 };
1171 let pem = encode(&[block.clone()]);
1172 assert!(pem.starts_with(b"-----BEGIN X.509 CERTIFICATE-----\n"));
1173 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
1174 assert_eq!(decoded.len(), 1);
1175 assert_eq!(decoded[0].as_ref().unwrap().r#type, label);
1176 }
1177
1178 #[test]
1179 fn decode_non_ascii_ignored() {
1180 let mut pem = b"-----BEGIN CERTIFICATE-----\n".to_vec();
1181 pem.push(0x80);
1182 pem.extend_from_slice(b"\n");
1183 pem.extend_from_slice(b"SGVsbG8=\n");
1184 pem.extend_from_slice(b"-----END CERTIFICATE-----\n");
1185
1186 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
1187 assert_eq!(decoded.len(), 1);
1188 assert!(decoded[0].is_ok());
1189 assert_eq!(decoded[0].as_ref().unwrap().contents, b"Hello");
1190 }
1191
1192 #[test]
1195 fn decode_rfc7468_section6_x509_crl() {
1196 let pem = b"-----BEGIN X509 CRL-----\n\
1197 MIIB9DCCAV8CAQEwCwYJKoZIhvcNAQEFMIIBCDEXMBUGA1UEChMOVmVyaVNpZ24s\n\
1198 IEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxRjBEBgNVBAsT\n\
1199 PXd3dy52ZXJpc2lnbi5jb20vcmVwb3NpdG9yeS9SUEEgSW5jb3JwLiBieSBSZWYu\n\
1200 LExJQUIuTFREKGMpOTgxHjAcBgNVBAsTFVBlcnNvbmEgTm90IFZhbGlkYXRlZDEm\n\
1201 MCQGA1UECxMdRGlnaXRhbCBJRCBDbGFzcyAxIC0gTmV0c2NhcGUxGDAWBgNVBAMU\n\
1202 D1NpbW9uIEpvc2Vmc3NvbjEiMCAGCSqGSIb3DQEJARYTc2ltb25Aam9zZWZzc29u\n\
1203 Lm9yZxcNMDYxMjI3MDgwMjM0WhcNMDcwMjA3MDgwMjM1WjAjMCECEC4QNwPfRoWd\n\
1204 elUNpllhhTgXDTA2MTIyNzA4MDIzNFowCwYJKoZIhvcNAQEFA4GBAD0zX+J2hkcc\n\
1205 Nbrq1Dn5IKL8nXLgPGcHv1I/le1MNo9t1ohGQxB5HnFUkRPAY82fR6Epor4aHgVy\n\
1206 b+5y+neKN9Kn2mPF4iiun+a4o26CjJ0pArojCL1p8T0yyi9Xxvyc/ezaZ98HiIyP\n\
1207 c3DGMNR+oUmSjKZ0jIhAYmeLxaPHfQwR\n\
1208 -----END X509 CRL-----\n";
1209
1210 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1211 assert_eq!(decoded.len(), 1);
1212 let block = decoded[0].as_ref().unwrap();
1213 assert_eq!(block.r#type, "X509 CRL");
1214 assert!(block.headers.is_empty());
1215 assert!(!block.contents.is_empty());
1216 }
1217
1218 #[test]
1219 fn decode_rfc7468_section7_certificate_request() {
1220 let pem = b"-----BEGIN CERTIFICATE REQUEST-----\n\
1221 MIIBWDCCAQcCAQAwTjELMAkGA1UEBhMCU0UxJzAlBgNVBAoTHlNpbW9uIEpvc2Vm\n\
1222 c3NvbiBEYXRha29uc3VsdCBBQjEWMBQGA1UEAxMNam9zZWZzc29uLm9yZzBOMBAG\n\
1223 ByqGSM49AgEGBSuBBAAhAzoABLLPSkuXY0l66MbxVJ3Mot5FCFuqQfn6dTs+9/CM\n\
1224 EOlSwVej77tj56kj9R/j9Q+LfysX8FO9I5p3oGIwYAYJKoZIhvcNAQkOMVMwUTAY\n\
1225 BgNVHREEETAPgg1qb3NlZnNzb24ub3JnMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/\n\
1226 BAUDAwegADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgM/ADA8\n\
1227 AhxBvfhxPFfbBbsE1NoFmCUczOFApEuQVUw3ZP69AhwWXk3dgSUsKnuwL5g/ftAY\n\
1228 dEQc8B8jAcnuOrfU\n\
1229 -----END CERTIFICATE REQUEST-----\n";
1230
1231 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1232 assert_eq!(decoded.len(), 1);
1233 let block = decoded[0].as_ref().unwrap();
1234 assert_eq!(block.r#type, "CERTIFICATE REQUEST");
1235 assert!(!block.contents.is_empty());
1236 }
1237
1238 #[test]
1239 fn decode_rfc7468_section8_pkcs7() {
1240 let pem = b"-----BEGIN PKCS7-----\n\
1241 MIHjBgsqhkiG9w0BCRABF6CB0zCB0AIBADFho18CAQCgGwYJKoZIhvcNAQUMMA4E\n\
1242 CLfrI6dr0gUWAgITiDAjBgsqhkiG9w0BCRADCTAUBggqhkiG9w0DBwQIZpECRWtz\n\
1243 u5kEGDCjerXY8odQ7EEEromZJvAurk/j81IrozBSBgkqhkiG9w0BBwEwMwYLKoZI\n\
1244 hvcNAQkQAw8wJDAUBggqhkiG9w0DBwQI0tCBcU09nxEwDAYIKwYBBQUIAQIFAIAQ\n\
1245 OsYGYUFdAH0RNc1p4VbKEAQUM2Xo8PMHBoYdqEcsbTodlCFAZH4=\n\
1246 -----END PKCS7-----\n";
1247
1248 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1249 assert_eq!(decoded.len(), 1);
1250 let block = decoded[0].as_ref().unwrap();
1251 assert_eq!(block.r#type, "PKCS7");
1252 assert!(!block.contents.is_empty());
1253 }
1254
1255 #[test]
1256 fn decode_rfc7468_section9_cms() {
1257 let pem = b"-----BEGIN CMS-----\n\
1258 MIGDBgsqhkiG9w0BCRABCaB0MHICAQAwDQYLKoZIhvcNAQkQAwgwXgYJKoZIhvcN\n\
1259 AQcBoFEET3icc87PK0nNK9ENqSxItVIoSa0o0S/ISczMs1ZIzkgsKk4tsQ0N1nUM\n\
1260 dvb05OXi5XLPLEtViMwvLVLwSE0sKlFIVHAqSk3MBkkBAJv0Fx0=\n\
1261 -----END CMS-----\n";
1262
1263 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1264 assert_eq!(decoded.len(), 1);
1265 let block = decoded[0].as_ref().unwrap();
1266 assert_eq!(block.r#type, "CMS");
1267 assert!(!block.contents.is_empty());
1268 }
1269
1270 #[test]
1271 fn decode_rfc7468_section11_encrypted_private_key() {
1272 let pem = b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n\
1273 MIHNMEAGCSqGSIb3DQEFDTAzMBsGCSqGSIb3DQEFDDAOBAghhICA6T/51QICCAAw\n\
1274 FAYIKoZIhvcNAwcECBCxDgvI59i9BIGIY3CAqlMNBgaSI5QiiWVNJ3IpfLnEiEsW\n\
1275 Z0JIoHyRmKK/+cr9QPLnzxImm0TR9s4JrG3CilzTWvb0jIvbG3hu0zyFPraoMkap\n\
1276 8eRzWsIvC5SVel+CSjoS2mVS87cyjlD+txrmrXOVYDE+eTgMLbrLmsWh3QkCTRtF\n\
1277 QC7k0NNzUHTV9yGDwfqMbw==\n\
1278 -----END ENCRYPTED PRIVATE KEY-----\n";
1279
1280 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1281 assert_eq!(decoded.len(), 1);
1282 let block = decoded[0].as_ref().unwrap();
1283 assert_eq!(block.r#type, "ENCRYPTED PRIVATE KEY");
1284 assert!(!block.contents.is_empty());
1285 }
1286
1287 #[test]
1288 fn decode_rfc7468_section12_attribute_certificate() {
1289 let pem = b"-----BEGIN ATTRIBUTE CERTIFICATE-----\n\
1290 MIICKzCCAZQCAQEwgZeggZQwgYmkgYYwgYMxCzAJBgNVBAYTAlVTMREwDwYDVQQI\n\
1291 DAhOZXcgWW9yazEUMBIGA1UEBwwLU3RvbnkgQnJvb2sxDzANBgNVBAoMBkNTRTU5\n\
1292 MjE6MDgGA1UEAwwxU2NvdHQgU3RhbGxlci9lbWFpbEFkZHJlc3M9c3N0YWxsZXJA\n\
1293 aWMuc3VueXNiLmVkdQIGARWrgUUSoIGMMIGJpIGGMIGDMQswCQYDVQQGEwJVUzER\n\
1294 MA8GA1UECAwITmV3IFlvcmsxFDASBgNVBAcMC1N0b255IEJyb29rMQ8wDQYDVQQK\n\
1295 DAZDU0U1OTIxOjA4BgNVBAMMMVNjb3R0IFN0YWxsZXIvZW1haWxBZGRyZXNzPXNz\n\
1296 dGFsbGVyQGljLnN1bnlzYi5lZHUwDQYJKoZIhvcNAQEFBQACBgEVq4FFSjAiGA8z\n\
1297 OTA3MDIwMTA1MDAwMFoYDzM5MTEwMTMxMDUwMDAwWjArMCkGA1UYSDEiMCCGHmh0\n\
1298 dHA6Ly9pZGVyYXNobi5vcmcvaW5kZXguaHRtbDANBgkqhkiG9w0BAQUFAAOBgQAV\n\
1299 M9axFPXXozEFcer06bj9MCBBCQLtAM7ZXcZjcxyva7xCBDmtZXPYUluHf5OcWPJz\n\
1300 5XPus/xS9wBgtlM3fldIKNyNO8RsMp6Ocx+PGlICc7zpZiGmCYLl64lAEGPO/bsw\n\
1301 Smluak1aZIttePeTAHeJJs8izNJ5aR3Wcd3A5gLztQ==\n\
1302 -----END ATTRIBUTE CERTIFICATE-----\n";
1303
1304 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1305 assert_eq!(decoded.len(), 1);
1306 let block = decoded[0].as_ref().unwrap();
1307 assert_eq!(block.r#type, "ATTRIBUTE CERTIFICATE");
1308 assert!(!block.contents.is_empty());
1309 }
1310
1311 #[test]
1312 fn decode_rfc7468_section13_public_key() {
1313 let pem = b"-----BEGIN PUBLIC KEY-----\n\
1314 MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEn1LlwLN/KBYQRVH6HfIMTzfEqJOVztLe\n\
1315 kLchp2hi78cCaMY81FBlYs8J9l7krc+M4aBeCGYFjba+hiXttJWPL7ydlE+5UG4U\n\
1316 Nkn3Eos8EiZByi9DVsyfy9eejh+8AXgp\n\
1317 -----END PUBLIC KEY-----\n";
1318
1319 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1320 assert_eq!(decoded.len(), 1);
1321 let block = decoded[0].as_ref().unwrap();
1322 assert_eq!(block.r#type, "PUBLIC KEY");
1323 assert!(!block.contents.is_empty());
1324 }
1325
1326 #[test]
1329 fn decode_rfc7468_appendix_x509_certificate() {
1330 let pem = b"-----BEGIN X509 CERTIFICATE-----\n\
1331 MIIBHDCBxaADAgECAgIcxzAJBgcqhkjOPQQBMBAxDjAMBgNVBAMUBVBLSVghMB4X\n\
1332 DTE0MDkxNDA2MTU1MFoXDTI0MDkxNDA2MTU1MFowEDEOMAwGA1UEAxQFUEtJWCEw\n\
1333 WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATwoQSr863QrR0PoRIYQ96H7WykDePH\n\
1334 Wa0eVAE24bth43wCNc+U5aZ761dhGhSSJkVWRgVH5+prLIr+nzfIq+X4oxAwDjAM\n\
1335 BgNVHRMBAf8EAjAAMAkGByqGSM49BAEDRwAwRAIfMdKS5F63lMnWVhi7uaKJzKCs\n\
1336 NnY/OKgBex6MIEAv2AIhAI2GdvfL+mGvhyPZE+JxRxWChmggb5/9eHdUcmW/jkOH\n\
1337 -----END X509 CERTIFICATE-----\n";
1338
1339 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1340 assert_eq!(decoded.len(), 1);
1341 let block = decoded[0].as_ref().unwrap();
1342 assert_eq!(block.r#type, "X509 CERTIFICATE");
1343 assert!(!block.contents.is_empty());
1344 }
1345
1346 #[test]
1347 fn decode_rfc7468_appendix_dot_x509_certificate() {
1348 let pem = b"-----BEGIN X.509 CERTIFICATE-----\n\
1349 MIIBHDCBxaADAgECAgIcxzAJBgcqhkjOPQQBMBAxDjAMBgNVBAMUBVBLSVghMB4X\n\
1350 DTE0MDkxNDA2MTU1MFoXDTI0MDkxNDA2MTU1MFowEDEOMAwGA1UEAxQFUEtJWCEw\n\
1351 WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATwoQSr863QrR0PoRIYQ96H7WykDePH\n\
1352 Wa0eVAE24bth43wCNc+U5aZ761dhGhSSJkVWRgVH5+prLIr+nzfIq+X4oxAwDjAM\n\
1353 BgNVHRMBAf8EAjAAMAkGByqGSM49BAEDRwAwRAIfMdKS5F63lMnWVhi7uaKJzKCs\n\
1354 NnY/OKgBex6MIEAv2AIhAI2GdvfL+mGvhyPZE+JxRxWChmggb5/9eHdUcmW/jkOH\n\
1355 -----END X.509 CERTIFICATE-----\n";
1356
1357 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1358 assert_eq!(decoded.len(), 1);
1359 let block = decoded[0].as_ref().unwrap();
1360 assert_eq!(block.r#type, "X.509 CERTIFICATE");
1361 assert!(!block.contents.is_empty());
1362 }
1363
1364 #[test]
1365 fn decode_rfc7468_appendix_new_certificate_request() {
1366 let pem = b"-----BEGIN NEW CERTIFICATE REQUEST-----\n\
1367 MIIBWDCCAQcCAQAwTjELMAkGA1UEBhMCU0UxJzAlBgNVBAoTHlNpbW9uIEpvc2Vm\n\
1368 c3NvbiBEYXRha29uc3VsdCBBQjEWMBQGA1UEAxMNam9zZWZzc29uLm9yZzBOMBAG\n\
1369 ByqGSM49AgEGBSuBBAAhAzoABLLPSkuXY0l66MbxVJ3Mot5FCFuqQfn6dTs+9/CM\n\
1370 EOlSwVej77tj56kj9R/j9Q+LfysX8FO9I5p3oGIwYAYJKoZIhvcNAQkOMVMwUTAY\n\
1371 BgNVHREEETAPgg1qb3NlZnNzb24ub3JnMAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/\n\
1372 BAUDAwegADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgM/ADA8\n\
1373 AhxBvfhxPFfbBbsE1NoFmCUczOFApEuQVUw3ZP69AhwWXk3dgSUsKnuwL5g/ftAY\n\
1374 dEQc8B8jAcnuOrfU\n\
1375 -----END NEW CERTIFICATE REQUEST-----\n";
1376
1377 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1378 assert_eq!(decoded.len(), 1);
1379 let block = decoded[0].as_ref().unwrap();
1380 assert_eq!(block.r#type, "NEW CERTIFICATE REQUEST");
1381 assert!(!block.contents.is_empty());
1382 }
1383
1384 #[test]
1385 fn decode_rfc7468_appendix_certificate_chain() {
1386 let pem = b"-----BEGIN CERTIFICATE CHAIN-----\n\
1387 MIHjBgsqhkiG9w0BCRABF6CB0zCB0AIBADFho18CAQCgGwYJKoZIhvcNAQUMMA4E\n\
1388 CLfrI6dr0gUWAgITiDAjBgsqhkiG9w0BCRADCTAUBggqhkiG9w0DBwQIZpECRWtz\n\
1389 u5kEGDCjerXY8odQ7EEEromZJvAurk/j81IrozBSBgkqhkiG9w0BBwEwMwYLKoZI\n\
1390 hvcNAQkQAw8wJDAUBggqhkiG9w0DBwQI0tCBcU09nxEwDAYIKwYBBQUIAQIFAIAQ\n\
1391 OsYGYUFdAH0RNc1p4VbKEAQUM2Xo8PMHBoYdqEcsbTodlCFAZH4=\n\
1392 -----END CERTIFICATE CHAIN-----\n";
1393
1394 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1395 assert_eq!(decoded.len(), 1);
1396 let block = decoded[0].as_ref().unwrap();
1397 assert_eq!(block.r#type, "CERTIFICATE CHAIN");
1398 assert!(!block.contents.is_empty());
1399 }
1400
1401 #[test]
1404 fn reject_too_few_trailing_dashes_begin() {
1405 let pem = b"-----BEGIN FOO----\ndGVzdA==\n-----END FOO-----\n";
1406 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1407 assert!(
1408 decoded.is_empty() || decoded[0].is_err(),
1409 "expected error for BEGIN with 4 trailing dashes"
1410 );
1411 }
1412
1413 #[test]
1414 fn reject_too_many_trailing_dashes_begin() {
1415 let pem = b"-----BEGIN FOO-------\ndGVzdA==\n-----END FOO-------\n";
1416 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1417 assert!(
1418 decoded.is_empty() || decoded[0].is_err(),
1419 "expected error for BEGIN with 7 trailing dashes"
1420 );
1421 }
1422
1423 #[test]
1424 fn reject_too_few_trailing_dashes_end() {
1425 let pem = b"-----BEGIN FOO-----\ndGVzdA==\n-----END FOO----\n";
1426 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1427 assert!(
1428 decoded.is_empty() || decoded[0].is_err(),
1429 "expected error for END with 4 trailing dashes"
1430 );
1431 }
1432
1433 #[test]
1434 fn reject_too_many_trailing_dashes_end() {
1435 let pem = b"-----BEGIN FOO-----\ndGVzdA==\n-----END FOO------\n";
1436 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1437 assert!(
1438 decoded.is_empty() || decoded[0].is_err(),
1439 "expected error for END with 6 trailing dashes"
1440 );
1441 }
1442
1443 #[test]
1444 fn reject_missing_ending_space() {
1445 let pem = b"-----BEGIN FOO-----\ndGVzdA==\n-----ENDBAR-----\n";
1446 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1447 assert!(
1448 decoded.is_empty() || decoded[0].is_err(),
1449 "expected error for missing space between END and label"
1450 );
1451 }
1452
1453 #[test]
1454 fn reject_trailing_non_whitespace_on_end() {
1455 let pem = b"-----BEGIN FOO-----\ndGVzdA==\n-----END FOO----- .\n";
1456 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1457 assert_eq!(decoded.len(), 1);
1458 assert!(decoded[0].is_err(), "expected error for trailing '. ' on END line");
1459 }
1460
1461 #[test]
1462 fn reject_repeating_begin_no_end() {
1463 let input = b"-----BEGIN \n".repeat(100);
1464 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&input).collect();
1465 assert_eq!(decoded.len(), 1);
1466 assert!(decoded[0].is_err(), "expected error for 100 repeated BEGIN lines with no END");
1467 }
1468
1469 #[test]
1470 fn reject_only_end_marker() {
1471 let pem = b"-----END PUBLIC KEY-----\n";
1472 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1473 assert!(
1474 decoded.is_empty() || decoded[0].is_err(),
1475 "expected no valid block from input containing only END marker"
1476 );
1477 }
1478
1479 #[test]
1482 fn decode_empty_lines_before_end() {
1483 let pem = b"-----BEGIN DATA-----\n\
1484 ZGF0YQ==\n\
1485 \n\
1486 \n\
1487 -----END DATA-----\n";
1488 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1489 assert_eq!(decoded.len(), 1);
1490 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1491 }
1492
1493 #[test]
1494 fn decode_blank_lines_between_begin_and_end() {
1495 let pem = b"-----BEGIN EMPTY-----\n\
1496 \n\
1497 \n\
1498 \n\
1499 -----END EMPTY-----\n";
1500 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1501 assert_eq!(decoded.len(), 1);
1502 assert!(decoded[0].as_ref().unwrap().contents.is_empty());
1503 }
1504
1505 #[test]
1506 fn decode_header_key_only_no_value() {
1507 let pem = b"-----BEGIN DATA-----\n\
1508 Key-Only:\n\
1509 \n\
1510 ZGF0YQ==\n\
1511 -----END DATA-----\n";
1512 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1513 assert_eq!(decoded.len(), 1);
1514 let block = decoded[0].as_ref().unwrap();
1515 assert_eq!(block.contents, b"data");
1516 let header = block.headers.iter().find(|&(k, _)| k == "Key-Only");
1517 assert!(header.is_some());
1518 assert_eq!(header.unwrap().1, "");
1519 }
1520
1521 #[test]
1522 fn decode_multiple_blank_lines_before_body() {
1523 let pem = b"-----BEGIN DATA-----\n\
1524 \n\
1525 \n\
1526 \n\
1527 \n\
1528 ZGF0YQ==\n\
1529 -----END DATA-----\n";
1530 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1531 assert_eq!(decoded.len(), 1);
1532 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1533 }
1534
1535 #[test]
1536 fn decode_header_value_with_spaces_only() {
1537 let pem = b"-----BEGIN DATA-----\n\
1538 Key: \n\
1539 \n\
1540 ZGF0YQ==\n\
1541 -----END DATA-----\n";
1542 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1543 assert_eq!(decoded.len(), 1);
1544 let block = decoded[0].as_ref().unwrap();
1545 assert_eq!(block.contents, b"data");
1546 }
1547
1548 #[test]
1549 fn decode_header_continuation_empty_line() {
1550 let pem = b"-----BEGIN DATA-----\n\
1551 Key: start\n\
1552 \t\n\
1553 \n\
1554 ZGF0YQ==\n\
1555 -----END DATA-----\n";
1556 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1557 assert_eq!(decoded.len(), 1);
1558 let block = decoded[0].as_ref().unwrap();
1559 assert_eq!(block.contents, b"data");
1560 }
1561
1562 #[test]
1563 fn decode_header_with_no_blank_line_before_body() {
1564 let pem = b"-----BEGIN FOO-----\n\
1565 Header: 1\n\
1566 -----END FOO-----\n";
1567 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1568 assert_eq!(decoded.len(), 1);
1569 let block = decoded[0].as_ref().unwrap();
1570 assert_eq!(block.r#type, "FOO");
1571 assert_eq!(block.headers.len(), 1);
1572 assert_eq!(block.headers.iter().next().unwrap().0, "Header");
1573 assert_eq!(block.headers.iter().next().unwrap().1, "1");
1574 assert!(block.contents.is_empty());
1575 }
1576
1577 #[test]
1580 fn roundtrip_0_bytes() {
1581 let blocks = [Block {
1582 r#type: "DATA".into(),
1583 headers: Headers::new(),
1584 contents: Vec::new(),
1585 }];
1586 roundtrip(&blocks);
1587 }
1588
1589 #[test]
1590 fn roundtrip_1_byte() {
1591 let blocks = [Block {
1592 r#type: "DATA".into(),
1593 headers: Headers::new(),
1594 contents: vec![0x00],
1595 }];
1596 roundtrip(&blocks);
1597 }
1598
1599 #[test]
1600 fn roundtrip_2_bytes() {
1601 let blocks = [Block {
1602 r#type: "DATA".into(),
1603 headers: Headers::new(),
1604 contents: vec![0x00, 0x01],
1605 }];
1606 roundtrip(&blocks);
1607 }
1608
1609 #[test]
1610 fn roundtrip_3_bytes() {
1611 let blocks = [Block {
1612 r#type: "DATA".into(),
1613 headers: Headers::new(),
1614 contents: vec![0x00, 0x01, 0x02],
1615 }];
1616 roundtrip(&blocks);
1617 }
1618
1619 #[test]
1620 fn roundtrip_64_bytes() {
1621 let contents: Vec<u8> = (0u8..64).collect();
1622 let blocks = [Block {
1623 r#type: "DATA".into(),
1624 headers: Headers::new(),
1625 contents,
1626 }];
1627 roundtrip(&blocks);
1628 }
1629
1630 #[test]
1631 fn roundtrip_65_bytes() {
1632 let contents: Vec<u8> = (0u8..65).collect();
1633 let blocks = [Block {
1634 r#type: "DATA".into(),
1635 headers: Headers::new(),
1636 contents,
1637 }];
1638 roundtrip(&blocks);
1639 }
1640
1641 #[test]
1642 fn roundtrip_128_bytes() {
1643 let contents: Vec<u8> = (0u8..128).collect();
1644 let blocks = [Block {
1645 r#type: "DATA".into(),
1646 headers: Headers::new(),
1647 contents,
1648 }];
1649 roundtrip(&blocks);
1650 }
1651
1652 #[test]
1653 fn roundtrip_255_bytes() {
1654 let contents: Vec<u8> = (0u8..255).collect();
1655 let blocks = [Block {
1656 r#type: "DATA".into(),
1657 headers: Headers::new(),
1658 contents,
1659 }];
1660 roundtrip(&blocks);
1661 }
1662
1663 #[test]
1664 fn roundtrip_256_bytes() {
1665 let contents: Vec<u8> = (0u8..=255).collect();
1666 let blocks = [Block {
1667 r#type: "DATA".into(),
1668 headers: Headers::new(),
1669 contents,
1670 }];
1671 roundtrip(&blocks);
1672 }
1673
1674 #[test]
1677 fn encode_line_wrapping_exact_multiple_of_64() {
1678 let contents = vec![b'x'; 48];
1679 let blocks = [Block {
1680 r#type: "TEST".into(),
1681 headers: Headers::new(),
1682 contents,
1683 }];
1684 let pem = encode(&blocks);
1685 let pem_str = core::str::from_utf8(&pem).unwrap();
1686 let body = pem_str
1687 .strip_prefix("-----BEGIN TEST-----\n")
1688 .unwrap()
1689 .strip_suffix("\n-----END TEST-----\n")
1690 .unwrap();
1691 assert_eq!(body.lines().count(), 1);
1692 assert!(body.len() <= 64, "single line should be <= 64 chars");
1693 }
1694
1695 #[test]
1696 fn encode_line_wrapping_exactly_64_per_line() {
1697 let contents = vec![0u8; 64];
1698 let blocks = [Block {
1699 r#type: "TEST".into(),
1700 headers: Headers::new(),
1701 contents,
1702 }];
1703 let pem = encode(&blocks);
1704 let pem_str = core::str::from_utf8(&pem).unwrap();
1705 let body = pem_str
1706 .strip_prefix("-----BEGIN TEST-----\n")
1707 .unwrap()
1708 .strip_suffix("\n-----END TEST-----\n")
1709 .unwrap();
1710 assert!(!body.is_empty());
1711 }
1712
1713 #[test]
1716 fn roundtrip_label_with_hyphen() {
1717 let blocks = [Block {
1718 r#type: "TEST-DATA".into(),
1719 headers: Headers::new(),
1720 contents: b"hyphen test".to_vec(),
1721 }];
1722 roundtrip(&blocks);
1723 }
1724
1725 #[test]
1726 fn roundtrip_label_with_numbers() {
1727 let blocks = [Block {
1728 r#type: "AES-256-CBC".into(),
1729 headers: Headers::new(),
1730 contents: b"number test".to_vec(),
1731 }];
1732 roundtrip(&blocks);
1733 }
1734
1735 #[test]
1736 fn roundtrip_label_with_underscores() {
1737 let blocks = [Block {
1738 r#type: "MY_CUSTOM_LABEL".into(),
1739 headers: Headers::new(),
1740 contents: b"underscore test".to_vec(),
1741 }];
1742 roundtrip(&blocks);
1743 }
1744
1745 #[test]
1746 fn roundtrip_label_with_at_sign() {
1747 let blocks = [Block {
1748 r#type: "KEY@DOMAIN.COM".into(),
1749 headers: Headers::new(),
1750 contents: b"at sign test".to_vec(),
1751 }];
1752 roundtrip(&blocks);
1753 }
1754
1755 #[test]
1756 fn roundtrip_single_char_label() {
1757 let blocks = [Block {
1758 r#type: "X".into(),
1759 headers: Headers::new(),
1760 contents: b"single char".to_vec(),
1761 }];
1762 roundtrip(&blocks);
1763 }
1764
1765 #[test]
1766 fn roundtrip_long_label() {
1767 let label = "A".repeat(100);
1768 let blocks = [Block {
1769 r#type: &label,
1770 headers: Headers::new(),
1771 contents: b"long label test".to_vec(),
1772 }];
1773 roundtrip(&blocks);
1774 }
1775
1776 #[test]
1779 fn decode_leading_whitespace_before_begin() {
1780 let pem = b"\n\n\n-----BEGIN DATA-----\n\
1781 ZGF0YQ==\n\
1782 -----END DATA-----\n";
1783 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1784 assert_eq!(decoded.len(), 1);
1785 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1786 }
1787
1788 #[test]
1789 fn decode_tabs_in_body_only() {
1790 let pem = b"-----BEGIN DATA-----\n\
1791 ZGF0\tYQ==\n\
1792 -----END DATA-----\n";
1793 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1794 assert_eq!(decoded.len(), 1);
1795 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1796 }
1797
1798 #[test]
1799 fn decode_line_breaks_within_base64_line() {
1800 let pem = b"-----BEGIN DATA-----\n\
1801 ZGF0\n\
1802 YQ==\n\
1803 -----END DATA-----\n";
1804 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1805 assert_eq!(decoded.len(), 1);
1806 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1807 }
1808
1809 #[test]
1812 fn encode_multiple_header_pairs() {
1813 let block = Block {
1814 r#type: "DATA".into(),
1815 headers: Headers::from_pairs(&[("Key1", "val1"), ("Key2", "val2"), ("Key3", "val3")]),
1816 contents: b"multi header".to_vec(),
1817 };
1818 let pem = encode(&[block.clone()]);
1819 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
1820 assert_eq!(decoded.len(), 1);
1821 assert_eq!(decoded[0].as_ref().unwrap().headers, block.headers);
1822 assert_eq!(decoded[0].as_ref().unwrap().contents, block.contents);
1823 }
1824
1825 #[test]
1826 fn encode_header_with_empty_value() {
1827 let block = Block {
1828 r#type: "DATA".into(),
1829 headers: Headers::from_pairs(&[("Empty-Key", "")]),
1830 contents: b"empty value".to_vec(),
1831 };
1832 let pem = encode(&[block.clone()]);
1833 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(&pem).collect();
1834 assert_eq!(decoded.len(), 1);
1835 assert_eq!(decoded[0].as_ref().unwrap().headers.iter().next().unwrap().1, "");
1836 }
1837
1838 #[test]
1841 fn roundtrip_five_blocks() {
1842 let type_strs: Vec<String> = (0..5).map(|i| alloc::format!("BLOCK{}", i)).collect();
1843 let mut blocks = Vec::new();
1844 for i in 0..5 {
1845 blocks.push(Block {
1846 r#type: &type_strs[i],
1847 headers: Headers::new(),
1848 contents: alloc::format!("content {}", i).into_bytes(),
1849 });
1850 }
1851 roundtrip(&blocks);
1852 }
1853
1854 #[test]
1855 fn decode_block_with_explanatory_text() {
1856 let pem = b"Subject: CN=Atlantis\n\
1857 Issuer: CN=Atlantis\n\
1858 Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC\n\
1859 -----BEGIN CERTIFICATE-----\n\
1860 SGVsbG8=\n\
1861 -----END CERTIFICATE-----\n";
1862 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1863 assert_eq!(decoded.len(), 1);
1864 assert_eq!(decoded[0].as_ref().unwrap().contents, b"Hello");
1865 }
1866
1867 #[test]
1870 fn fuzz_random_roundtrip() {
1871 let mut state: u64 = 42;
1872 for i in 0..20 {
1873 state = state
1874 .wrapping_mul(6364136223846793005)
1875 .wrapping_add(1442695040888963407);
1876 let len = (state % 512) as usize;
1877 let contents: Vec<u8> = (0..len)
1878 .map(|j| {
1879 ((state
1880 .wrapping_add(j as u64)
1881 .wrapping_mul(1103515245)
1882 .wrapping_add(12345))
1883 >> 16) as u8
1884 })
1885 .collect();
1886
1887 let type_str = alloc::format!("TEST{}", i);
1888 let mut headers = Headers::new();
1889 for hi in 0..(state % 3) as usize {
1890 headers.push(&alloc::format!("H{}{}", i, hi), &alloc::format!("V{}{}", i, hi));
1891 }
1892 let blocks = [Block {
1893 r#type: &type_str,
1894 headers,
1895 contents,
1896 }];
1897 roundtrip(&blocks);
1898 }
1899 }
1900
1901 #[test]
1904 fn decode_rfc1421_full_encrypted_message() {
1905 let pem: &[u8] = b"-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n\
1906 Proc-Type: 4,ENCRYPTED\n\
1907 Content-Domain: RFC822\n\
1908 DEK-Info: DES-CBC,F8143EDE5960C597\n\
1909 Originator-ID-Symmetric: linn@zendia.enet.dec.com,,\n\
1910 Recipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\n\
1911 Key-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\n\x20B70665BB9BF7CBCDA60195DB94F727D3\n\
1912 \n\
1913 SGVsbG8=\n\
1914 -----END PRIVACY-ENHANCED MESSAGE-----\n";
1915
1916 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1917 assert_eq!(decoded.len(), 1);
1918 let block = decoded[0].as_ref().unwrap();
1919 assert_eq!(block.r#type, "PRIVACY-ENHANCED MESSAGE");
1920 assert_eq!(block.contents, b"Hello");
1921 assert!(block.headers.len() >= 4);
1922 }
1923
1924 #[test]
1927 fn decode_body_only_padding() {
1928 let pem = b"-----BEGIN DATA-----\n=\n-----END DATA-----\n";
1929 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1930 assert_eq!(decoded.len(), 1);
1931 assert!(decoded[0].is_err(), "a single = is invalid base64");
1932 }
1933
1934 #[test]
1935 fn decode_body_only_padding_pair() {
1936 let pem = b"-----BEGIN DATA-----\n==\n-----END DATA-----\n";
1937 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1938 assert_eq!(decoded.len(), 1);
1939 assert!(decoded[0].is_err(), "just == is invalid base64");
1940 }
1941
1942 #[test]
1945 fn decode_end_no_newline_matching() {
1946 let pem = b"-----BEGIN FOO-----\nZGF0YQ==\n-----END FOO-----";
1947 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1948 assert_eq!(decoded.len(), 1);
1949 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1950 }
1951
1952 #[test]
1955 fn decode_rfc7468_section5_explanatory_text() {
1956 let pem = b"Subject: CN=Atlantis\n\
1957 Issuer: CN=Atlantis\n\
1958 Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC\n\
1959 -----BEGIN CERTIFICATE-----\n\
1960 MIIBmTCCAUegAwIBAgIBKjAJBgUrDgMCHQUAMBMxETAPBgNVBAMTCEF0bGFudGlz\n\
1961 MB4XDTEyMDcwOTAzMTAzOFoXDTEzMDcwOTAzMTAzN1owEzERMA8GA1UEAxMIQXRs\n\
1962 YW50aXMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAu+BXo+miabDIHHx+yquqzqNh\n\
1963 Ryn/XtkJIIHVcYtHvIX+S1x5ErgMoHehycpoxbErZmVR4GCq1S2diNmRFZCRtQID\n\
1964 AQABo4GJMIGGMAwGA1UdEwEB/wQCMAAwIAYDVR0EAQH/BBYwFDAOMAwGCisGAQQB\n\
1965 gjcCARUDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzA1BgNVHQEE\n\
1966 LjAsgBA0jOnSSuIHYmnVryHAdywMoRUwEzERMA8GA1UEAxMIQXRsYW50aXOCASow\n\
1967 CQYFKw4DAh0FAANBAKi6HRBaNEL5R0n56nvfclQNaXiDT174uf+lojzA4lhVInc0\n\
1968 ILwpnZ1izL4MlI9eCSHhVQBHEp2uQdXJB+d5Byg=\n\
1969 -----END CERTIFICATE-----\n";
1970
1971 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1972 assert_eq!(decoded.len(), 1);
1973 let block = decoded[0].as_ref().unwrap();
1974 assert_eq!(block.r#type, "CERTIFICATE");
1975 assert!(!block.contents.is_empty());
1976 }
1977
1978 #[test]
1981 fn decode_base64_with_trailing_whitespace_in_line() {
1982 let pem = b"-----BEGIN DATA-----\n\
1983 ZGF0YQ== \n\
1984 -----END DATA-----\n";
1985 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1986 assert_eq!(decoded.len(), 1);
1987 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
1988 }
1989
1990 #[test]
1993 fn decode_base64_with_leading_whitespace() {
1994 let pem = b"-----BEGIN DATA-----\n\
1995 ZGF0YQ==\n\
1996 -----END DATA-----\n";
1997 let decoded: Vec<Result<Block<'_>, PemError<'_>>> = decode(pem).collect();
1998 assert_eq!(decoded.len(), 1);
1999 assert_eq!(decoded[0].as_ref().unwrap().contents, b"data");
2000 }
2001}