darkbio_crypto/pem/
mod.rs1use base64::Engine;
10use base64::engine::general_purpose::STANDARD;
11use std::error::Error;
12
13const PEM_HEADER: &[u8] = b"-----BEGIN ";
14const PEM_FOOTER: &[u8] = b"-----END ";
15const PEM_ENDING: &[u8] = b"-----";
16
17pub fn decode(data: &[u8]) -> Result<(String, Vec<u8>), Box<dyn Error>> {
29 if !data.starts_with(PEM_HEADER) {
31 return Err("pem: missing PEM header".into());
32 }
33 let header_end = data
35 .iter()
36 .position(|&b| b == b'\n')
37 .ok_or("pem: incomplete PEM header")?;
38
39 let line_ending: &[u8] = if header_end > 0 && data[header_end - 1] == b'\r' {
41 b"\r\n"
42 } else {
43 b"\n"
44 };
45
46 let header = if line_ending.len() == 2 {
48 &data[..header_end - 1]
49 } else {
50 &data[..header_end]
51 };
52
53 if !header.starts_with(PEM_HEADER) || !header.ends_with(PEM_ENDING) {
55 return Err("pem: malformed PEM header".into());
56 }
57 let block_type = &header[PEM_HEADER.len()..header.len() - PEM_ENDING.len()];
58 if block_type.is_empty() {
59 return Err("pem: empty PEM block type".into());
60 }
61 let kind = String::from_utf8(block_type.to_vec())?;
62
63 let mut footer = Vec::with_capacity(PEM_FOOTER.len() + block_type.len() + PEM_ENDING.len());
65 footer.extend_from_slice(PEM_FOOTER);
66 footer.extend_from_slice(block_type);
67 footer.extend_from_slice(PEM_ENDING);
68
69 let search_area = &data[header_end + 1..];
71 let footer_idx = search_area
72 .windows(footer.len())
73 .position(|w| w == footer.as_slice())
74 .ok_or("pem: missing PEM footer")?;
75 let footer_start = header_end + 1 + footer_idx;
76 let footer_end = footer_start + footer.len();
77
78 let rest = &data[footer_end..];
80 if !rest.is_empty() && rest != line_ending {
81 return Err("pem: trailing data after PEM block".into());
82 }
83
84 let body = &data[header_end + 1..footer_start];
86
87 if body.is_empty() {
89 return Err("pem: empty PEM body".into());
90 }
91 if !body.ends_with(line_ending) {
92 return Err("pem: body must end with newline before footer".into());
93 }
94 let body = &body[..body.len() - line_ending.len()];
95
96 let b64: Vec<u8> = body
98 .split(|&b| b == b'\n')
99 .flat_map(|line| {
100 if line.ends_with(b"\r") {
101 &line[..line.len() - 1]
102 } else {
103 line
104 }
105 })
106 .copied()
107 .collect();
108
109 let decoded = STANDARD.decode(&b64)?;
110
111 Ok((kind, decoded))
112}
113
114pub fn encode(kind: &str, data: &[u8]) -> String {
117 let b64 = STANDARD.encode(data);
118
119 let mut buf = String::new();
120 buf.push_str("-----BEGIN ");
121 buf.push_str(kind);
122 buf.push_str("-----\n");
123
124 for chunk in b64.as_bytes().chunks(64) {
125 buf.push_str(std::str::from_utf8(chunk).unwrap());
126 buf.push('\n');
127 }
128
129 buf.push_str("-----END ");
130 buf.push_str(kind);
131 buf.push_str("-----\n");
132
133 buf
134}