Skip to content

Commit 1ef058c

Browse files
committed
CSRP implementation option
1 parent 0fd3bc5 commit 1ef058c

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

srp/src/server.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ use subtle::ConstantTimeEq;
8585

8686
use crate::types::{SrpAuthError, SrpGroup};
8787
use crate::utils::{
88-
compute_hash, compute_k, compute_m1, compute_m1_rfc5054, compute_m2, compute_u,
88+
compute_hash, compute_k, compute_k_nopad, compute_m1, compute_m1_csrp, compute_m1_rfc5054,
89+
compute_m2, compute_u,
8990
};
9091

9192
/// SRP server state
@@ -126,6 +127,13 @@ impl<'a, D: Digest> SrpServer<'a, D> {
126127
(inter + self.params.g.modpow(b, &self.params.n)) % &self.params.n
127128
}
128129

130+
// k*v + g^b
131+
#[must_use]
132+
pub fn compute_b_pub_csrp(&self, b: &BigUint, k: &BigUint, v: &BigUint) -> BigUint {
133+
let inter = k * v;
134+
inter + self.params.g.modpow(b, &self.params.n)
135+
}
136+
129137
// <premaster secret> = (A * v^u) ^ b % N
130138
#[must_use]
131139
pub fn compute_premaster_secret(
@@ -151,6 +159,16 @@ impl<'a, D: Digest> SrpServer<'a, D> {
151159
.to_bytes_be()
152160
}
153161

162+
#[must_use]
163+
pub fn compute_public_ephemeral_csrp(&self, b: &[u8], v: &[u8]) -> Vec<u8> {
164+
self.compute_b_pub_csrp(
165+
&BigUint::from_bytes_be(b),
166+
&compute_k_nopad::<D>(self.params),
167+
&BigUint::from_bytes_be(v),
168+
)
169+
.to_bytes_be()
170+
}
171+
154172
/// Process client reply to the handshake.
155173
/// b is a random value,
156174
/// v is the provided during initial user registration
@@ -240,6 +258,56 @@ impl<'a, D: Digest> SrpServer<'a, D> {
240258
session_key: session_key.to_vec(),
241259
})
242260
}
261+
262+
/// Process client reply to the handshake according to RFC 5054.
263+
/// b is a random value,
264+
/// v is the provided during initial user registration
265+
pub fn process_reply_csrp(
266+
&self,
267+
username: &[u8],
268+
salt: &[u8],
269+
b: &[u8],
270+
v: &[u8],
271+
a_pub: &[u8],
272+
) -> Result<SrpServerVerifierRfc5054<D>, SrpAuthError> {
273+
let b = BigUint::from_bytes_be(b);
274+
let v = BigUint::from_bytes_be(v);
275+
let a_pub = BigUint::from_bytes_be(a_pub);
276+
277+
let k = compute_k_nopad::<D>(self.params);
278+
let b_pub = self.compute_b_pub_csrp(&b, &k, &v);
279+
280+
// Safeguard against malicious A
281+
if &a_pub % &self.params.n == BigUint::default() {
282+
return Err(SrpAuthError::IllegalParameter("a_pub".to_owned()));
283+
}
284+
285+
let u = compute_u::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be());
286+
287+
let premaster_secret = self
288+
.compute_premaster_secret(&a_pub, &v, &u, &b)
289+
.to_bytes_be();
290+
291+
let session_key = compute_hash::<D>(&premaster_secret);
292+
293+
let m1 = compute_m1_csrp::<D>(
294+
self.params,
295+
username,
296+
salt,
297+
&a_pub.to_bytes_be(),
298+
&b_pub.to_bytes_be(),
299+
session_key.as_slice(),
300+
);
301+
302+
let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, session_key.as_slice());
303+
304+
Ok(SrpServerVerifierRfc5054 {
305+
m1,
306+
m2,
307+
key: premaster_secret,
308+
session_key: session_key.to_vec(),
309+
})
310+
}
243311
}
244312

245313
impl<D: Digest> SrpServerVerifier<D> {

srp/src/utils.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ pub fn compute_k<D: Digest>(params: &SrpGroup) -> BigUint {
2727
BigUint::from_bytes_be(d.finalize().as_slice())
2828
}
2929

30+
// k = H(N | g)
31+
#[must_use]
32+
pub fn compute_k_nopad<D: Digest>(params: &SrpGroup) -> BigUint {
33+
let n = params.n.to_bytes_be();
34+
let g_bytes = params.g.to_bytes_be();
35+
36+
let mut d = D::new();
37+
d.update(&n);
38+
d.update(&g_bytes);
39+
BigUint::from_bytes_be(d.finalize().as_slice())
40+
}
41+
3042
// H(N) XOR H(PAD(g))
3143
#[must_use]
3244
pub fn compute_hash_n_xor_hash_g<D: Digest>(params: &SrpGroup) -> Vec<u8> {
@@ -45,6 +57,21 @@ pub fn compute_hash_n_xor_hash_g<D: Digest>(params: &SrpGroup) -> Vec<u8> {
4557
.collect()
4658
}
4759

60+
// H(N) XOR H(g)
61+
#[must_use]
62+
pub fn compute_hash_n_xor_hash_g_nopad<D: Digest>(params: &SrpGroup) -> Vec<u8> {
63+
let n = params.n.to_bytes_be();
64+
let g_bytes = params.g.to_bytes_be();
65+
66+
let h_n = compute_hash::<D>(&n).to_vec();
67+
let h_g = compute_hash::<D>(&g_bytes).to_vec();
68+
69+
h_n.iter()
70+
.zip(h_g.iter())
71+
.map(|(&x1, &x2)| x1 ^ x2)
72+
.collect()
73+
}
74+
4875
// M1 = H(A, B, K) this doesn't follow the spec but apparently no one does for M1
4976
#[must_use]
5077
pub fn compute_m1<D: Digest>(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output<D> {
@@ -82,6 +109,26 @@ pub fn compute_m1_rfc5054<D: Digest>(
82109
d.finalize()
83110
}
84111

112+
// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) following CSRP legacy mode
113+
#[must_use]
114+
pub fn compute_m1_csrp<D: Digest>(
115+
params: &SrpGroup,
116+
username: &[u8],
117+
salt: &[u8],
118+
a_pub: &[u8],
119+
b_pub: &[u8],
120+
key: &[u8],
121+
) -> Output<D> {
122+
let mut d = D::new();
123+
d.update(compute_hash_n_xor_hash_g_nopad::<D>(params));
124+
d.update(compute_hash::<D>(username));
125+
d.update(salt);
126+
d.update(a_pub);
127+
d.update(b_pub);
128+
d.update(key);
129+
d.finalize()
130+
}
131+
85132
// M2 = H(A, M1, K)
86133
#[must_use]
87134
pub fn compute_m2<D: Digest>(a_pub: &[u8], m1: &Output<D>, key: &[u8]) -> Output<D> {

0 commit comments

Comments
 (0)