1use base64::{engine::general_purpose::STANDARD as Base64, Engine};
4
5use crate::client::{Mechanism, MechanismError};
6use crate::common::scram::{generate_nonce, ScramProvider};
7use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret};
8
9use crate::error::Error;
10
11use alloc::format;
12use alloc::string::String;
13use alloc::vec::Vec;
14use core::marker::PhantomData;
15
16enum ScramState {
17 Init,
18 SentInitialMessage {
19 initial_message: Vec<u8>,
20 gs2_header: Vec<u8>,
21 },
22 GotServerData {
23 server_signature: Vec<u8>,
24 },
25}
26
27pub struct Scram<S: ScramProvider> {
29 name: String,
30 name_plus: String,
31 username: String,
32 password: Password,
33 client_first_extensions: String,
34 client_final_extensions: String,
35 client_nonce: String,
36 state: ScramState,
37 channel_binding: ChannelBinding,
38 _marker: PhantomData<S>,
39}
40
41impl<S: ScramProvider> Scram<S> {
42 pub fn new<N: Into<String>, P: Into<Password>>(
48 username: N,
49 password: P,
50 channel_binding: ChannelBinding,
51 ) -> Result<Scram<S>, Error> {
52 Ok(Scram {
53 name: format!("SCRAM-{}", S::name()),
54 name_plus: format!("SCRAM-{}-PLUS", S::name()),
55 username: username.into(),
56 password: password.into(),
57 client_first_extensions: String::new(),
58 client_final_extensions: String::new(),
59 client_nonce: generate_nonce()?,
60 state: ScramState::Init,
61 channel_binding,
62 _marker: PhantomData,
63 })
64 }
65
66 pub fn with_first_extensions(mut self, extensions: String) -> Self {
70 self.client_first_extensions = extensions;
71 self
72 }
73
74 pub fn with_final_extensions(mut self, extensions: String) -> Self {
78 self.client_final_extensions = extensions;
79 self
80 }
81
82 #[doc(hidden)]
84 #[cfg(test)]
85 pub fn new_with_nonce<N: Into<String>, P: Into<Password>>(
86 username: N,
87 password: P,
88 nonce: String,
89 ) -> Scram<S> {
90 Scram {
91 name: format!("SCRAM-{}", S::name()),
92 name_plus: format!("SCRAM-{}-PLUS", S::name()),
93 username: username.into(),
94 password: password.into(),
95 client_first_extensions: String::new(),
96 client_final_extensions: String::new(),
97 client_nonce: nonce,
98 state: ScramState::Init,
99 channel_binding: ChannelBinding::None,
100 _marker: PhantomData,
101 }
102 }
103}
104
105impl<S: ScramProvider> Mechanism for Scram<S> {
106 fn name(&self) -> &str {
107 match self.channel_binding {
109 ChannelBinding::None | ChannelBinding::Unsupported => &self.name,
110 ChannelBinding::TlsUnique(_) | ChannelBinding::TlsExporter(_) => &self.name_plus,
111 }
112 }
113
114 fn from_credentials(credentials: Credentials) -> Result<Scram<S>, MechanismError> {
115 if let Secret::Password(password) = credentials.secret {
116 if let Identity::Username(username) = credentials.identity {
117 Scram::new(username, password, credentials.channel_binding)
118 .map_err(|_| MechanismError::CannotGenerateNonce)
119 } else {
120 Err(MechanismError::ScramRequiresUsername)
121 }
122 } else {
123 Err(MechanismError::ScramRequiresPassword)
124 }
125 }
126
127 fn initial(&mut self) -> Vec<u8> {
128 let mut gs2_header = Vec::new();
129 gs2_header.extend(self.channel_binding.header());
130 let mut bare = Vec::new();
131 bare.extend(b"n=");
132 bare.extend(self.username.bytes());
133 bare.extend(b",r=");
134 bare.extend(self.client_nonce.bytes());
135 if !self.client_first_extensions.is_empty() {
136 bare.extend(b",");
137 bare.extend(self.client_first_extensions.bytes());
138 }
139 let mut data = Vec::new();
140 data.extend(&gs2_header);
141 data.extend(&bare);
142 self.state = ScramState::SentInitialMessage {
143 initial_message: bare,
144 gs2_header,
145 };
146 data
147 }
148
149 fn response(&mut self, challenge: &[u8]) -> Result<Vec<u8>, MechanismError> {
150 let next_state;
151 let ret;
152 match self.state {
153 ScramState::SentInitialMessage {
154 ref initial_message,
155 ref gs2_header,
156 } => {
157 let frame =
158 parse_frame(challenge).map_err(|_| MechanismError::CannotDecodeChallenge)?;
159 let server_nonce = frame.get(&'r');
160 let salt = frame.get(&'s').and_then(|v| Base64.decode(v).ok());
161 let iterations = frame.get(&'i').and_then(|v| v.parse().ok());
162 let server_nonce = server_nonce.ok_or(MechanismError::NoServerNonce)?;
163 let salt = salt.ok_or(MechanismError::NoServerSalt)?;
164 let iterations = iterations.ok_or(MechanismError::NoServerIterations)?;
165 let mut client_final_message_bare = Vec::new();
167 client_final_message_bare.extend(b"c=");
168 let mut cb_data: Vec<u8> = Vec::new();
169 cb_data.extend(gs2_header);
170 cb_data.extend(self.channel_binding.data());
171 client_final_message_bare.extend(Base64.encode(&cb_data).bytes());
172 client_final_message_bare.extend(b",r=");
173 client_final_message_bare.extend(server_nonce.bytes());
174 if !self.client_final_extensions.is_empty() {
175 client_final_message_bare.extend(b",");
176 client_final_message_bare.extend(self.client_final_extensions.bytes());
177 }
178 let salted_password = S::derive(&self.password, &salt, iterations)?;
179 let client_key = S::hmac(b"Client Key", &salted_password)?;
180 let server_key = S::hmac(b"Server Key", &salted_password)?;
181 let mut auth_message = Vec::new();
182 auth_message.extend(initial_message);
183 auth_message.push(b',');
184 auth_message.extend(challenge);
185 auth_message.push(b',');
186 auth_message.extend(&client_final_message_bare);
187 let stored_key = S::hash(&client_key);
188 let client_signature = S::hmac(&auth_message, &stored_key)?;
189 let client_proof = xor(&client_key, &client_signature);
190 let server_signature = S::hmac(&auth_message, &server_key)?;
191 let mut client_final_message = Vec::new();
192 client_final_message.extend(&client_final_message_bare);
193 client_final_message.extend(b",p=");
194 client_final_message.extend(Base64.encode(client_proof).bytes());
195 next_state = ScramState::GotServerData { server_signature };
196 ret = client_final_message;
197 }
198 _ => {
199 return Err(MechanismError::InvalidState);
200 }
201 }
202 self.state = next_state;
203 Ok(ret)
204 }
205
206 fn success(&mut self, data: &[u8]) -> Result<(), MechanismError> {
207 let frame = parse_frame(data).map_err(|_| MechanismError::CannotDecodeSuccessResponse)?;
208 match self.state {
209 ScramState::GotServerData {
210 ref server_signature,
211 } => {
212 if let Some(sig) = frame.get(&'v').and_then(|v| Base64.decode(v).ok()) {
213 if sig == *server_signature {
214 Ok(())
215 } else {
216 Err(MechanismError::InvalidSignatureInSuccessResponse)
217 }
218 } else {
219 Err(MechanismError::NoSignatureInSuccessResponse)
220 }
221 }
222 _ => Err(MechanismError::InvalidState),
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use crate::client::mechanisms::Scram;
230 use crate::client::Mechanism;
231 use crate::common::scram::{Sha1, Sha256};
232 use alloc::borrow::ToOwned;
233 use alloc::string::String;
234
235 #[test]
236 fn scram_sha1_works() {
237 let username = "user";
239 let password = "pencil";
240 let client_nonce = "fyko+d2lbbFgONRv9qkxdawL";
241 let client_init = b"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL";
242 let server_init = b"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096";
243 let client_final =
244 b"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=";
245 let server_final = b"v=rmF9pqV8S7suAoZWja4dJRkFsKQ=";
246 let mut mechanism =
247 Scram::<Sha1>::new_with_nonce(username, password, client_nonce.to_owned());
248 let init = mechanism.initial();
249 assert_eq!(
250 String::from_utf8(init.clone()).unwrap(),
251 String::from_utf8(client_init[..].to_owned()).unwrap()
252 ); let resp = mechanism.response(&server_init[..]).unwrap();
254 assert_eq!(
255 String::from_utf8(resp.clone()).unwrap(),
256 String::from_utf8(client_final[..].to_owned()).unwrap()
257 ); mechanism.success(&server_final[..]).unwrap();
259 }
260
261 #[test]
262 fn scram_sha256_works() {
263 let username = "user";
265 let password = "pencil";
266 let client_nonce = "rOprNGfwEbeRWgbNEkqO";
267 let client_init = b"n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
268 let server_init = b"r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
269 let client_final = b"c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
270 let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
271 let mut mechanism =
272 Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned());
273 let init = mechanism.initial();
274 assert_eq!(
275 String::from_utf8(init.clone()).unwrap(),
276 String::from_utf8(client_init[..].to_owned()).unwrap()
277 ); let resp = mechanism.response(&server_init[..]).unwrap();
279 assert_eq!(
280 String::from_utf8(resp.clone()).unwrap(),
281 String::from_utf8(client_final[..].to_owned()).unwrap()
282 ); mechanism.success(&server_final[..]).unwrap();
284 }
285
286 #[test]
287 fn scram_kafka_token_delegation_works() {
288 let username = "6Lbb79aSTs-mDWUPc64D9Q";
290 let password = "O574x+7mB0B8R9Yt8DqwWbIzBgEm3lUE+fy7VWdvCwcLvGvwJK9GM4y0Qaz/MxiIxDHEnxDfSuB13uycXiUqyg==";
291 let client_nonce = "o6wj2xqdu0fxe4nmnukkj076m";
292 let client_init = b"n,,n=6Lbb79aSTs-mDWUPc64D9Q,r=o6wj2xqdu0fxe4nmnukkj076m,tokenauth=true";
293 let server_init = b"r=o6wj2xqdu0fxe4nmnukkj076m1eut816hvmsycqw2qzyn14zxvr,s=MWVtNWw1Mzc1MnFianNoYWhqMjhyYzVzZHM=,i=4096";
294 let client_final = b"c=biws,r=o6wj2xqdu0fxe4nmnukkj076m1eut816hvmsycqw2qzyn14zxvr,p=qVfqg28hDgroc6pal4qCF+8hO1/wiB84o7snGRDZKuE=";
295 let server_final = b"v=2ZSkAlHEUj6WehcizLhQRiiVGn+VDVtmAqj1v/IPa28=";
296 let mut mechanism =
297 Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned())
298 .with_first_extensions("tokenauth=true".to_owned());
299 let init = mechanism.initial();
300 assert_eq!(
301 core::str::from_utf8(&init).unwrap(),
302 core::str::from_utf8(client_init).unwrap()
303 ); let resp = mechanism.response(server_init).unwrap();
305 assert_eq!(
306 core::str::from_utf8(&resp).unwrap(),
307 core::str::from_utf8(client_final).unwrap()
308 ); mechanism.success(server_final).unwrap();
310 }
311
312 #[test]
313 fn scram_final_extension_works() {
314 let username = "some_user";
315 let password = "a_password";
316 let client_nonce = "client_nonce";
317 let client_init = b"n,,n=some_user,r=client_nonce";
318 let server_init =
319 b"r=client_nonceserver_nonce,s=MWVtNWw1Mzc1MnFianNoYWhqMjhyYzVzZHM=,i=4096";
320 let client_final = b"c=biws,r=client_nonceserver_nonce,foo=true,p=T9XQLmykBv74DzbaCtX90/ElJYJU2XWM/jHmHJ+BI/w=";
321 let mut mechanism =
322 Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned())
323 .with_final_extensions("foo=true".to_owned());
324 let init = mechanism.initial();
325 assert_eq!(
326 core::str::from_utf8(&init).unwrap(),
327 core::str::from_utf8(client_init).unwrap()
328 ); let resp = mechanism.response(server_init).unwrap();
330 assert_eq!(
331 core::str::from_utf8(&resp).unwrap(),
332 core::str::from_utf8(client_final).unwrap()
333 ); }
335}