1use std::fmt;
7
8use base64::{Engine, engine::general_purpose::STANDARD as B64};
9use ring::{
10 hmac as ring_hmac,
11 rand::{SecureRandom, SystemRandom},
12};
13use sha2::{Digest, Sha256, Sha512};
14use subtle::ConstantTimeEq;
15
16use super::{
17 ScramClientFirst, ScramCredential,
18 messages::{JAVA_B64, ScramClientFinal},
19 scram_hash_len,
20};
21use crate::{AuthError, AuthMethod, Principal, SaslMechanism};
22
23struct AwaitingClientFirst {
24 username: String,
25 credential: ScramCredential,
26 principal_override: Option<Principal>,
27}
28
29struct AwaitingClientFinal {
30 username: String,
31 credential: ScramCredential,
32 principal_override: Option<Principal>,
33 client_first_bare: String,
34 server_first: String,
35}
36
37#[allow(private_interfaces)]
43pub enum ScramServerExchange {
44 AwaitingClientFirst(AwaitingClientFirst),
45 AwaitingClientFinal(AwaitingClientFinal),
46}
47
48impl fmt::Debug for ScramServerExchange {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::AwaitingClientFirst(_) => f
52 .debug_tuple("ScramServerExchange::AwaitingClientFirst")
53 .finish(),
54 Self::AwaitingClientFinal(_) => f
55 .debug_tuple("ScramServerExchange::AwaitingClientFinal")
56 .finish(),
57 }
58 }
59}
60
61#[derive(Debug)]
62#[must_use]
63pub enum StepResult {
64 Continue(Vec<u8>, ScramServerExchange),
65 Done(Principal, Vec<u8>),
66 Failed(AuthError),
67 Disconnect,
75}
76
77impl ScramServerExchange {
78 #[must_use]
79 pub fn new(username: String, credential: ScramCredential) -> Self {
80 Self::AwaitingClientFirst(AwaitingClientFirst {
81 username,
82 credential,
83 principal_override: None,
84 })
85 }
86
87 #[must_use]
94 pub fn new_with_principal(
95 username: String,
96 credential: ScramCredential,
97 override_principal: Principal,
98 ) -> Self {
99 Self::AwaitingClientFirst(AwaitingClientFirst {
100 username,
101 credential,
102 principal_override: Some(override_principal),
103 })
104 }
105
106 pub fn step(self, client_bytes: &[u8]) -> StepResult {
108 match self {
109 Self::AwaitingClientFirst(s) => s.step(client_bytes),
110 Self::AwaitingClientFinal(s) => s.step(client_bytes),
111 }
112 }
113}
114
115impl AwaitingClientFirst {
116 #[tracing::instrument(
121 level = "debug",
122 skip_all,
123 fields(
124 mechanism = %self.credential.mechanism.wire_name(),
125 principal = %self.username,
126 )
127 )]
128 fn step(self, client_bytes: &[u8]) -> StepResult {
129 let client_first = match ScramClientFirst::parse(client_bytes) {
134 Ok(client_first) => client_first,
135 Err(e) => return StepResult::Failed(e),
136 };
137 if client_first.username != self.username {
138 return StepResult::Failed(AuthError::UnknownUser);
139 }
140 if client_first
144 .gs2_header
145 .strip_prefix("n,a=")
146 .and_then(|header| header.strip_suffix(','))
147 .is_some_and(|authzid| authzid != client_first.username)
148 {
149 return StepResult::Failed(AuthError::AuthorizationIdMismatch);
150 }
151 let c_nonce = client_first.nonce;
152 let mut server_nonce_bytes = [0u8; 18];
153 if SystemRandom::new().fill(&mut server_nonce_bytes).is_err() {
155 return StepResult::Failed(AuthError::MalformedMessage);
156 }
157 let server_nonce = B64.encode(server_nonce_bytes);
158 let combined_nonce = format!("{c_nonce}{server_nonce}");
159 let server_first = format!(
160 "r={},s={},i={}",
161 combined_nonce,
162 B64.encode(&self.credential.salt),
163 self.credential.iterations,
164 );
165 let response = server_first.clone().into_bytes();
166 let next = AwaitingClientFinal {
167 username: self.username,
168 credential: self.credential,
169 principal_override: self.principal_override,
170 client_first_bare: client_first.bare,
171 server_first,
172 };
173 StepResult::Continue(response, ScramServerExchange::AwaitingClientFinal(next))
174 }
175}
176
177impl AwaitingClientFinal {
178 #[tracing::instrument(
184 level = "debug",
185 skip_all,
186 fields(
187 mechanism = %self.credential.mechanism.wire_name(),
188 principal = %self.username,
189 )
190 )]
191 fn step(self, client_bytes: &[u8]) -> StepResult {
192 let client_first_bare = &self.client_first_bare;
193 let server_first = &self.server_first;
194 let client_final = match ScramClientFinal::parse(client_bytes) {
197 Ok(client_final) => client_final,
198 Err(e) => return StepResult::Failed(e),
199 };
200 let nonce = client_final.nonce;
201
202 let expected_nonce = server_first
208 .strip_prefix("r=")
209 .and_then(|rest| rest.split(',').next())
210 .unwrap_or_default();
211 if nonce != expected_nonce {
212 return StepResult::Failed(AuthError::MalformedMessage);
213 }
214
215 let Ok(proof) = JAVA_B64.decode(client_final.proof) else {
223 return StepResult::Failed(AuthError::MalformedMessage);
224 };
225 if proof.len() != scram_hash_len(self.credential.mechanism) {
226 return StepResult::Disconnect;
227 }
228
229 let client_final_no_proof = client_final.without_proof;
231
232 let auth_message = format!("{client_first_bare},{server_first},{client_final_no_proof}");
233
234 let (computed_stored, server_signature) = match self.credential.mechanism {
235 SaslMechanism::ScramSha512 => verify_and_sign_sha512(
236 &self.credential.stored_key,
237 &self.credential.server_key,
238 &proof,
239 auth_message.as_bytes(),
240 ),
241 SaslMechanism::ScramSha256 => verify_and_sign_sha256(
242 &self.credential.stored_key,
243 &self.credential.server_key,
244 &proof,
245 auth_message.as_bytes(),
246 ),
247 SaslMechanism::Plain | SaslMechanism::OAuthBearer | SaslMechanism::Gssapi => {
248 return StepResult::Failed(AuthError::MalformedMessage);
249 }
250 };
251
252 if computed_stored
253 .ct_eq(self.credential.stored_key.as_slice())
254 .unwrap_u8()
255 != 1
256 {
257 return StepResult::Failed(AuthError::BadProof);
258 }
259 let server_final = format!("v={}", B64.encode(&server_signature));
260 let principal = self
265 .principal_override
266 .clone()
267 .unwrap_or_else(|| Principal {
268 name: self.username.clone(),
269 auth_method: AuthMethod::from_sasl(self.credential.mechanism),
270 groups: vec![],
271 });
272 StepResult::Done(principal, server_final.into_bytes())
273 }
274}
275
276fn verify_and_sign_sha512(
277 stored_key: &[u8],
278 server_key: &[u8],
279 proof: &[u8],
280 auth_message: &[u8],
281) -> (Vec<u8>, Vec<u8>) {
282 verify_and_sign_with(
283 ring_hmac::HMAC_SHA512,
284 stored_key,
285 server_key,
286 proof,
287 auth_message,
288 |client_key| Sha512::digest(client_key).to_vec(),
289 )
290}
291
292fn verify_and_sign_sha256(
293 stored_key: &[u8],
294 server_key: &[u8],
295 proof: &[u8],
296 auth_message: &[u8],
297) -> (Vec<u8>, Vec<u8>) {
298 verify_and_sign_with(
299 ring_hmac::HMAC_SHA256,
300 stored_key,
301 server_key,
302 proof,
303 auth_message,
304 |client_key| Sha256::digest(client_key).to_vec(),
305 )
306}
307
308fn verify_and_sign_with(
313 algorithm: ring_hmac::Algorithm,
314 stored_key: &[u8],
315 server_key: &[u8],
316 proof: &[u8],
317 auth_message: &[u8],
318 hash: impl FnOnce(&[u8]) -> Vec<u8>,
319) -> (Vec<u8>, Vec<u8>) {
320 let client_signature =
321 ring_hmac::sign(&ring_hmac::Key::new(algorithm, stored_key), auth_message);
322 let client_key: Vec<u8> = client_signature
323 .as_ref()
324 .iter()
325 .zip(proof.iter())
326 .map(|(a, b)| a ^ b)
327 .collect();
328 let computed_stored = hash(&client_key);
329 let server_signature =
330 ring_hmac::sign(&ring_hmac::Key::new(algorithm, server_key), auth_message)
331 .as_ref()
332 .to_vec();
333 (computed_stored, server_signature)
334}