1pub fn parse_key(raw: &str) -> Result<[u8; 32], String> {
16 let trimmed = raw.trim();
17 if trimmed.len() == 64 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
19 let mut out = [0u8; 32];
20 for (i, byte) in out.iter_mut().enumerate() {
21 *byte = u8::from_str_radix(&trimmed[i * 2..i * 2 + 2], 16)
22 .map_err(|err| format!("invalid hex key byte {i}: {err}"))?;
23 }
24 return Ok(out);
25 }
26 let decoded = decode_base64(trimmed)
30 .map_err(|err| format!("key is neither 64-hex nor base64 (decode error: {err})"))?;
31 if decoded.len() != 32 {
32 return Err(format!(
33 "decoded key is {} bytes; AES-256-GCM requires exactly 32",
34 decoded.len()
35 ));
36 }
37 let mut out = [0u8; 32];
38 out.copy_from_slice(&decoded);
39 Ok(out)
40}
41
42fn decode_base64(s: &str) -> Result<Vec<u8>, String> {
43 fn val(c: u8) -> Option<u8> {
44 match c {
45 b'A'..=b'Z' => Some(c - b'A'),
46 b'a'..=b'z' => Some(c - b'a' + 26),
47 b'0'..=b'9' => Some(c - b'0' + 52),
48 b'+' => Some(62),
49 b'/' => Some(63),
50 _ => None,
51 }
52 }
53 let bytes: Vec<u8> = s
54 .bytes()
55 .filter(|b| !b.is_ascii_whitespace() && *b != b'=')
56 .collect();
57 let mut out = Vec::with_capacity(bytes.len() * 3 / 4);
58 let mut i = 0;
59 while i + 3 < bytes.len() {
60 let a = val(bytes[i]).ok_or_else(|| format!("invalid base64 char at {i}"))?;
61 let b = val(bytes[i + 1]).ok_or_else(|| format!("invalid base64 char at {}", i + 1))?;
62 let c = val(bytes[i + 2]).ok_or_else(|| format!("invalid base64 char at {}", i + 2))?;
63 let d = val(bytes[i + 3]).ok_or_else(|| format!("invalid base64 char at {}", i + 3))?;
64 out.push((a << 2) | (b >> 4));
65 out.push(((b & 0x0F) << 4) | (c >> 2));
66 out.push(((c & 0x03) << 6) | d);
67 i += 4;
68 }
69 let rem = bytes.len() - i;
70 match rem {
71 0 => {}
72 2 => {
73 let a = val(bytes[i]).ok_or_else(|| format!("invalid base64 char at {i}"))?;
74 let b = val(bytes[i + 1]).ok_or_else(|| format!("invalid base64 char at {}", i + 1))?;
75 out.push((a << 2) | (b >> 4));
76 }
77 3 => {
78 let a = val(bytes[i]).ok_or_else(|| format!("invalid base64 char at {i}"))?;
79 let b = val(bytes[i + 1]).ok_or_else(|| format!("invalid base64 char at {}", i + 1))?;
80 let c = val(bytes[i + 2]).ok_or_else(|| format!("invalid base64 char at {}", i + 2))?;
81 out.push((a << 2) | (b >> 4));
82 out.push(((b & 0x0F) << 4) | (c >> 2));
83 }
84 _ => return Err(format!("invalid base64 length remainder {rem}")),
85 }
86 Ok(out)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use reddb_types::encoding::base64_encode;
93
94 #[test]
95 fn parse_key_accepts_hex() {
96 let hex = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
97 let key = parse_key(hex).unwrap();
98 assert_eq!(key[0], 0x01);
99 assert_eq!(key[31], 0x20);
100 }
101
102 #[test]
103 fn parse_key_accepts_hex_with_whitespace() {
104 let hex = " 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\n";
105 assert!(parse_key(hex).is_ok());
106 }
107
108 #[test]
109 fn parse_key_rejects_wrong_length() {
110 assert!(parse_key("ab").is_err());
111 assert!(parse_key("zz".repeat(32).as_str()).is_err()); }
113
114 #[test]
115 fn parse_key_accepts_base64() {
116 let raw = vec![0xAB_u8; 32];
118 let out = base64_encode(&raw);
119 let key = parse_key(&out).unwrap();
120 assert_eq!(key, [0xABu8; 32]);
121 }
122
123 #[test]
124 fn decode_base64_accepts_symbols_whitespace_and_padding() {
125 assert_eq!(decode_base64(" +/+/==\n").unwrap(), vec![251, 255, 191]);
126 }
127
128 #[test]
129 fn decode_base64_rejects_invalid_chars_by_position() {
130 for input in [
131 "!AAA", "A!AA", "AA!A", "AAA!", "!A", "A!", "!AA", "A!A", "AA!",
132 ] {
133 assert!(decode_base64(input).is_err(), "{input}");
134 }
135 }
136
137 #[test]
138 fn decode_base64_rejects_single_char_remainder() {
139 let err = decode_base64("A").unwrap_err();
140 assert!(err.contains("invalid base64 length remainder 1"));
141 }
142}