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