1use anyhow::{anyhow, bail, Result};
10use data_encoding::BASE64URL_NOPAD;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum KeyFormat {
15 Hex,
17 LegacyBase64,
20}
21
22pub fn normalize_key_classify(key: &str) -> Result<(String, KeyFormat)> {
30 let trimmed = key.trim();
31 match trimmed.len() {
32 128 => {
33 hex::decode(trimmed).map_err(|e| anyhow!("not a valid hex key: {e}"))?;
34 Ok((trimmed.to_lowercase(), KeyFormat::Hex))
35 }
36 86 => {
37 let bytes = BASE64URL_NOPAD
38 .decode(trimmed.as_bytes())
39 .map_err(|e| anyhow!("not a valid base64 key: {e}"))?;
40 if bytes.len() != 64 {
41 bail!("decoded base64 key is {} bytes, expected 64", bytes.len());
42 }
43 Ok((hex::encode(bytes), KeyFormat::LegacyBase64))
44 }
45 n => bail!("key has length {n}; expected 128 (hex) or 86 (legacy base64)"),
46 }
47}
48
49pub fn normalize_key_to_hex(key: &str) -> Result<String> {
51 Ok(normalize_key_classify(key)?.0)
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn hex_passes_through() {
60 let h = "0".repeat(128);
61 let (out, fmt) = normalize_key_classify(&h).unwrap();
62 assert_eq!(out, h);
63 assert_eq!(fmt, KeyFormat::Hex);
64 }
65
66 #[test]
67 fn base64_classifies_as_legacy() {
68 let b64 = "A".repeat(86);
69 let (_, fmt) = normalize_key_classify(&b64).unwrap();
70 assert_eq!(fmt, KeyFormat::LegacyBase64);
71 }
72
73 #[test]
74 fn hex_lowercased() {
75 let mixed = "AaBbCcDd".to_string() + &"0".repeat(120);
76 let n = normalize_key_to_hex(&mixed).unwrap();
77 assert_eq!(n.chars().next().unwrap(), 'a');
78 }
79
80 #[test]
81 fn base64_round_trips_to_hex() {
82 let b64 = "A".repeat(86);
84 let h = normalize_key_to_hex(&b64).unwrap();
85 assert_eq!(h, "0".repeat(128));
86 }
87
88 #[test]
89 fn wrong_length_rejected() {
90 assert!(normalize_key_to_hex(&"a".repeat(50)).is_err());
91 assert!(normalize_key_to_hex(&"a".repeat(127)).is_err());
92 assert!(normalize_key_to_hex("").is_err());
93 }
94
95 #[test]
96 fn trims_whitespace() {
97 let h = "0".repeat(128);
98 let padded = format!(" {h}\n");
99 assert_eq!(normalize_key_to_hex(&padded).unwrap(), h);
100 }
101}