wow_srp/
srp_internal.rs

1//! Internal functions only exposed in order to help other implementations with testing and verification.
2//! This module should not be used except for when verifying another implementation.
3//!
4//! # Structure
5//!
6//!
7//!
8
9use 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
21/// Only used for the [`calculate_client_proof`] function. Since the large safe prime and generator are
22/// statically determined we can precalculate it. See also the [`calculate_xor_hash`] function.
23const 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
27/// Calculate the `x` value which is used for generating the password verifier `v`. See [`calculate_password_verifier`].
28///
29/// `x` is calculated as `H( salt | H( upper( username | : |  password ) ) )` as described on page 3 of [RFC2945] and page 8 of [RFC5054].
30/// Uppercasing is not a requirement for SRP6 itself, only for the `WoW` client.
31///
32/// `H()` is the SHA1 hashing function.
33/// `:` is the literal character `:`.
34///
35/// Some implementations assign `p = H( upper( username | : |  password ) )` in an intermediate step, and then `H( salt | p )` later for clarity.
36/// Keep in mind that `p` in [RFC2945] refers to only the password string on page 4 as `p = <raw password>`.
37///
38/// Notice that the `x` value should only be calculated server side when a user registers an account or changes their password, since the database should never contain the raw password.
39///
40/// # Arguments
41///
42/// * `username` (`U` in [RFC2945], `I` in [RFC5054]) is an **uppercase** UTF-8 encoded strings.
43/// * `password` (`p` in [RFC2945], `P` in [RFC5054]) is an **uppercase** UTF-8 encoded strings.
44/// * `salt` (`s` in [RFC2945] and [RFC5054]) is a **little endian** [32][`SALT_LENGTH_IN_BYTES`] byte array of random values.
45/// The client will not reject an authentication attempt with a salt of all zeros.
46///
47/// # Different Implementations
48///
49/// * [Ember](https://github.com/EmberEmu/Ember/blob/12834cd347472224fa180656222822744be3b1b0/src/libs/srp6/src/Util.cpp#L98)
50///
51/// [RFC2945]: https://tools.ietf.org/html/rfc2945
52/// [RFC5054]: https://tools.ietf.org/html/rfc5054
53pub 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
72/// Calculate the password verifier `v` used for generating the server public key `B` and the session key intermediate value `S`.
73/// See [`calculate_server_public_key`] and [`calculate_S`].
74///
75/// `v` is calculated as `g^x % N` as described on page 3 of [RFC2945].
76/// For `x` see [`calculate_x`].
77///
78/// # Arguments
79///
80/// * `username` (`U` in [RFC2945], `I` in [RFC5054]) is an **uppercase** UTF-8 encoded strings.
81/// * `password` (`p` in [RFC2945], `P` in [RFC5054]) is an **uppercase** UTF-8 encoded strings.
82/// * `salt` (`s` in [RFC2945] and [RFC5054]) is a **little endian** [32 byte][`SALT_LENGTH_IN_BYTES`] array of random values.
83/// The client will not reject an authentication attempt with a salt of all zeros.
84///
85/// # Return value
86///
87/// A zero padded **little endian** array the [size of N][`N_LENGTH`].
88///
89/// # Different Implementations
90///
91/// * [Ember](https://github.com/EmberEmu/Ember/blob/12834cd347472224fa180656222822744be3b1b0/src/libs/srp6/include/srp6/Util.h#L48)
92///
93/// [RFC2945]: https://tools.ietf.org/html/rfc2945
94/// [RFC5054]: https://tools.ietf.org/html/rfc5054
95pub fn calculate_password_verifier(
96    username: &NormalizedString,
97    password: &NormalizedString,
98    salt: &Salt,
99    // Return an array instead of Verifier because this is never directly used to create a Verifier
100) -> [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
111/// Calculate the server public key `B`.
112pub 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
126/// Calculate the parameter `u` used for generating the session key.
127pub 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/// Calculate the `S` value used for generating the session key.
136/// Return value is a N sized big endian array.
137#[allow(non_snake_case)] // There is no better descriptor than 'S'
138pub 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/// Return value is big endian??
155#[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
181// Returns a 40 byte big endian array.
182pub 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            // Normalize hex values to uppercase
315            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            // Normalize hex values to uppercase
334            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            // Normalize hex values to uppercase
355            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            // Normalize hex values to uppercase
382            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            // Normalize hex values to uppercase
443            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            // Normalize hex values to uppercase
470            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            // Normalize hex values to uppercase
497            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            // Normalize hex values to uppercase
537            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}