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