1use std::{iter, str::from_utf8};
7
8use base64::{Engine, engine::general_purpose::STANDARD as B64};
9use hmac::{Hmac, KeyInit, Mac};
10use ring::rand::{SecureRandom, SystemRandom};
11use sha2::{Digest, Sha256, Sha512};
12use subtle::ConstantTimeEq;
13
14use super::messages::{encode_saslname, is_extension};
15use crate::{AuthError, SaslMechanism};
16
17#[derive(Debug)]
19pub struct ScramClientExchange {
20 username: String,
21 password: Vec<u8>,
22 mechanism: SaslMechanism,
23 extensions: Vec<(String, String)>,
24}
25
26#[derive(Debug)]
28pub struct AwaitingServerFirst {
29 username: String,
30 password: Vec<u8>,
31 mechanism: SaslMechanism,
32 client_first_bare: String,
33 client_nonce: String,
34}
35
36#[derive(Debug)]
38pub struct AwaitingServerFinal {
39 mechanism: SaslMechanism,
40 username: String,
41 auth_message: String,
42 server_key: Vec<u8>,
43}
44
45impl ScramClientExchange {
46 #[must_use]
47 pub fn new(username: String, password: Vec<u8>, mechanism: SaslMechanism) -> Self {
50 assert2::assert!(mechanism.is_scram());
51 Self {
52 username,
53 password,
54 mechanism,
55 extensions: Vec::new(),
56 }
57 }
58
59 pub fn with_extensions<K, V>(
70 mut self,
71 extensions: impl IntoIterator<Item = (K, V)>,
72 ) -> Result<Self, AuthError>
73 where
74 K: Into<String>,
75 V: Into<String>,
76 {
77 for (key, value) in extensions {
78 let (key, value) = (key.into(), value.into());
79 if !is_extension(&key, &value) {
80 return Err(AuthError::MalformedMessage);
81 }
82 match self.extensions.iter_mut().find(|(seen, _)| *seen == key) {
86 Some(entry) => entry.1 = value,
87 None => self.extensions.push((key, value)),
88 }
89 }
90 Ok(self)
91 }
92
93 #[allow(private_interfaces)]
101 #[tracing::instrument(
102 level = "debug",
103 skip_all,
104 fields(mechanism = %self.mechanism.wire_name(), principal = %self.username),
105 err
106 )]
107 pub fn client_first(self) -> Result<(Vec<u8>, AwaitingServerFirst), AuthError> {
110 let mut nonce_bytes = [0u8; 18];
111 SystemRandom::new()
112 .fill(&mut nonce_bytes)
113 .map_err(|_| AuthError::MalformedMessage)?;
114 let client_nonce = B64.encode(nonce_bytes);
115 let bare = iter::once(format!(
116 "n={},r={client_nonce}",
117 encode_saslname(&self.username)
118 ))
119 .chain(
120 self.extensions
121 .iter()
122 .map(|(key, value)| format!("{key}={value}")),
123 )
124 .collect::<Vec<_>>()
125 .join(",");
126 let msg = format!("n,,{bare}");
127 let next = AwaitingServerFirst {
128 username: self.username,
129 password: self.password,
130 mechanism: self.mechanism,
131 client_first_bare: bare,
132 client_nonce,
133 };
134 Ok((msg.into_bytes(), next))
135 }
136}
137
138impl AwaitingServerFirst {
139 #[allow(private_interfaces)]
145 #[tracing::instrument(
146 level = "debug",
147 skip_all,
148 fields(mechanism = %self.mechanism.wire_name(), principal = %self.username),
149 err
150 )]
151 pub fn step(self, server_bytes: &[u8]) -> Result<(Vec<u8>, AwaitingServerFinal), AuthError> {
152 let s = from_utf8(server_bytes).map_err(|_| AuthError::MalformedMessage)?;
153 let mut nonce = None;
154 let mut salt = None;
155 let mut iterations = None;
156 for attr in s.split(',') {
157 if let Some(v) = attr.strip_prefix("r=") {
158 nonce = Some(v.to_owned());
159 } else if let Some(v) = attr.strip_prefix("s=") {
160 salt = Some(B64.decode(v).map_err(|_| AuthError::MalformedMessage)?);
161 } else if let Some(v) = attr.strip_prefix("i=") {
162 iterations = Some(v.parse::<u32>().map_err(|_| AuthError::MalformedMessage)?);
163 }
164 }
165 let (Some(combined_nonce), Some(salt), Some(iters)) = (nonce, salt, iterations) else {
166 return Err(AuthError::MalformedMessage);
167 };
168 if !combined_nonce.starts_with(&self.client_nonce) {
169 return Err(AuthError::BadProof);
170 }
171
172 let channel_binding = B64.encode(b"n,,");
173 let client_final_no_proof = format!("c={channel_binding},r={combined_nonce}");
174 let auth_message = format!("{},{s},{client_final_no_proof}", self.client_first_bare);
175
176 let (proof, server_key) = match self.mechanism {
177 SaslMechanism::ScramSha512 => {
178 compute_proof_sha512(&self.password, &salt, iters, auth_message.as_bytes())?
179 }
180 SaslMechanism::ScramSha256 => {
181 compute_proof_sha256(&self.password, &salt, iters, auth_message.as_bytes())?
182 }
183 SaslMechanism::Plain | SaslMechanism::OAuthBearer | SaslMechanism::Gssapi => {
184 return Err(AuthError::MalformedMessage);
185 }
186 };
187
188 let client_final = format!("{client_final_no_proof},p={}", B64.encode(&proof));
189 let next = AwaitingServerFinal {
190 mechanism: self.mechanism,
191 username: self.username,
192 auth_message,
193 server_key,
194 };
195 Ok((client_final.into_bytes(), next))
196 }
197}
198
199impl AwaitingServerFinal {
200 #[tracing::instrument(
204 level = "debug",
205 skip_all,
206 fields(mechanism = %self.mechanism.wire_name(), principal = %self.username),
207 err
208 )]
209 pub fn verify_server_final(self, server_bytes: &[u8]) -> Result<(), AuthError> {
210 let s = from_utf8(server_bytes).map_err(|_| AuthError::MalformedMessage)?;
211 let v_b64 = s.strip_prefix("v=").ok_or(AuthError::MalformedMessage)?;
212 let v = B64.decode(v_b64).map_err(|_| AuthError::MalformedMessage)?;
213 let expected: Vec<u8> = match self.mechanism {
214 SaslMechanism::ScramSha512 => {
215 let mut mac = <Hmac<Sha512>>::new_from_slice(&self.server_key)
216 .map_err(|_| AuthError::MalformedMessage)?;
217 mac.update(self.auth_message.as_bytes());
218 mac.finalize().into_bytes().to_vec()
219 }
220 SaslMechanism::ScramSha256 => {
221 let mut mac = <Hmac<Sha256>>::new_from_slice(&self.server_key)
222 .map_err(|_| AuthError::MalformedMessage)?;
223 mac.update(self.auth_message.as_bytes());
224 mac.finalize().into_bytes().to_vec()
225 }
226 SaslMechanism::Plain | SaslMechanism::OAuthBearer | SaslMechanism::Gssapi => {
227 return Err(AuthError::MalformedMessage);
228 }
229 };
230 if expected.ct_eq(&v).unwrap_u8() != 1 {
231 return Err(AuthError::BadProof);
232 }
233 Ok(())
234 }
235}
236
237pub(super) fn compute_proof_sha512(
238 password: &[u8],
239 salt: &[u8],
240 iters: u32,
241 auth_message: &[u8],
242) -> Result<(Vec<u8>, Vec<u8>), AuthError> {
243 let salted: [u8; 64] = pbkdf2::pbkdf2_hmac_array::<Sha512, 64>(password, salt, iters);
244 let mut client_key_mac =
245 <Hmac<Sha512>>::new_from_slice(&salted).map_err(|_| AuthError::MalformedMessage)?;
246 client_key_mac.update(b"Client Key");
247 let client_key = client_key_mac.finalize().into_bytes();
248 let stored_key = Sha512::digest(client_key);
249 let mut server_key_mac =
250 <Hmac<Sha512>>::new_from_slice(&salted).map_err(|_| AuthError::MalformedMessage)?;
251 server_key_mac.update(b"Server Key");
252 let server_key = server_key_mac.finalize().into_bytes().to_vec();
253
254 let mut client_sig_mac =
255 <Hmac<Sha512>>::new_from_slice(&stored_key).map_err(|_| AuthError::MalformedMessage)?;
256 client_sig_mac.update(auth_message);
257 let client_signature = client_sig_mac.finalize().into_bytes();
258 let proof: Vec<u8> = client_key
259 .iter()
260 .zip(client_signature.iter())
261 .map(|(a, b)| a ^ b)
262 .collect();
263 Ok((proof, server_key))
264}
265
266pub(super) fn compute_proof_sha256(
267 password: &[u8],
268 salt: &[u8],
269 iters: u32,
270 auth_message: &[u8],
271) -> Result<(Vec<u8>, Vec<u8>), AuthError> {
272 let salted: [u8; 32] = pbkdf2::pbkdf2_hmac_array::<Sha256, 32>(password, salt, iters);
273 let mut client_key_mac =
274 <Hmac<Sha256>>::new_from_slice(&salted).map_err(|_| AuthError::MalformedMessage)?;
275 client_key_mac.update(b"Client Key");
276 let client_key = client_key_mac.finalize().into_bytes();
277 let stored_key = Sha256::digest(client_key);
278 let mut server_key_mac =
279 <Hmac<Sha256>>::new_from_slice(&salted).map_err(|_| AuthError::MalformedMessage)?;
280 server_key_mac.update(b"Server Key");
281 let server_key = server_key_mac.finalize().into_bytes().to_vec();
282
283 let mut client_sig_mac =
284 <Hmac<Sha256>>::new_from_slice(&stored_key).map_err(|_| AuthError::MalformedMessage)?;
285 client_sig_mac.update(auth_message);
286 let client_signature = client_sig_mac.finalize().into_bytes();
287 let proof: Vec<u8> = client_key
288 .iter()
289 .zip(client_signature.iter())
290 .map(|(a, b)| a ^ b)
291 .collect();
292 Ok((proof, server_key))
293}