1use sha1::{Digest, Sha1};
10
11use crate::error::InvalidPublicKeyError;
12use crate::key::{
13 PrivateKey, Proof, ReconnectData, SKey, Sha1Hash, Verifier, PROOF_LENGTH, SESSION_KEY_LENGTH,
14 SHA1_HASH_LENGTH, S_LENGTH,
15};
16use crate::key::{PublicKey, Salt};
17use crate::key::{SessionKey, PASSWORD_VERIFIER_LENGTH};
18use crate::normalized_string::NormalizedString;
19use crate::primes::{Generator, KValue, LargeSafePrime};
20
21const PRECALCULATED_XOR_HASH: [u8; SHA1_HASH_LENGTH as usize] = [
24 221, 123, 176, 58, 56, 172, 115, 17, 3, 152, 124, 90, 80, 111, 202, 150, 108, 123, 194, 167,
25];
26
27pub fn calculate_x(
54 username: &NormalizedString,
55 password: &NormalizedString,
56 salt: &Salt,
57) -> Sha1Hash {
58 let p = Sha1::new()
59 .chain_update(username.as_ref())
60 .chain_update(":")
61 .chain_update(password.as_ref())
62 .finalize();
63
64 let x = Sha1::new()
65 .chain_update(salt.as_le_bytes())
66 .chain_update(p)
67 .finalize();
68
69 Sha1Hash::from_le_bytes(x.into())
70}
71
72pub fn calculate_password_verifier(
96 username: &NormalizedString,
97 password: &NormalizedString,
98 salt: &Salt,
99 ) -> [u8; PASSWORD_VERIFIER_LENGTH as usize] {
101 let x = calculate_x(username, password, salt).as_bigint();
102
103 let generator = Generator::default().to_bigint();
104 let large_safe_prime = LargeSafePrime::default().to_bigint();
105
106 let password_verifier = generator.modpow(&x, &large_safe_prime);
107
108 password_verifier.to_padded_32_byte_array_le()
109}
110
111pub fn calculate_server_public_key(
113 password_verifier: &Verifier,
114 server_private_key: &PrivateKey,
115) -> Result<PublicKey, InvalidPublicKeyError> {
116 let generator = Generator::default().to_bigint();
117 let large_safe_prime = LargeSafePrime::default().to_bigint();
118
119 let server_public_key = (KValue::bigint() * password_verifier.as_bigint()
120 + generator.modpow(&server_private_key.as_bigint(), &large_safe_prime))
121 % large_safe_prime;
122
123 PublicKey::try_from_bigint(server_public_key)
124}
125
126pub fn calculate_u(client_public_key: &PublicKey, server_public_key: &PublicKey) -> Sha1Hash {
128 let s = Sha1::new()
129 .chain_update(client_public_key.as_le_bytes())
130 .chain_update(server_public_key.as_le_bytes())
131 .finalize();
132 Sha1Hash::from_le_bytes(s.into())
133}
134
135#[allow(non_snake_case)] pub fn calculate_S(
139 client_public_key: &PublicKey,
140 password_verifier: &Verifier,
141 u: &Sha1Hash,
142 server_private_key: &PrivateKey,
143) -> SKey {
144 let large_safe_prime = LargeSafePrime::default().to_bigint();
145
146 (client_public_key.as_bigint()
147 * password_verifier
148 .as_bigint()
149 .modpow(&u.as_bigint(), &large_safe_prime))
150 .modpow(&server_private_key.as_bigint(), &large_safe_prime)
151 .into()
152}
153
154#[allow(non_snake_case)]
156pub fn calculate_interleaved(S: &SKey) -> SessionKey {
157 let S = S.as_equal_slice();
158
159 let mut E = [0_u8; (S_LENGTH / 2) as usize];
160 for (i, e) in S.iter().step_by(2).enumerate() {
161 E[i] = *e;
162 }
163 let G = Sha1::new().chain_update(&E[..S.len() / 2]).finalize();
164
165 let mut F = [0_u8; (S_LENGTH / 2) as usize];
166 for (i, f) in S.iter().skip(1).step_by(2).enumerate() {
167 F[i] = *f;
168 }
169 let H = Sha1::new().chain_update(&F[..S.len() / 2]).finalize();
170
171 let mut result = [0_u8; SESSION_KEY_LENGTH as usize];
172 let zip = G.iter().zip(H.iter());
173 for (i, r) in zip.enumerate() {
174 result[i * 2] = *r.0;
175 result[(i * 2) + 1] = *r.1;
176 }
177
178 SessionKey::from_le_bytes(result)
179}
180
181pub fn calculate_session_key(
183 client_public_key: &PublicKey,
184 server_public_key: &PublicKey,
185 password_verifier: &Verifier,
186 server_private_key: &PrivateKey,
187) -> SessionKey {
188 let u = &calculate_u(client_public_key, server_public_key);
189 #[allow(non_snake_case)]
190 let S = calculate_S(client_public_key, password_verifier, u, server_private_key);
191
192 calculate_interleaved(&S)
193}
194
195pub fn calculate_server_proof(
196 client_public_key: &PublicKey,
197 client_proof: &Proof,
198 session_key: &SessionKey,
199) -> Proof {
200 let s = Sha1::new()
201 .chain_update(client_public_key.as_le_bytes())
202 .chain_update(client_proof.as_le_bytes())
203 .chain_update(session_key.as_le_bytes())
204 .finalize();
205
206 Proof::from_le_bytes(s.into())
207}
208
209pub(crate) fn calculate_xor_hash(
210 large_safe_prime: &LargeSafePrime,
211 generator: &Generator,
212) -> Sha1Hash {
213 let large_safe_prime_hash = Sha1::new()
214 .chain_update(large_safe_prime.as_le_bytes())
215 .finalize();
216
217 let g_hash = Sha1::new().chain_update([generator.as_u8()]).finalize();
218
219 let mut xor_hash = [0_u8; SHA1_HASH_LENGTH as usize];
220 for (i, n) in large_safe_prime_hash.iter().enumerate() {
221 xor_hash[i] = *n ^ g_hash[i];
222 }
223
224 Sha1Hash::from_le_bytes(xor_hash)
225}
226
227pub fn calculate_client_proof(
228 username: &NormalizedString,
229 session_key: &SessionKey,
230 client_public_key: &PublicKey,
231 server_public_key: &PublicKey,
232 salt: &Salt,
233) -> Proof {
234 let username_hash = Sha1::new().chain_update(username.as_ref()).finalize();
235
236 let out: [u8; PROOF_LENGTH as usize] = Sha1::new()
237 .chain_update(PRECALCULATED_XOR_HASH)
238 .chain_update(username_hash)
239 .chain_update(salt.as_le_bytes())
240 .chain_update(client_public_key.as_le_bytes())
241 .chain_update(server_public_key.as_le_bytes())
242 .chain_update(session_key.as_le_bytes())
243 .finalize()
244 .into();
245
246 Proof::from_le_bytes(out)
247}
248
249pub fn calculate_reconnect_proof(
250 username: &NormalizedString,
251 client_data: &ReconnectData,
252 server_data: &ReconnectData,
253 session_key: &SessionKey,
254) -> Proof {
255 let s = Sha1::new()
256 .chain_update(username.as_ref())
257 .chain_update(client_data.as_le_bytes())
258 .chain_update(server_data.as_le_bytes())
259 .chain_update(session_key.as_le_bytes())
260 .finalize();
261
262 Proof::from_le_bytes(s.into())
263}
264
265#[cfg(test)]
266mod test {
267 use crate::key::{
268 PrivateKey, Proof, PublicKey, ReconnectData, SKey, Salt, SessionKey, Sha1Hash, Verifier,
269 };
270 use crate::normalized_string::NormalizedString;
271 use crate::primes::{
272 Generator, LargeSafePrime, LARGE_SAFE_PRIME_BIG_ENDIAN, LARGE_SAFE_PRIME_LITTLE_ENDIAN,
273 };
274 use crate::srp_internal::{
275 calculate_S, calculate_client_proof, calculate_interleaved, calculate_password_verifier,
276 calculate_reconnect_proof, calculate_server_proof, calculate_server_public_key,
277 calculate_session_key, calculate_u, calculate_x, calculate_xor_hash,
278 PRECALCULATED_XOR_HASH,
279 };
280
281 #[test]
282 fn verify_reconnection_proof() {
283 let contents = include_str!("../tests/srp6_internal/calculate_reconnection_values.txt");
284
285 for line in contents.lines() {
286 let mut line = line.split_whitespace();
287 let username = NormalizedString::new(line.next().unwrap()).unwrap();
288 let client_data = ReconnectData::from_le_hex_str(line.next().unwrap());
289 let server_data = ReconnectData::from_le_hex_str(line.next().unwrap());
290 let session_key = SessionKey::from_le_hex_str(line.next().unwrap());
291 let expected = Proof::from_le_hex_str(line.next().unwrap());
292
293 let proof =
294 calculate_reconnect_proof(&username, &client_data, &server_data, &session_key);
295 assert_eq!(proof, expected);
296 }
297 }
298
299 #[test]
300 fn verify_x_username_and_password() {
301 let contents = include_str!("../tests/srp6_internal/calculate_x_values.txt");
302 let salt = Salt::from_be_hex_str(
303 "CAC94AF32D817BA64B13F18FDEDEF92AD4ED7EF7AB0E19E9F2AE13C828AEAF57",
304 );
305 for line in contents.lines() {
306 let mut line = line.split_whitespace();
307 let username = NormalizedString::new(line.next().unwrap()).unwrap();
308 let password = NormalizedString::new(line.next().unwrap()).unwrap();
309
310 let expected = Sha1Hash::from_be_hex_str(line.next().unwrap());
311
312 let x = calculate_x(&username, &password, &salt);
313
314 assert_eq!(expected, x, "Salt: '{}'", &salt.as_be_hex_string());
316 }
317 }
318
319 #[test]
320 fn verify_x_salt() {
321 let contents = include_str!("../tests/srp6_internal/calculate_x_salt_values.txt");
322 let username = NormalizedString::new("USERNAME123").unwrap();
323 let password = NormalizedString::new("PASSWORD123").unwrap();
324
325 for line in contents.lines() {
326 let mut line = line.split_whitespace();
327 let salt = Salt::from_be_hex_str(line.next().unwrap());
328
329 let expected = Sha1Hash::from_be_hex_str(line.next().unwrap());
330
331 let x = calculate_x(&username, &password, &salt);
332
333 assert_eq!(expected, x, "Salt: '{}'", &salt.as_be_hex_string());
335 }
336 }
337
338 #[test]
339 fn verify_password_verifier_username_password_salt() {
340 let contents = include_str!("../tests/srp6_internal/calculate_v_values.txt");
341
342 for line in contents.lines() {
343 let mut line = line.split_whitespace();
344 let username = NormalizedString::new(line.next().unwrap()).unwrap();
345 let password = NormalizedString::new(line.next().unwrap()).unwrap();
346
347 let salt = Salt::from_be_hex_str(line.next().unwrap());
348
349 let expected = Verifier::from_be_hex_str(line.next().unwrap());
350
351 let v =
352 Verifier::from_le_bytes(calculate_password_verifier(&username, &password, &salt));
353
354 assert_eq!(
356 expected,
357 v,
358 "Username: '{}',\n Password: '{}',\n Salt: '{}'",
359 username,
360 password,
361 &salt.as_be_hex_string()
362 );
363 }
364 }
365
366 #[test]
367 fn verify_server_public_key_calculation() {
368 let contents = include_str!("../tests/srp6_internal/calculate_B_values.txt");
369 for line in contents.lines() {
370 let mut line = line.split_whitespace();
371
372 let verifier = Verifier::from_be_hex_str(line.next().unwrap());
373
374 let server_private_key = PrivateKey::from_be_hex_str(line.next().unwrap());
375
376 let expected = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
377
378 let server_public_key =
379 calculate_server_public_key(&verifier, &server_private_key).unwrap();
380
381 assert_eq!(
383 expected,
384 server_public_key,
385 "v: '{}',\n b: '{}'",
386 verifier.as_be_hex_string(),
387 server_private_key.as_be_hex_string(),
388 );
389 }
390 }
391
392 #[test]
393 fn verify_u() {
394 let contents = include_str!("../tests/srp6_internal/calculate_u_values.txt");
395
396 for line in contents.lines() {
397 let mut line = line.split_whitespace();
398
399 let client_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
400
401 let server_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
402
403 let expected = Sha1Hash::from_be_hex_str(line.next().unwrap());
404
405 let u = calculate_u(&client_public_key, &server_public_key);
406
407 assert_eq!(
408 expected,
409 u,
410 "A: '{}',\n B: '{}'",
411 client_public_key.as_be_hex_string(),
412 server_public_key.as_be_hex_string()
413 );
414 }
415 }
416
417 #[test]
418 #[allow(non_snake_case)]
419 fn verify_S() {
420 let contents = include_str!("../tests/srp6_internal/calculate_S_values.txt");
421
422 for line in contents.lines() {
423 let mut line = line.split_whitespace();
424
425 let client_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
426
427 let password_verifier = Verifier::from_be_hex_str(line.next().unwrap());
428
429 let u = Sha1Hash::from_be_hex_str(line.next().unwrap());
430
431 let server_private_key = PrivateKey::from_be_hex_str(line.next().unwrap());
432
433 let expected = SKey::from_be_hex_str(line.next().unwrap());
434
435 let S = calculate_S(
436 &client_public_key,
437 &password_verifier,
438 &u,
439 &server_private_key,
440 );
441
442 assert_eq!(
444 expected,
445 S,
446 "A: '{}',\n v: '{}',\n u: '{}',\n b: '{}'",
447 client_public_key.as_be_hex_string(),
448 password_verifier.as_be_hex_string(),
449 u.as_be_hex_string(),
450 server_private_key.as_be_hex_string(),
451 );
452 }
453 }
454
455 #[test]
456 #[allow(non_snake_case)]
457 fn verify_interleaved_key() {
458 let contents = include_str!("../tests/srp6_internal/calculate_interleaved_values.txt");
459
460 for line in contents.lines() {
461 let mut line = line.split_whitespace();
462
463 let S = SKey::from_le_hex_str(line.next().unwrap());
464
465 let expected = SessionKey::from_le_hex_str(line.next().unwrap());
466
467 let interleaved = calculate_interleaved(&S);
468
469 assert_eq!(expected, interleaved, "S: '{}'", &S.as_be_hex_string());
471 }
472 }
473
474 #[test]
475 fn verify_session_key() {
476 let contents = include_str!("../tests/srp6_internal/calculate_session_key_values.txt");
477
478 for line in contents.lines() {
479 let mut line = line.split_whitespace();
480 let client_public_key = PublicKey::from_le_hex_str(line.next().unwrap());
481 let password_verifier = Verifier::from_le_hex_str(line.next().unwrap());
482 let server_private_key = PrivateKey::from_le_hex_str(line.next().unwrap());
483
484 let expected = SessionKey::from_le_hex_str(line.next().unwrap());
485
486 let server_public_key =
487 calculate_server_public_key(&password_verifier, &server_private_key).unwrap();
488
489 let session_key = calculate_session_key(
490 &client_public_key,
491 &server_public_key,
492 &password_verifier,
493 &server_private_key,
494 );
495
496 assert_eq!(
498 expected,
499 session_key,
500 "client_public_key: '{}',\n password_verifier: '{}',\n server_private_key: '{}',\n server_public_key: '{}'",
501 &client_public_key.as_be_hex_string(),
502 &password_verifier.as_be_hex_string(),
503 &server_private_key.as_be_hex_string(),
504 &server_public_key.as_be_hex_string(),
505 );
506 }
507 }
508
509 #[test]
510 fn verify_client_proof() {
511 let contents = include_str!("../tests/srp6_internal/calculate_M1_values.txt");
512
513 for line in contents.lines() {
514 let mut line = line.split_whitespace();
515
516 let username = NormalizedString::new(line.next().unwrap()).unwrap();
517
518 let session_key = SessionKey::from_le_hex_str(line.next().unwrap());
519
520 let client_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
521
522 let server_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
523
524 let salt = Salt::from_be_hex_str(line.next().unwrap());
525
526 let expected = Proof::from_be_hex_str(line.next().unwrap());
527
528 let client_proof = calculate_client_proof(
529 &username,
530 &session_key,
531 &client_public_key,
532 &server_public_key,
533 &salt,
534 );
535
536 assert_eq!(
538 expected,
539 client_proof,
540 "Username: '{}',\n session_key: '{}',\n client_public_key: '{}',\n server_public_key: '{}',\n salt: '{}'",
541 username,
542 &session_key.as_be_hex_string(),
543 &client_public_key.as_be_hex_string(),
544 &server_public_key.as_be_hex_string(),
545 &salt.as_be_hex_string(),
546 );
547 }
548 }
549
550 #[test]
551 fn verify_server_proof() {
552 let contents = include_str!("../tests/srp6_internal/calculate_M2_values.txt");
553
554 for line in contents.lines() {
555 let mut line = line.split_whitespace();
556
557 let client_public_key = PublicKey::from_be_hex_str(line.next().unwrap()).unwrap();
558
559 let client_proof = Proof::from_be_hex_str(line.next().unwrap());
560
561 let session_key = SessionKey::from_le_hex_str(line.next().unwrap());
562
563 let expected = Proof::from_be_hex_str(line.next().unwrap());
564
565 let server_proof =
566 calculate_server_proof(&client_public_key, &client_proof, &session_key);
567
568 assert_eq!(
569 expected,
570 server_proof,
571 "Client public key: '{}',\n client_proof: '{}',\n session_key: '{}'",
572 client_public_key.as_be_hex_string(),
573 client_proof.as_be_hex_string(),
574 session_key.as_be_hex_string(),
575 );
576 }
577 }
578
579 #[test]
580 fn large_safe_prime_same_big_and_little_endian() {
581 let large_safe_prime = LARGE_SAFE_PRIME_BIG_ENDIAN;
582 let mut large_safe_prime_little_endian = LARGE_SAFE_PRIME_LITTLE_ENDIAN;
583 large_safe_prime_little_endian.reverse();
584 assert_eq!(large_safe_prime, large_safe_prime_little_endian);
585 }
586
587 #[test]
588 fn precalculated_xor_hash_is_correct() {
589 let large_safe_prime = LargeSafePrime::default();
590 let generator = Generator::default();
591 let xor_hash = calculate_xor_hash(&large_safe_prime, &generator);
592
593 assert_eq!(xor_hash.as_le_bytes(), &PRECALCULATED_XOR_HASH);
594 }
595}