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