rsfbclient_rust/
srp.rs

1//! SRP client implementation for the firebird authentication (Wire Protocol 13)
2
3#![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    /// Srp Group used by the firebird server
15    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
29/// SRP client state before handshake with the server.
30pub struct SrpClient<'a, D: Digest> {
31    params: &'a SrpGroup,
32
33    a: BigUint,
34    a_pub: BigUint,
35
36    d: PhantomData<D>,
37}
38
39/// SRP client state after handshake with the server.
40pub struct SrpClientVerifier<D: Digest> {
41    proof: GenericArray<u8, D::OutputSize>,
42    // Firebird hashes this with SHA1 for some reason
43    key: GenericArray<u8, <Sha1 as OutputSizeUser>::OutputSize>,
44}
45
46/// Compute user private key as described in the RFC 5054. Consider using proper
47/// password hashing algorithm instead.
48pub 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    /// Create new SRP client instance.
64    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    // Firebird hashes this with SHA1 for some reason
77    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        // Because we do operation in modulo N we can get: (kv + g^b) < kv
87        let v = if *b_pub > interm {
88            (b_pub - &interm) % n
89        } else {
90            (n + b_pub - &interm) % n
91        };
92        // S = |B - kg^x| ^ (a + ux)
93        let s = powm(&v, &(&self.a + (u * x) % n), n);
94        Sha1::digest(&s.to_bytes_be())
95    }
96
97    /// Process server reply to the handshake.
98    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                // Firebird hashes this with SHA1 for some reason
108                &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        // Safeguard against malicious B
118        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        // M = H(pow(H(N), H(g)) % N | H(U) | s | A | B | K)
127        let proof = {
128            let hn = {
129                let n = &self.params.n;
130
131                // Firebird hashes this with SHA1 for some reason
132                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                // Firebird hashes this with SHA1 for some reason
138                BigUint::from_bytes_be(&Sha1::new().chain_update(g.to_bytes_be()).finalize())
139            };
140            // Firebird hashes this with SHA1 for some reason
141            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    /// Get public ephemeral value for handshaking with the server.
157    pub fn get_a_pub(&self) -> Vec<u8> {
158        self.a_pub.to_bytes_be()
159    }
160}
161
162impl<D: Digest> SrpClientVerifier<D> {
163    /// Get shared secret key without authenticating server, e.g. for using with
164    /// authenticated encryption modes. DO NOT USE this method without
165    /// some kind of secure authentication
166    pub fn get_key(self) -> GenericArray<u8, <Sha1 as OutputSizeUser>::OutputSize> {
167        self.key
168    }
169
170    /// Verification data for sending to the server.
171    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/// SRP authentication error.
195#[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/// Group used for SRP computations
213#[derive(Debug, Clone, Eq, PartialEq)]
214pub struct SrpGroup {
215    /// A large safe prime (N = 2q+1, where q is prime)
216    pub n: BigUint,
217    /// A generator modulo N
218    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    /// Compute `k` with given hash function and return SRP parameters
227    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        // Real one randomly generated
277        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        // Real ones are received from server
290        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        // Real one randomly generated
322        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        // Real ones are received from server
327        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}