1#![forbid(unsafe_code)]
57
58pub mod error;
59
60use std::{
61 fmt::{Display, Formatter},
62 str::FromStr,
63};
64
65use base64::{Engine, engine::general_purpose::STANDARD};
66use error::Error;
67use regex::Regex;
68use tsumiki::decoder::{DecodableFrom, Decoder};
69
70const PRIVATE_KEY_LABEL: &str = "PRIVATE KEY";
71const ENCRYPTED_PRIVATE_KEY_LABEL: &str = "ENCRYPTED PRIVATE KEY";
72const RSA_PRIVATE_KEY_LABEL: &str = "RSA PRIVATE KEY";
73const EC_PRIVATE_KEY_LABEL: &str = "EC PRIVATE KEY";
74const PUBLIC_KEY_LABEL: &str = "PUBLIC KEY";
75const RSA_PUBLIC_KEY_LABEL: &str = "RSA PUBLIC KEY";
76const CERTIFICATE_LABEL: &str = "CERTIFICATE";
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Label {
88 PrivateKey,
90 EncryptedPrivateKey,
92 RSAPrivateKey,
94 ECPrivateKey,
96 PublicKey,
98 RSAPublicKey,
100 Certificate,
102 Unknown,
104}
105
106impl Display for Label {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 match self {
109 Label::PrivateKey => write!(f, "{}", PRIVATE_KEY_LABEL),
110 Label::EncryptedPrivateKey => write!(f, "{}", ENCRYPTED_PRIVATE_KEY_LABEL),
111 Label::RSAPrivateKey => write!(f, "{}", RSA_PRIVATE_KEY_LABEL),
112 Label::ECPrivateKey => write!(f, "{}", EC_PRIVATE_KEY_LABEL),
113 Label::PublicKey => write!(f, "{}", PUBLIC_KEY_LABEL),
114 Label::RSAPublicKey => write!(f, "{}", RSA_PUBLIC_KEY_LABEL),
115 Label::Certificate => write!(f, "{}", CERTIFICATE_LABEL),
116 Label::Unknown => write!(f, "UNKNOWN"),
117 }
118 }
119}
120
121impl FromStr for Label {
122 type Err = Error;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 match s {
126 PRIVATE_KEY_LABEL => Ok(Label::PrivateKey),
127 ENCRYPTED_PRIVATE_KEY_LABEL => Ok(Label::EncryptedPrivateKey),
128 RSA_PRIVATE_KEY_LABEL => Ok(Label::RSAPrivateKey),
129 EC_PRIVATE_KEY_LABEL => Ok(Label::ECPrivateKey),
130 PUBLIC_KEY_LABEL => Ok(Label::PublicKey),
131 RSA_PUBLIC_KEY_LABEL => Ok(Label::RSAPublicKey),
132 CERTIFICATE_LABEL => Ok(Label::Certificate),
133 _ => Err(Error::InvalidLabel),
134 }
135 }
136}
137
138impl Label {
139 fn get_label(line: &str) -> Result<Label, Error> {
140 let re = Regex::new(r"-----(?:BEGIN|END) ([A-Z ]+)-----\s*")
141 .map_err(|_| Error::InvalidEncapsulationBoundary)?;
142 if let Some(captured) = re.captures(line) {
143 if captured.len() != 2 {
144 return Err(Error::InvalidEncapsulationBoundary);
145 }
146 return captured
147 .get(1)
148 .ok_or(Error::InvalidEncapsulationBoundary)
149 .map(|c| Label::from_str(c.as_str()))?;
150 }
151
152 Err(Error::InvalidEncapsulationBoundary)
153 }
154}
155
156#[derive(Debug, Clone)]
174pub struct Pem {
175 label: Label,
176 base64_data: String, }
178
179impl Pem {
180 pub fn new(label: Label, base64_data: String) -> Self {
190 Pem { label, base64_data }
191 }
192
193 pub fn from_bytes(label: Label, data: &[u8]) -> Self {
203 let base64_data = STANDARD.encode(data);
204 Pem { label, base64_data }
205 }
206
207 pub fn label(&self) -> Label {
209 self.label
210 }
211
212 pub fn data(&self) -> &str {
214 &self.base64_data
215 }
216}
217
218impl Display for Pem {
219 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
220 writeln!(f, "-----BEGIN {}-----", self.label)?;
221 for chunk in self.base64_data.as_bytes().chunks(64) {
223 let line = std::str::from_utf8(chunk).map_err(|_| std::fmt::Error)?;
224 writeln!(f, "{}", line)?;
225 }
226 write!(f, "-----END {}-----", self.label)
227 }
228}
229
230pub trait ToPem {
232 type Error;
234
235 fn pem_label(&self) -> Label;
237
238 fn to_pem(&self) -> Result<Pem, Self::Error>;
240}
241
242pub trait FromPem: Sized {
244 type Error;
246
247 fn expected_label() -> Label;
249
250 fn from_pem(pem: &Pem) -> Result<Self, Self::Error>;
252}
253
254impl DecodableFrom<Pem> for Vec<u8> {}
255
256impl Decoder<Pem, Vec<u8>> for Pem {
257 type Error = Error;
258
259 fn decode(&self) -> Result<Vec<u8>, Self::Error> {
260 let decoded = STANDARD.decode(self.data()).map_err(Error::Base64Decode)?;
262 Ok(decoded)
263 }
264}
265
266impl DecodableFrom<String> for Pem {}
267
268impl Decoder<String, Pem> for String {
269 type Error = Error;
270
271 fn decode(&self) -> Result<Pem, Self::Error> {
272 Pem::from_str(self)
273 }
274}
275
276impl DecodableFrom<&str> for Pem {}
277
278impl Decoder<&str, Pem> for &str {
279 type Error = Error;
280
281 fn decode(&self) -> Result<Pem, Self::Error> {
282 Pem::from_str(self)
283 }
284}
285
286pub fn parse_many(s: &str) -> Result<Vec<Pem>, Error> {
301 let normalized = s.replace("----------", "-----\n-----");
303
304 let mut pems = Vec::new();
305 let mut current_block: Option<(Label, Vec<&str>)> = None;
306
307 for line in normalized.lines() {
308 if let Ok(label) = Label::get_label(line) {
309 if line.contains("BEGIN") {
310 current_block = Some((label, vec![line]));
312 } else if line.contains("END") {
313 if let Some((begin_label, mut lines)) = current_block.take() {
315 if begin_label == label {
316 lines.push(line);
317 let block = lines.join("\n") + "\n";
318 pems.push(Pem::from_str(&block)?);
319 } else {
320 return Err(Error::LabelMissMatch);
321 }
322 } else {
323 return Err(Error::MissingPreEncapsulationBoundary);
324 }
325 }
326 } else if let Some((_, ref mut lines)) = current_block {
327 lines.push(line);
329 }
330 }
332
333 if current_block.is_some() {
334 return Err(Error::MissingPostEncapsulationBoundary);
335 }
336
337 if pems.is_empty() {
338 return Err(Error::MissingPreEncapsulationBoundary);
339 }
340
341 Ok(pems)
342}
343
344impl FromStr for Pem {
345 type Err = Error;
346
347 fn from_str(s: &str) -> Result<Self, Self::Err> {
348 let mut state = PemParsingState::default();
349 let mut label = Label::Unknown;
350 let mut base64_lines = vec![];
351 let mut base64_finl_lines = vec![];
352 let mut lines = s.lines();
353 loop {
354 match state {
355 PemParsingState::Init => match lines.next() {
356 Some(line) => {
357 if line.is_empty() {
358 return Err(Error::MissingPreEncapsulationBoundary);
359 }
360 if let Ok(l) = Label::get_label(line) {
361 label = l;
362 state = PemParsingState::PreEncapsulationBoundary;
363 } else {
364 }
368 }
369 None => return Err(Error::MissingPreEncapsulationBoundary),
370 },
371 PemParsingState::PreEncapsulationBoundary => match lines.next() {
372 Some(line) => {
373 if line.is_empty() || Label::get_label(line).is_ok() {
374 return Err(Error::MissingData);
375 }
376 if is_base64_finl(line) {
377 base64_finl_lines.push(line);
378 state = PemParsingState::Base64Finl;
379 } else {
380 base64_lines.push(line);
381 state = PemParsingState::Base64Lines;
382 }
383 }
384 None => return Err(Error::MissingData),
385 },
386 PemParsingState::Base64Lines => match lines.next() {
387 Some(line) => {
388 if line.is_empty() {
389 return Err(Error::InvalidBase64Line);
390 }
391 if let Ok(l) = Label::get_label(line) {
392 if l.ne(&label) {
394 return Err(Error::LabelMissMatch);
395 }
396 state = PemParsingState::PostEncapsulationBoundary;
397 } else if is_base64_finl(line) {
398 base64_finl_lines.push(line);
399 state = PemParsingState::Base64Finl;
400 } else {
401 base64_lines.push(line);
402 }
403 }
404 None => return Err(Error::MissingPostEncapsulationBoundary),
405 },
406 PemParsingState::Base64Finl => match lines.next() {
407 Some(line) => {
408 if line.is_empty() {
409 return Err(Error::InvalidBase64Finl);
410 }
411 if let Ok(l) = Label::get_label(line) {
412 if l.ne(&label) {
414 return Err(Error::LabelMissMatch);
415 }
416 state = PemParsingState::PostEncapsulationBoundary;
417 } else {
418 if !is_base64_finl(line) {
419 return Err(Error::InvalidBase64Finl);
420 }
421 base64_finl_lines.push(line);
422 }
423 }
424 None => return Err(Error::MissingPostEncapsulationBoundary),
425 },
426 PemParsingState::PostEncapsulationBoundary => break,
427 }
428 }
429 let finl = base64_finl(&base64_finl_lines)?;
430 base64_lines.push(&finl);
431
432 Ok(Pem {
433 label,
434 base64_data: base64_lines.join(""),
435 })
436 }
437}
438
439#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
445enum PemParsingState {
446 #[default]
447 Init,
448 PreEncapsulationBoundary,
449 Base64Lines,
450 Base64Finl,
451 PostEncapsulationBoundary,
452}
453
454fn base64_finl(lines: &[&str]) -> Result<String, Error> {
455 if lines.iter().any(|l| l.is_empty()) {
462 return Err(Error::InvalidBase64Finl);
463 }
464 let lines = lines.iter().map(|l| l.trim()).collect::<Vec<&str>>();
465 let content = lines.join("");
466 Ok(content)
467}
468
469fn is_base64_finl(line: &str) -> bool {
470 if line.contains("=") {
471 return true;
472 }
473 false
474}
475
476#[cfg(test)]
477mod tests {
478 use rstest::rstest;
479
480 use crate::Error;
481 use crate::Label;
482 use crate::Pem;
483 use std::str::FromStr;
484 use tsumiki::decoder::Decoder;
485
486 #[rstest(
487 input,
488 expected,
489 case("-----BEGIN PRIVATE KEY-----", Label::PrivateKey),
490 case("-----END PUBLIC KEY-----", Label::PublicKey),
491 case("-----END PUBLIC KEY----- ", Label::PublicKey),
492 case("-----END PUBLIC KEY----- ", Label::PublicKey)
493 )]
494 fn test_get_label(input: &str, expected: Label) {
495 let got = Label::get_label(input).unwrap();
496 assert_eq!(expected, got);
497 }
498
499 const TEST_PEM1: &str = r"-----BEGIN PRIVATE KEY-----
500AAA
501-----END PRIVATE KEY-----
502";
503 const TEST_PEM2: &str = r"-----BEGIN PRIVATE KEY-----
504AAA
505BBB==
506-----END PRIVATE KEY-----
507";
508 const TEST_PEM3: &str = r"-----BEGIN PRIVATE KEY-----
509AAA
510BBB=
511=
512-----END PRIVATE KEY-----
513";
514 const TEST_PEM4: &str = r"Subject: CN=Atlantis
515Issuer: CN=Atlantis
516-----BEGIN PRIVATE KEY-----
517AAA=
518-----END PRIVATE KEY-----
519";
520 const TEST_PEM_CERT1: &str = r"-----BEGIN CERTIFICATE-----
521MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G
522A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y
523aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0
524ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw
525CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy
526dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu
527dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB
528BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X
529uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud
530DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG
531SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA
532l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo=
533-----END CERTIFICATE-----";
534
535 const TEST_PEM_CERT2: &str = r"-----BEGIN CERTIFICATE-----
536MIIDXTCCAkWgAwIBAgIJAKL0UG+mRkmSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
537BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
538aWRnaXRzIFB0eSBMdGQwHhcNMTYxMjIxMTYzMDA1WhcNMjYxMjE5MTYzMDA1WjBF
539MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
540ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
541CgKCAQEAw3khLOKBaKp0I+rkfpJH6i1KBmfEpuCrzK5LMZaFZiVgW/SxXU31N1ee
5424WMrNkfxbI4UlGhPmvlTjP7bvC5V0U28kCZ5s9PQb1FvkPvEJhw9aJVf3zr5wZRb
5438PyBwP3qUfYYWdJmHAHSKb3wDTl4m9wW0i3BNJxW2FLCQU0hRGiCBnW3hEMCH8m2
544P+kQhUITjy9VfNJmKi5dL3RDXZHN+9gYvwHAabMh8qdWKaJCxAiLN4AO9dVXqOJd
545e1TuZ/Vl6qJ3hYT3T3DdVCJ7vHXLqXBnGMxbFhD8rJ4f5V7QRQVbKl1fWZRGtqzB
546YaKyMMoHCMLa3qJvGDEJGTCKB1LEawIDAQABo1AwTjAdBgNVHQ4EFgQUo2hUXWzw
547BI1kxA1WFCLKjWHHwdQwHwYDVR0jBBgwFoAUo2hUXWzwBI1kxA1WFCLKjWHHwdQw
548DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAaDQl2e0vqOCqGNzYqZyY
549S7RJVYW6WIoq7KdQ0m2Bz2NKRvh2KCqCLZvOuDWoOqMHIQM3FnOFv2FIzTT6sqLv
550njRKYAx9Vd4NeMkPq3QHJU7RMkr3EGqFPB8/Zr/p8lZL5DsHKAQv0P9fxbLPxEqw
551Db4tBf4sFjflSF5g3yD4UwmQvSvYGDW8LqhpSL0FZ8thCR4Ii9L9vGBr5fqB3pFM
552uS6eN4Ck5fC4VaZuPKpCj6c7L5i8BDvPbZV4h6FJZFGpd7qPrCJUvYJH0u5MiLJh
553H6Z2F5qzxFr3dVOYlTUQPYJGBZBpXgXL5fBnPWnPPuLFBNLNNqCpM5cY+c5dS9YE
554pg==
555-----END CERTIFICATE-----";
556
557 #[rstest(
558 input,
559 expected_label,
560 expected_data,
561 case(TEST_PEM1, Label::PrivateKey, "AAA"),
562 case(TEST_PEM2, Label::PrivateKey, "AAABBB=="),
563 case(TEST_PEM3, Label::PrivateKey, "AAABBB=="),
564 case(TEST_PEM4, Label::PrivateKey, "AAA="),
565 case(
566 TEST_PEM_CERT1,
567 Label::Certificate,
568 "MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQswCQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4XuQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1UdDwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqGSM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEAl4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo="
569 )
570 )]
571 fn test_pem_from_str(input: &str, expected_label: Label, expected_data: &str) {
572 let pem = Pem::from_str(input).unwrap();
573 assert_eq!(expected_label, pem.label());
574 assert_eq!(expected_data, pem.data());
575 }
576
577 const INVALID_TEST_PEM1: &str = r"";
578 const INVALID_TEST_PEM2: &str = r"-----BEGIN PRIVATE KEY-----
579
580-----END PRIVATE KEY-----
581";
582 const INVALID_TEST_PEM3: &str = r"-----BEGIN PRIVATE KEY-----
583AAA
584";
585 const INVALID_TEST_PEM4: &str = r"-----BEGIN PRIVATE KEY-----
586AAA
587
588-----END PRIVATE KEY-----
589";
590 const INVALID_TEST_PEM5: &str = r"-----BEGIN PRIVATE KEY-----
591AAA==
592-----END PUBLIC KEY-----
593";
594 #[rstest(
595 input,
596 expected,
597 case(INVALID_TEST_PEM1, Error::MissingPreEncapsulationBoundary),
598 case(INVALID_TEST_PEM2, Error::MissingData),
599 case(INVALID_TEST_PEM3, Error::MissingPostEncapsulationBoundary),
600 case(INVALID_TEST_PEM4, Error::InvalidBase64Line),
601 case(INVALID_TEST_PEM5, Error::LabelMissMatch)
602 )]
603 fn test_pem_from_str_with_error(input: &str, expected: Error) {
604 if let Err(e) = Pem::from_str(input) {
605 assert_eq!(expected, e);
606 } else {
607 panic!("this test should return an error");
608 }
609 }
610
611 #[rstest(
612 pem_str,
613 label,
614 case(TEST_PEM_CERT1, Label::Certificate),
615 case(TEST_PEM_CERT2, Label::Certificate)
616 )]
617 fn test_pem_roundtrip(pem_str: &str, label: Label) {
618 let original_pem: Pem = pem_str.parse().unwrap();
619 let decoded: Vec<u8> = original_pem.decode().unwrap();
620 let re_encoded_pem = Pem::from_bytes(label, &decoded);
621
622 let re_decoded: Vec<u8> = re_encoded_pem.decode().unwrap();
624 assert_eq!(decoded, re_decoded);
625 }
626
627 #[rstest]
628 #[case::single(vec![TEST_PEM_CERT1], "\n", 1)]
629 #[case::multiple(vec![TEST_PEM_CERT1, TEST_PEM_CERT2], "\n", 2)]
630 #[case::with_whitespace(vec![TEST_PEM_CERT1, TEST_PEM_CERT2], "\n\n\n", 2)]
631 #[case::no_trailing_newline(vec![TEST_PEM_CERT1, TEST_PEM_CERT2], "", 2)]
632 fn test_parse_many(#[case] certs: Vec<&str>, #[case] sep: &str, #[case] expected_count: usize) {
633 let input = certs
634 .iter()
635 .map(|c| c.trim_end())
636 .collect::<Vec<_>>()
637 .join(sep);
638 let pems = crate::parse_many(&input).unwrap();
639 assert_eq!(pems.len(), expected_count);
640 }
641
642 #[test]
643 fn test_parse_many_empty() {
644 let result = crate::parse_many("");
645 assert!(result.is_err());
646 }
647}