1use hmac::Hmac;
18use num_bigint::{BigInt, Sign};
19use num_traits::ops::euclid::Euclid;
20use sha2::{Digest, Sha256, Sha512};
21
22fn sha256(parts: &[&[u8]]) -> [u8; 32] {
23 let mut h = Sha256::new();
24 for p in parts {
25 h.update(p);
26 }
27 h.finalize().into()
28}
29
30fn sh(data: &[u8], salt: &[u8]) -> [u8; 32] {
31 sha256(&[salt, data, salt])
32}
33
34fn ph1(password: &[u8], salt1: &[u8], salt2: &[u8]) -> [u8; 32] {
35 sh(&sh(password, salt1), salt2)
36}
37
38fn ph2(password: &[u8], salt1: &[u8], salt2: &[u8]) -> [u8; 32] {
39 let hash1 = ph1(password, salt1, salt2);
40 let mut dk = [0u8; 64];
41 pbkdf2::pbkdf2::<Hmac<Sha512>>(&hash1, salt1, 100_000, &mut dk).unwrap();
42 sh(&dk, salt2)
43}
44
45fn pad256(data: &[u8]) -> [u8; 256] {
46 let mut out = [0u8; 256];
47 let start = 256usize.saturating_sub(data.len());
48 out[start..].copy_from_slice(&data[data.len().saturating_sub(256)..]);
49 out
50}
51
52fn xor32(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
53 let mut out = [0u8; 32];
54 for i in 0..32 {
55 out[i] = a[i] ^ b[i];
56 }
57 out
58}
59
60#[derive(Debug)]
62pub enum SrpError {
63 GbOutOfRange,
65 GaOutOfRange,
67}
68
69impl std::fmt::Display for SrpError {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 SrpError::GbOutOfRange => write!(f, "SRP: server g_b out of safe range"),
73 SrpError::GaOutOfRange => write!(f, "SRP: client g_a out of safe range"),
74 }
75 }
76}
77
78impl std::error::Error for SrpError {}
79
80pub fn calculate_2fa(
85 salt1: &[u8],
86 salt2: &[u8],
87 p: &[u8],
88 g: i32,
89 g_b: &[u8],
90 a: &[u8],
91 password: impl AsRef<[u8]>,
92) -> Result<([u8; 32], [u8; 256]), SrpError> {
93 let big_p = BigInt::from_bytes_be(Sign::Plus, p);
94 let g_b = pad256(g_b);
95 let a = pad256(a);
96 let g_hash = pad256(&[g as u8]);
97
98 let big_g_b = BigInt::from_bytes_be(Sign::Plus, &g_b);
99 let big_g = BigInt::from(g as u32);
100 let big_a = BigInt::from_bytes_be(Sign::Plus, &a);
101
102 {
104 let one = BigInt::from(1u32);
105 let p_minus_one = &big_p - &one;
106 if big_g_b <= one || big_g_b >= p_minus_one {
107 return Err(SrpError::GbOutOfRange);
108 }
109 }
110
111 let k = sha256(&[p, &g_hash]);
112 let big_k = BigInt::from_bytes_be(Sign::Plus, &k);
113
114 let g_a = big_g.modpow(&big_a, &big_p);
115 let g_a = pad256(&g_a.to_bytes_be().1);
116
117 {
119 let big_g_a = BigInt::from_bytes_be(Sign::Plus, &g_a);
120 let one = BigInt::from(1u32);
121 let p_minus_one = &big_p - &one;
122 if big_g_a <= one || big_g_a >= p_minus_one {
123 return Err(SrpError::GaOutOfRange);
124 }
125 }
126
127 let u = sha256(&[&g_a, &g_b]);
128 let big_u = BigInt::from_bytes_be(Sign::Plus, &u);
129
130 let x = ph2(password.as_ref(), salt1, salt2);
131 let big_x = BigInt::from_bytes_be(Sign::Plus, &x);
132
133 let big_v = big_g.modpow(&big_x, &big_p);
134 let big_kv = (big_k * big_v) % &big_p;
135
136 let big_t = (big_g_b - big_kv).rem_euclid(&big_p);
137
138 let exp = big_a + big_u * big_x;
139 let big_sa = big_t.modpow(&exp, &big_p);
140
141 let k_a = sha256(&[&pad256(&big_sa.to_bytes_be().1)]);
142
143 let h_p = sha256(&[p]);
144 let h_g = sha256(&[&g_hash]);
145 let p_xg = xor32(&h_p, &h_g);
146 let m1 = sha256(&[
147 &p_xg,
148 &sha256(&[salt1]),
149 &sha256(&[salt2]),
150 &g_a,
151 &g_b,
152 &k_a,
153 ]);
154
155 Ok((m1, g_a))
156}