diff --git a/srp/src/server.rs b/srp/src/server.rs index b8e4780..cab8aa4 100644 --- a/srp/src/server.rs +++ b/srp/src/server.rs @@ -85,7 +85,8 @@ use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; use crate::utils::{ - compute_hash, compute_k, compute_m1, compute_m1_rfc5054, compute_m2, compute_u, + compute_hash, compute_k, compute_k_nopad, compute_m1, compute_m1_csrp, compute_m1_rfc5054, + compute_m2, compute_u, }; /// SRP server state @@ -126,6 +127,13 @@ impl<'a, D: Digest> SrpServer<'a, D> { (inter + self.params.g.modpow(b, &self.params.n)) % &self.params.n } + // k*v + g^b + #[must_use] + pub fn compute_b_pub_csrp(&self, b: &BigUint, k: &BigUint, v: &BigUint) -> BigUint { + let inter = k * v; + inter + self.params.g.modpow(b, &self.params.n) + } + // = (A * v^u) ^ b % N #[must_use] pub fn compute_premaster_secret( @@ -151,6 +159,16 @@ impl<'a, D: Digest> SrpServer<'a, D> { .to_bytes_be() } + #[must_use] + pub fn compute_public_ephemeral_csrp(&self, b: &[u8], v: &[u8]) -> Vec { + self.compute_b_pub_csrp( + &BigUint::from_bytes_be(b), + &compute_k_nopad::(self.params), + &BigUint::from_bytes_be(v), + ) + .to_bytes_be() + } + /// Process client reply to the handshake. /// b is a random value, /// v is the provided during initial user registration @@ -240,6 +258,56 @@ impl<'a, D: Digest> SrpServer<'a, D> { session_key: session_key.to_vec(), }) } + + /// Process client reply to the handshake according to RFC 5054. + /// b is a random value, + /// v is the provided during initial user registration + pub fn process_reply_csrp( + &self, + username: &[u8], + salt: &[u8], + b: &[u8], + v: &[u8], + a_pub: &[u8], + ) -> Result, SrpAuthError> { + let b = BigUint::from_bytes_be(b); + let v = BigUint::from_bytes_be(v); + let a_pub = BigUint::from_bytes_be(a_pub); + + let k = compute_k_nopad::(self.params); + let b_pub = self.compute_b_pub_csrp(&b, &k, &v); + + // Safeguard against malicious A + if &a_pub % &self.params.n == BigUint::default() { + return Err(SrpAuthError::IllegalParameter("a_pub".to_owned())); + } + + let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + + let premaster_secret = self + .compute_premaster_secret(&a_pub, &v, &u, &b) + .to_bytes_be(); + + let session_key = compute_hash::(&premaster_secret); + + let m1 = compute_m1_csrp::( + self.params, + username, + salt, + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + session_key.as_slice(), + ); + + let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, session_key.as_slice()); + + Ok(SrpServerVerifierRfc5054 { + m1, + m2, + key: premaster_secret, + session_key: session_key.to_vec(), + }) + } } impl SrpServerVerifier { diff --git a/srp/src/utils.rs b/srp/src/utils.rs index 8232a73..b01b294 100644 --- a/srp/src/utils.rs +++ b/srp/src/utils.rs @@ -27,6 +27,18 @@ pub fn compute_k(params: &SrpGroup) -> BigUint { BigUint::from_bytes_be(d.finalize().as_slice()) } +// k = H(N | g) +#[must_use] +pub fn compute_k_nopad(params: &SrpGroup) -> BigUint { + let n = params.n.to_bytes_be(); + let g_bytes = params.g.to_bytes_be(); + + let mut d = D::new(); + d.update(&n); + d.update(&g_bytes); + BigUint::from_bytes_be(d.finalize().as_slice()) +} + // H(N) XOR H(PAD(g)) #[must_use] pub fn compute_hash_n_xor_hash_g(params: &SrpGroup) -> Vec { @@ -45,6 +57,21 @@ pub fn compute_hash_n_xor_hash_g(params: &SrpGroup) -> Vec { .collect() } +// H(N) XOR H(g) +#[must_use] +pub fn compute_hash_n_xor_hash_g_nopad(params: &SrpGroup) -> Vec { + let n = params.n.to_bytes_be(); + let g_bytes = params.g.to_bytes_be(); + + let h_n = compute_hash::(&n).to_vec(); + let h_g = compute_hash::(&g_bytes).to_vec(); + + h_n.iter() + .zip(h_g.iter()) + .map(|(&x1, &x2)| x1 ^ x2) + .collect() +} + // M1 = H(A, B, K) this doesn't follow the spec but apparently no one does for M1 #[must_use] pub fn compute_m1(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output { @@ -82,6 +109,26 @@ pub fn compute_m1_rfc5054( d.finalize() } +// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) following CSRP legacy mode +#[must_use] +pub fn compute_m1_csrp( + params: &SrpGroup, + username: &[u8], + salt: &[u8], + a_pub: &[u8], + b_pub: &[u8], + key: &[u8], +) -> Output { + let mut d = D::new(); + d.update(compute_hash_n_xor_hash_g_nopad::(params)); + d.update(compute_hash::(username)); + d.update(salt); + d.update(a_pub); + d.update(b_pub); + d.update(key); + d.finalize() +} + // M2 = H(A, M1, K) #[must_use] pub fn compute_m2(a_pub: &[u8], m1: &Output, key: &[u8]) -> Output {