1const B64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8
9pub fn base64_encode(data: &[u8]) -> String {
11 let mut out = String::with_capacity((data.len() + 2) / 3 * 4);
12 for chunk in data.chunks(3) {
13 let b0 = chunk[0] as u32;
14 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
15 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
16 let triple = (b0 << 16) | (b1 << 8) | b2;
17
18 out.push(B64_CHARS[((triple >> 18) & 0x3F) as usize] as char);
19 out.push(B64_CHARS[((triple >> 12) & 0x3F) as usize] as char);
20 if chunk.len() > 1 {
21 out.push(B64_CHARS[((triple >> 6) & 0x3F) as usize] as char);
22 } else {
23 out.push('=');
24 }
25 if chunk.len() > 2 {
26 out.push(B64_CHARS[(triple & 0x3F) as usize] as char);
27 } else {
28 out.push('=');
29 }
30 }
31 out
32}
33
34pub fn base64_decode(s: &str) -> Option<Vec<u8>> {
36 let mut out = Vec::with_capacity(s.len() * 3 / 4);
37 let mut buf = 0u32;
38 let mut count = 0u32;
39
40 for c in s.chars() {
41 if c.is_whitespace() {
42 continue;
43 }
44 if c == '=' {
45 break;
46 }
47 let val = b64_char_value(c)? as u32;
48 buf = (buf << 6) | val;
49 count += 1;
50 if count == 4 {
51 out.push((buf >> 16) as u8);
52 out.push((buf >> 8) as u8);
53 out.push(buf as u8);
54 buf = 0;
55 count = 0;
56 }
57 }
58 match count {
59 2 => {
60 buf <<= 12;
61 out.push((buf >> 16) as u8);
62 }
63 3 => {
64 buf <<= 6;
65 out.push((buf >> 16) as u8);
66 out.push((buf >> 8) as u8);
67 }
68 0 => {}
69 _ => return None, }
71 Some(out)
72}
73
74fn b64_char_value(c: char) -> Option<u8> {
75 match c {
76 'A'..='Z' => Some(c as u8 - b'A'),
77 'a'..='z' => Some(c as u8 - b'a' + 26),
78 '0'..='9' => Some(c as u8 - b'0' + 52),
79 '+' => Some(62),
80 '/' => Some(63),
81 _ => None,
82 }
83}
84
85pub fn pem_encode(label: &str, der: &[u8]) -> String {
91 let b64 = base64_encode(der);
92 let mut out = String::new();
93 out.push_str("-----BEGIN ");
94 out.push_str(label);
95 out.push_str("-----\n");
96 for line in b64.as_bytes().chunks(64) {
98 out.push_str(std::str::from_utf8(line).unwrap());
99 out.push('\n');
100 }
101 out.push_str("-----END ");
102 out.push_str(label);
103 out.push_str("-----\n");
104 out
105}
106
107pub fn pem_decode(label: &str, pem: &str) -> Option<Vec<u8>> {
110 let begin = format!("-----BEGIN {}-----", label);
111 let end = format!("-----END {}-----", label);
112
113 let start = pem.find(&begin)? + begin.len();
114 let stop = pem.find(&end)?;
115 let b64_block = &pem[start..stop];
116 base64_decode(b64_block)
117}
118
119#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn base64_roundtrip() {
129 let cases: &[(&[u8], &str)] = &[
130 (b"", ""),
131 (b"f", "Zg=="),
132 (b"fo", "Zm8="),
133 (b"foo", "Zm9v"),
134 (b"foob", "Zm9vYg=="),
135 (b"fooba", "Zm9vYmE="),
136 (b"foobar", "Zm9vYmFy"),
137 ];
138 for (data, expected) in cases {
139 let encoded = base64_encode(data);
140 assert_eq!(encoded, *expected, "encode {:?}", data);
141 let decoded = base64_decode(&encoded).unwrap();
142 assert_eq!(decoded, *data, "decode {:?}", expected);
143 }
144 }
145
146 #[test]
147 fn base64_binary() {
148 let data: Vec<u8> = (0..256).map(|i| i as u8).collect();
149 let enc = base64_encode(&data);
150 let dec = base64_decode(&enc).unwrap();
151 assert_eq!(dec, data);
152 }
153
154 #[test]
155 fn pem_roundtrip() {
156 let der = vec![0x30, 0x03, 0x02, 0x01, 0x42]; let pem = pem_encode("TEST DATA", &der);
158 assert!(pem.contains("-----BEGIN TEST DATA-----"));
159 assert!(pem.contains("-----END TEST DATA-----"));
160 let decoded = pem_decode("TEST DATA", &pem).unwrap();
161 assert_eq!(decoded, der);
162 }
163
164 #[test]
165 fn pem_wrong_label_returns_none() {
166 let pem = pem_encode("FOO", &[0x01]);
167 assert!(pem_decode("BAR", &pem).is_none());
168 }
169}