1use sha2::{Digest, Sha256};
20
21use crate::storage::encryption::pbkdf2::pbkdf2_sha256;
22use crate::storage::encryption::pbkdf2::Pbkdf2Params;
23
24pub const DEFAULT_ITER: u32 = 16_384;
28
29pub const MIN_ITER: u32 = 4096;
32
33#[derive(Debug, Clone)]
37pub struct ScramVerifier {
38 pub salt: Vec<u8>,
39 pub iter: u32,
40 pub stored_key: [u8; 32],
41 pub server_key: [u8; 32],
42}
43
44impl ScramVerifier {
45 pub fn from_password(password: &str, salt: Vec<u8>, iter: u32) -> Self {
48 let salted = salted_password(password.as_bytes(), &salt, iter);
49 let client_key = hmac_sha256(&salted, b"Client Key");
50 let stored_key: [u8; 32] = sha256(&client_key);
51 let server_key = hmac_sha256(&salted, b"Server Key");
52 Self {
53 salt,
54 iter,
55 stored_key,
56 server_key,
57 }
58 }
59}
60
61pub fn salted_password(password: &[u8], salt: &[u8], iter: u32) -> [u8; 32] {
63 let params = Pbkdf2Params {
64 iterations: iter,
65 ..Pbkdf2Params::default()
67 };
68 let v = pbkdf2_sha256(password, salt, ¶ms);
69 let mut out = [0u8; 32];
70 out.copy_from_slice(&v[..32]);
71 out
72}
73
74pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
77 crate::crypto::hmac_sha256(key, data)
78}
79
80pub fn sha256(data: &[u8]) -> [u8; 32] {
82 let mut hasher = Sha256::new();
83 hasher.update(data);
84 let mut out = [0u8; 32];
85 out.copy_from_slice(&hasher.finalize());
86 out
87}
88
89pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
92 a.iter().zip(b.iter()).map(|(x, y)| x ^ y).collect()
93}
94
95pub fn auth_message(
98 client_first_bare: &str,
99 server_first: &str,
100 client_final_no_proof: &str,
101) -> Vec<u8> {
102 let mut out = Vec::with_capacity(
103 client_first_bare.len() + 1 + server_first.len() + 1 + client_final_no_proof.len(),
104 );
105 out.extend_from_slice(client_first_bare.as_bytes());
106 out.push(b',');
107 out.extend_from_slice(server_first.as_bytes());
108 out.push(b',');
109 out.extend_from_slice(client_final_no_proof.as_bytes());
110 out
111}
112
113pub fn client_proof(stored_key: &[u8], auth_message: &[u8], client_key: &[u8]) -> Vec<u8> {
116 let signature = hmac_sha256(stored_key, auth_message);
117 xor(client_key, &signature)
118}
119
120pub fn verify_client_proof(
123 verifier: &ScramVerifier,
124 auth_message: &[u8],
125 presented_proof: &[u8],
126) -> bool {
127 if presented_proof.len() != 32 {
128 return false;
129 }
130 let signature = hmac_sha256(&verifier.stored_key, auth_message);
132 let client_key = xor(presented_proof, &signature);
133 let derived_stored: [u8; 32] = sha256(&client_key);
134 crate::crypto::constant_time_eq(&derived_stored, &verifier.stored_key)
135}
136
137pub fn server_signature(server_key: &[u8], auth_message: &[u8]) -> [u8; 32] {
140 hmac_sha256(server_key, auth_message)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
150 fn verifier_is_deterministic() {
151 let salt = b"reddb-test-salt".to_vec();
152 let v1 = ScramVerifier::from_password("hunter2", salt.clone(), 4096);
153 let v2 = ScramVerifier::from_password("hunter2", salt, 4096);
154 assert_eq!(v1.stored_key, v2.stored_key);
155 assert_eq!(v1.server_key, v2.server_key);
156 }
157
158 #[test]
162 fn full_round_trip() {
163 let salt = b"reddb-rt-salt".to_vec();
164 let iter = 4096;
165 let verifier = ScramVerifier::from_password("correct horse", salt.clone(), iter);
166
167 let client_first_bare = "n=alice,r=cnonce";
168 let server_first = "r=cnonce+snonce,s=cmVkZGItcnQtc2FsdA==,i=4096";
169 let client_final_no_proof = "c=biws,r=cnonce+snonce";
170 let am = auth_message(client_first_bare, server_first, client_final_no_proof);
171
172 let salted = salted_password(b"correct horse", &salt, iter);
174 let client_key = hmac_sha256(&salted, b"Client Key");
175 let proof = client_proof(&verifier.stored_key, &am, &client_key);
176
177 assert!(verify_client_proof(&verifier, &am, &proof));
179
180 let salted_bad = salted_password(b"wrong password", &salt, iter);
182 let client_key_bad = hmac_sha256(&salted_bad, b"Client Key");
183 let proof_bad = client_proof(&verifier.stored_key, &am, &client_key_bad);
184 assert!(!verify_client_proof(&verifier, &am, &proof_bad));
185 }
186
187 #[test]
188 fn server_signature_round_trip() {
189 let v = ScramVerifier::from_password("p", b"s".to_vec(), 4096);
190 let am = b"some auth message".to_vec();
191 let sig = server_signature(&v.server_key, &am);
192 let again = server_signature(&v.server_key, &am);
194 assert_eq!(sig, again);
195 let other = server_signature(&v.server_key, b"different");
197 assert_ne!(sig, other);
198 }
199
200 #[test]
201 fn xor_basic() {
202 assert_eq!(
203 xor(&[0xff, 0x00, 0xaa], &[0x0f, 0xff, 0x55]),
204 vec![0xf0, 0xff, 0xff]
205 );
206 }
207}