1use std::collections::HashMap;
12use std::sync::Mutex;
13
14use argon2::password_hash::rand_core::OsRng;
15use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
16use argon2::{Algorithm, Argon2, Params, Version};
17use tokio::sync::Semaphore;
18
19fn argon2() -> Argon2<'static> {
22 Argon2::new(
23 Algorithm::Argon2id,
24 Version::V0x13,
25 Params::new(8 * 1024, 2, 1, None).expect("valid params"),
26 )
27}
28
29pub fn hash_password(password: &[u8]) -> String {
30 let salt = SaltString::generate(&mut OsRng);
31 argon2()
32 .hash_password(password, &salt)
33 .expect("argon2 hashing")
34 .to_string()
35}
36
37fn verify_one(password: &[u8], phc: &str) -> bool {
38 PasswordHash::new(phc).is_ok_and(|h| argon2().verify_password(password, &h).is_ok())
39}
40
41fn hex_decode(s: &[u8]) -> Option<Vec<u8>> {
42 if !s.len().is_multiple_of(2) {
43 return None;
44 }
45 let v = |c: u8| (c as char).to_digit(16).map(|d| d as u8);
46 s.chunks(2)
47 .map(|p| Some(v(p[0])? << 4 | v(p[1])?))
48 .collect()
49}
50
51fn hex_encode(b: &[u8], upper: bool) -> Vec<u8> {
52 b.iter()
53 .flat_map(|x| {
54 if upper {
55 format!("{x:02X}")
56 } else {
57 format!("{x:02x}")
58 }
59 .into_bytes()
60 })
61 .collect()
62}
63
64fn verify_password(password: &[u8], phc: &str) -> bool {
66 if verify_one(password, phc) {
67 return true;
68 }
69 if password.len() == 40
70 && let Some(raw) = hex_decode(password)
71 {
72 return verify_one(&raw, phc);
73 }
74 if password.len() == 20 {
75 return verify_one(&hex_encode(password, true), phc)
76 || verify_one(&hex_encode(password, false), phc);
77 }
78 false
79}
80
81pub fn parse_basic(header: &str) -> Option<(String, Vec<u8>)> {
83 let (scheme, value) = header.trim().split_once(' ')?;
84 if !scheme.eq_ignore_ascii_case("basic") {
85 return None;
86 }
87 let decoded = ocpp_ws::base64::decode(value.trim())?;
88 let colon = decoded.iter().position(|&b| b == b':')?;
89 let user = String::from_utf8(decoded[..colon].to_vec()).ok()?;
90 Some((user, decoded[colon + 1..].to_vec()))
91}
92
93pub struct Authenticator {
96 permits: Semaphore,
97 verified: Mutex<HashMap<String, (Vec<u8>, String)>>,
98}
99
100impl Authenticator {
101 pub fn new(concurrency: usize) -> Self {
102 Authenticator {
103 permits: Semaphore::new(concurrency.max(1)),
104 verified: Mutex::new(HashMap::new()),
105 }
106 }
107
108 pub async fn verify(&self, identity: &str, password: &[u8], phc: &str) -> bool {
109 {
110 let cache = self.verified.lock().unwrap_or_else(|e| e.into_inner());
111 if let Some((pw, hash)) = cache.get(identity)
112 && pw.as_slice() == password
113 && hash == phc
114 {
115 return true;
116 }
117 }
118 let Ok(_permit) = self.permits.acquire().await else {
119 return false;
120 };
121 let (pw, h) = (password.to_vec(), phc.to_owned());
122 let ok = tokio::task::spawn_blocking(move || verify_password(&pw, &h))
123 .await
124 .unwrap_or(false);
125 let mut cache = self.verified.lock().unwrap_or_else(|e| e.into_inner());
126 if ok {
127 cache.insert(identity.to_owned(), (password.to_vec(), phc.to_owned()));
128 } else {
129 cache.remove(identity);
130 }
131 ok
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn basic_header_with_binary_key() {
141 let (u, p) = parse_basic("Basic QUwxMDAwOgABAgMEBQYH////////////////").unwrap();
142 assert_eq!(u, "AL1000");
143 assert_eq!(p.len(), 20);
144 assert_eq!(&p[..8], &[0, 1, 2, 3, 4, 5, 6, 7]);
145 assert!(parse_basic("Bearer abc").is_none());
146 }
147
148 #[tokio::test]
149 async fn raw_and_hex_forms_match() {
150 let key: Vec<u8> = (0u8..20).collect();
151 let phc = hash_password(&key);
152 let a = Authenticator::new(2);
153 assert!(a.verify("CS1", &key, &phc).await);
154 assert!(
155 a.verify("CS1", b"000102030405060708090a0b0c0d0e0f10111213", &phc)
156 .await
157 );
158 assert!(!a.verify("CS1", b"wrong", &phc).await);
159 let phc2 = hash_password(b"a-utf8-password-of-2.0.1");
160 assert!(a.verify("CS2", b"a-utf8-password-of-2.0.1", &phc2).await);
161 assert!(a.verify("CS2", b"a-utf8-password-of-2.0.1", &phc2).await);
162 }
163}