1#![allow(clippy::many_single_char_names)]
4
5use std::{error, fmt, marker::PhantomData};
6
7use digest::{Digest, OutputSizeUser};
8use generic_array::GenericArray;
9use lazy_static::lazy_static;
10use num_bigint::BigUint;
11use sha1::Sha1;
12
13lazy_static! {
14 pub static ref SRP_GROUP: SrpGroup = SrpGroup {
16 n: BigUint::from_bytes_be(&[
17 230, 125, 46, 153, 75, 47, 144, 12, 63, 65, 240, 143, 91, 178, 98, 126, 208, 212, 158,
18 225, 254, 118, 122, 82, 239, 205, 86, 92, 214, 231, 104, 129, 44, 62, 30, 156, 232,
19 240, 168, 190, 166, 203, 19, 205, 41, 221, 235, 247, 169, 109, 74, 147, 181, 93, 72,
20 141, 240, 153, 161, 92, 137, 220, 176, 100, 7, 56, 235, 44, 189, 217, 168, 247, 186,
21 181, 97, 171, 27, 13, 193, 198, 205, 171, 243, 3, 38, 74, 8, 209, 188, 169, 50, 209,
22 241, 238, 66, 139, 97, 157, 151, 15, 52, 42, 186, 154, 101, 121, 59, 139, 47, 4, 26,
23 229, 54, 67, 80, 193, 111, 115, 95, 86, 236, 188, 168, 123, 213, 123, 41, 231,
24 ]),
25 g: BigUint::from_bytes_be(&[2]),
26 };
27}
28
29pub struct SrpClient<'a, D: Digest> {
31 params: &'a SrpGroup,
32
33 a: BigUint,
34 a_pub: BigUint,
35
36 d: PhantomData<D>,
37}
38
39pub struct SrpClientVerifier<D: Digest> {
41 proof: GenericArray<u8, D::OutputSize>,
42 key: GenericArray<u8, <Sha1 as OutputSizeUser>::OutputSize>,
44}
45
46pub fn srp_private_key<D: Digest>(
49 username: &[u8],
50 password: &[u8],
51 salt: &[u8],
52) -> GenericArray<u8, D::OutputSize> {
53 let p = D::new()
54 .chain_update(username)
55 .chain_update(b":")
56 .chain_update(password)
57 .finalize();
58
59 D::new().chain_update(salt).chain_update(&p).finalize()
60}
61
62impl<'a, D: Digest> SrpClient<'a, D> {
63 pub fn new(a: &[u8], params: &'a SrpGroup) -> Self {
65 let a = BigUint::from_bytes_be(a);
66 let a_pub = params.powm(&a);
67
68 Self {
69 params,
70 a,
71 a_pub,
72 d: Default::default(),
73 }
74 }
75
76 fn calc_key(
78 &self,
79 b_pub: &BigUint,
80 x: &BigUint,
81 u: &BigUint,
82 ) -> GenericArray<u8, <Sha1 as OutputSizeUser>::OutputSize> {
83 let n = &self.params.n;
84 let k = self.params.compute_k::<Sha1>();
85 let interm = (k * self.params.powm(x)) % n;
86 let v = if *b_pub > interm {
88 (b_pub - &interm) % n
89 } else {
90 (n + b_pub - &interm) % n
91 };
92 let s = powm(&v, &(&self.a + (u * x) % n), n);
94 Sha1::digest(&s.to_bytes_be())
95 }
96
97 pub fn process_reply(
99 self,
100 user: &[u8],
101 salt: &[u8],
102 private_key: &[u8],
103 b_pub: &[u8],
104 ) -> Result<SrpClientVerifier<D>, SrpAuthError> {
105 let u = {
106 BigUint::from_bytes_be(
107 &Sha1::new()
109 .chain_update(&self.a_pub.to_bytes_be())
110 .chain_update(b_pub)
111 .finalize(),
112 )
113 };
114
115 let b_pub = BigUint::from_bytes_be(b_pub);
116
117 if &b_pub % &self.params.n == BigUint::default() {
119 return Err(SrpAuthError {
120 description: "Malicious b_pub value",
121 });
122 }
123
124 let x = BigUint::from_bytes_be(private_key);
125 let key = self.calc_key(&b_pub, &x, &u);
126 let proof = {
128 let hn = {
129 let n = &self.params.n;
130
131 BigUint::from_bytes_be(&Sha1::new().chain_update(n.to_bytes_be()).finalize())
133 };
134 let hg = {
135 let g = &self.params.g;
136
137 BigUint::from_bytes_be(&Sha1::new().chain_update(g.to_bytes_be()).finalize())
139 };
140 let hu = Sha1::new().chain_update(user).finalize();
142
143 D::new()
144 .chain_update((hn.modpow(&hg, &self.params.n)).to_bytes_be())
145 .chain_update(hu)
146 .chain_update(salt)
147 .chain_update(&self.a_pub.to_bytes_be())
148 .chain_update(&b_pub.to_bytes_be())
149 .chain_update(&key)
150 .finalize()
151 };
152
153 Ok(SrpClientVerifier { proof, key })
154 }
155
156 pub fn get_a_pub(&self) -> Vec<u8> {
158 self.a_pub.to_bytes_be()
159 }
160}
161
162impl<D: Digest> SrpClientVerifier<D> {
163 pub fn get_key(self) -> GenericArray<u8, <Sha1 as OutputSizeUser>::OutputSize> {
167 self.key
168 }
169
170 pub fn get_proof(&self) -> GenericArray<u8, D::OutputSize> {
172 self.proof.clone()
173 }
174}
175
176pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
177 let zero = BigUint::from(0u32);
178 let one = BigUint::from(1u32);
179 let two = BigUint::from(2u32);
180 let mut exp = exp.clone();
181 let mut result = one.clone();
182 let mut base = base % modulus;
183
184 while exp > zero {
185 if &exp % &two == one {
186 result = (result * &base) % modulus;
187 }
188 exp >>= 1;
189 base = (&base * &base) % modulus;
190 }
191 result
192}
193
194#[derive(Debug, Copy, Clone, Eq, PartialEq)]
196pub struct SrpAuthError {
197 pub(crate) description: &'static str,
198}
199
200impl fmt::Display for SrpAuthError {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 write!(f, "SRP authentication error")
203 }
204}
205
206impl error::Error for SrpAuthError {
207 fn description(&self) -> &str {
208 self.description
209 }
210}
211
212#[derive(Debug, Clone, Eq, PartialEq)]
214pub struct SrpGroup {
215 pub n: BigUint,
217 pub g: BigUint,
219}
220
221impl SrpGroup {
222 pub(crate) fn powm(&self, v: &BigUint) -> BigUint {
223 powm(&self.g, v, &self.n)
224 }
225
226 pub(crate) fn compute_k<D: Digest>(&self) -> BigUint {
228 let n = self.n.to_bytes_be();
229 let g_bytes = self.g.to_bytes_be();
230 let mut buf = vec![0u8; n.len()];
231 let l = n.len() - g_bytes.len();
232 buf[l..].copy_from_slice(&g_bytes);
233
234 BigUint::from_bytes_be(&D::new().chain_update(&n).chain_update(&buf).finalize())
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use super::SRP_GROUP;
241 use crate::srp::{srp_private_key, SrpClient};
242 use num_bigint::BigUint;
243 use sha1::Sha1;
244 use sha2::Sha256;
245
246 #[test]
247 fn srp_group_k() {
248 use sha1::Digest;
249
250 let k = {
251 let n = SRP_GROUP.n.to_bytes_be();
252 let g_bytes = SRP_GROUP.g.to_bytes_be();
253 let mut buf = vec![0u8; n.len()];
254 let l = n.len() - g_bytes.len();
255 buf[l..].copy_from_slice(&g_bytes);
256
257 BigUint::from_bytes_be(
258 &sha1::Sha1::new()
259 .chain_update(&n)
260 .chain_update(&buf)
261 .finalize(),
262 )
263 };
264
265 assert_eq!(
266 "1277432915985975349439481660349303019122249719989",
267 &k.to_string()
268 );
269 }
270
271 #[test]
272 fn srp() {
273 let user = b"sysdba";
274 let password = b"masterkey";
275
276 let seed = b"h\xa8\x1a\x9d\xe3\xc2)F\xcc\xea02\xd9\x93'\xba\xdf=}\x9a\xdf\t6\xdc\xa3m\xde\xb7N\xf2\xd9\xda";
278
279 let cli = SrpClient::<Sha1>::new(seed, &SRP_GROUP);
280
281 assert_eq!(
282 cli.get_a_pub(),
283 BigUint::parse_bytes(
284 b"c89f2e8556d724baee8781483c1397fa7b034afcdcb35b835c0caf54d3975980d5783cf8d81f0fb4f5bda079634ab78b9d6db31b4fa8ff961b04aba693fc867a9861fba9dcf306eae7b27b66c347c7ab0c87119168b68420cd1e211121533f90802f992d77485722dce0d19662414c0b21f09b750d439a16a4c9e9b076dcec77"
285 , 16
286 ).unwrap().to_bytes_be()
287 );
288
289 let salt = b"9\xe0\xee\x06\xa9]\xbe\xa7\xe4V\x08\xb1g\xa1\x93\x19\xf6\x11\xcb@\t\xeb\x9c\xf8\xe5K_;\xd1\xeb\x0f\xde";
291 let serv_pub = BigUint::parse_bytes(
292 b"dc341bd8a8584dd0d69dda440550fb0f16c5b258f5b8fb422d5e2d92652006862cc6bb8dbd5fdd00f1744b75196a894dff7616742eb305ab1af96c39cbff4a80d088bf82c44e146cc176def524d700037608fd2c2bf193ffc59509d2cd3e1c792bfa9b623cbb3cf105b2ec0048f942f879253e0e3f26de88dd7a56e0a12d6fc",
293 16
294 ).unwrap().to_bytes_be();
295
296 let cli_priv = srp_private_key::<Sha1>(user, password, salt);
297
298 assert_eq!(
299 b"\xe7\xd1>*\xaag\x9a\xa9\"w\x17&>\xca\xff\x86+ '\xdc",
300 &cli_priv[..]
301 );
302
303 let verifier = cli.process_reply(user, salt, &cli_priv, &serv_pub).unwrap();
304
305 assert_eq!(
306 b"C~\xe6\xad\xe1\x97d\xed\xbf\x16D7\xb1C\xbf\xb1\xc9\x92\xc4@",
307 &verifier.get_proof()[..]
308 );
309
310 assert_eq!(
311 b"\xd5,\xe6(\xf6\x04\xec\xdb\xf2\xa2J\xc8zw\xb0\x9a\x87O\xe8\xf7",
312 &verifier.get_key()[..]
313 );
314 }
315
316 #[test]
317 fn srp256() {
318 let user = b"SYSDBA";
319 let password = b"masterkey";
320
321 let seed = b"`\x97U'\x03\\\xf2\xad\x19\x89\x80o\x04\x07!\x0b\xc8\x1e\xdc\x04\xe2v*V\xaf\xd5)\xdd\xda-C\x93";
323
324 let cli = SrpClient::<Sha256>::new(seed, &SRP_GROUP);
325
326 let salt = b"\x02\xe2h\x800\x00\x00\x00y\xa4x\xa7\x00\x00\x00\x02\xd1\xa6\x97\x90\x00\x00\x00&\xe1`\x1c\x00\x00\x00\x05O";
328 let serv_pub = BigUint::parse_bytes(
329 b"57bcd7d4241869e616ed54b5ab1814ca7b97b04bc269c4054a1325708a9f80821efeade02b875d2bda35c7e1e217ff7ef432c77720aa57baa250bdfbca47de56cccdfa8a6e82c74a99e4ae3db3f07f88d4b583169180fc78e70672e10746da0a27c5709e9b67fab4eaa7b426ac1cebf506d6cdaec1c1a0ade0e9e63a4a89d80a",
330 16,
331 ).unwrap().to_bytes_be();
332
333 let cli_priv = srp_private_key::<Sha1>(user, password, salt);
334
335 assert_eq!(
336 b"\xb9\xc1\xacv\x98\xb7\xbf\x90\xa5\xa2!\xb4S\xd6|\xad\x19\x91\x18\x07",
337 &cli_priv[..]
338 );
339
340 let verifier = cli.process_reply(user, salt, &cli_priv, &serv_pub).unwrap();
341
342 assert_eq!(
343 b"Fu\xc1\x80V\xc0K\x00\xcc+\x99\x16b2L\"\xc6\xf0\x8b\xb9\x0b\xeb6wAk\x03F\x9aw\x03\x08",
344 &verifier.get_proof()[..]
345 );
346
347 assert_eq!(
348 b"\xe6*\x9c\xfd\xe3\xa3\xf8t[\xca\xa0\x06\x7f\xfc\x85z\xe6(\x84\xed",
349 &verifier.get_key()[..]
350 );
351 }
352}