From f01e9b962f13451e9a338e4d29c5161b7eaa5b12 Mon Sep 17 00:00:00 2001 From: Masih Yeganeh Date: Fri, 15 Sep 2023 21:25:07 +0330 Subject: [PATCH 1/5] Implemented standard implementation option and also added an option to omit username when calculating X --- srp/src/client.rs | 65 ++++++++++++--- srp/src/utils.rs | 46 +++++++++- srp/tests/rfc5054_standard.rs | 152 ++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 srp/tests/rfc5054_standard.rs diff --git a/srp/src/client.rs b/srp/src/client.rs index 53f904d..c255d80 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -82,6 +82,17 @@ //! let pwd_verifier = client.compute_verifier(username, password, salt); //! send_registration_data(username, salt, &pwd_verifier); //! ``` +//! +//! +//! Alternatively, you can instantiate the client with options to use the implementation +//! from the specification of SRP (that generates M1 differently) or omit the username +//! when calculating X to be able to authenticate the same users with their different IDs. +//! +//! ```rust +//! let by_the_spec = true; +//! let no_user_in_x = true; +//! let client = crate::srp::client::SrpClient::::new_with_options(&crate::srp::groups::G_2048, by_the_spec, no_user_in_x); +//! ``` use std::marker::PhantomData; @@ -90,11 +101,13 @@ use num_bigint::BigUint; use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; -use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; +use crate::utils::{compute_k, compute_m1, compute_m1_std, compute_m2, compute_u}; /// SRP client state before handshake with the server. pub struct SrpClient<'a, D: Digest> { params: &'a SrpGroup, + by_the_spec: bool, + no_user_in_x: bool, d: PhantomData, } @@ -111,6 +124,21 @@ impl<'a, D: Digest> SrpClient<'a, D> { pub const fn new(params: &'a SrpGroup) -> Self { Self { params, + by_the_spec: false, + no_user_in_x: false, + d: PhantomData, + } + } + + pub const fn new_with_options( + params: &'a SrpGroup, + by_the_spec: bool, + no_user_in_x: bool, + ) -> Self { + Self { + params, + by_the_spec, + no_user_in_x, d: PhantomData, } } @@ -170,6 +198,7 @@ impl<'a, D: Digest> SrpClient<'a, D> { /// Get password verifier (v in RFC5054) for user registration on the server. #[must_use] pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec { + let username = if self.no_user_in_x { &[] } else { username }; let identity_hash = Self::compute_identity_hash(username, password); let x = Self::compute_x(identity_hash.as_slice(), salt); self.compute_v(&x).to_bytes_be() @@ -205,24 +234,36 @@ impl<'a, D: Digest> SrpClient<'a, D> { let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); let k = compute_k::(self.params); + let username = if self.no_user_in_x { &[] } else { username }; let identity_hash = Self::compute_identity_hash(username, password); let x = Self::compute_x(identity_hash.as_slice(), salt); let key = self.compute_premaster_secret(&b_pub, &k, &x, &a, &u); - let m1 = compute_m1::( - &a_pub.to_bytes_be(), - &b_pub.to_bytes_be(), - &key.to_bytes_be(), - ); + let key = if self.by_the_spec { + let mut u = D::new(); + u.update(key.to_bytes_be()); + u.finalize().to_vec() + } else { + key.to_bytes_be() + }; + + let m1 = if self.by_the_spec { + compute_m1_std::( + &self.params, + username, + salt, + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + key.as_slice(), + ) + } else { + compute_m1::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be(), key.as_slice()) + }; - let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be()); + let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, key.as_slice()); - Ok(SrpClientVerifier { - m1, - m2, - key: key.to_bytes_be(), - }) + Ok(SrpClientVerifier { m1, m2, key }) } } diff --git a/srp/src/utils.rs b/srp/src/utils.rs index 0d77129..d74907e 100644 --- a/srp/src/utils.rs +++ b/srp/src/utils.rs @@ -27,8 +27,25 @@ pub fn compute_k(params: &SrpGroup) -> BigUint { 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 { + let n = params.n.to_bytes_be(); + let g_bytes = params.g.to_bytes_be(); + let mut buf = vec![0u8; n.len()]; + let l = n.len() - g_bytes.len(); + buf[l..].copy_from_slice(&g_bytes); + + let h_n = compute_hash::(&n).to_vec(); + let h_g = compute_hash::(&buf).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 -// M1 should equal = H(H(N) XOR H(g) | H(U) | s | A | B | K) according to the spec #[must_use] pub fn compute_m1(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output { let mut d = D::new(); @@ -38,6 +55,33 @@ pub fn compute_m1(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output(data: &[u8]) -> Output { + let mut d = D::new(); + d.update(data); + d.finalize() +} + +// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) this follows the spec +#[must_use] +pub fn compute_m1_std( + 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::(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 { diff --git a/srp/tests/rfc5054_standard.rs b/srp/tests/rfc5054_standard.rs new file mode 100644 index 0000000..f9f68d5 --- /dev/null +++ b/srp/tests/rfc5054_standard.rs @@ -0,0 +1,152 @@ +use hex_literal::hex; +use num_bigint::BigUint; +use sha2::Sha256; +use srp::client::SrpClient; +use srp::groups::G_2048; +use srp::server::SrpServer; +use srp::utils::{compute_hash, compute_k, compute_u}; + +#[test] +#[allow(clippy::many_single_char_names)] +fn rfc5054_standard() { + let i = b""; + let p = hex!("CCAF1BCA 820E8E6B 392C0EBA 7014CC9C DF49A650 84B39A53 834CC090 92BCDA20"); + let s = hex!("BC01D972 31E5A4BC 79171C6D 83783FF2"); + let group = &G_2048; + + let k = compute_k::(group); + + assert_eq!( + k.to_bytes_be(), + hex!("05B9E8EF 059C6B32 EA59FC1D 322D37F0 4AA30BAE 5AA9003B 8321E21D DB04E300"), + "bad k value" + ); + + let identity_hash = SrpClient::::compute_identity_hash(i, &p); + let x = SrpClient::::compute_x(identity_hash.as_slice(), &s); + + assert_eq!( + x.to_bytes_be(), + hex!("FD1709C7 30244792 F33348CB FBBD4AB4 39AC8090 FCDDC474 46244073 0D85ADDB"), + "bad x value" + ); + + let client = SrpClient::::new_with_options(group, true, true); + let v = client.compute_v(&x); + + assert_eq!( + v.to_bytes_be(), + hex!( + " + A2E59E34 4EC9AB6D 611BFC12 4A2E5DC7 46174702 9AC44A6F 6A8DB9E2 + 7326A5CB E370C469 A20D59CD 63FA13E4 1F0F1968 61A0AA3A 778AB5F5 + 2A0D57E9 BC3E9494 7ACDA1BD 3E62785D DB51FCE1 D2A34C87 E95CAD5A + 30731035 269E72AF 235E4537 62F94011 C965E1D3 F940A196 43B56810 + D7CD38AE 4DBB7CFF 80E529FE E33CBB88 C7877096 62342D98 314687BF + 0A5B0AE2 E6595B9A DC61B1BE 691E3176 62A01A24 BE963C70 8565694F + 575DCEA2 791364C7 465B1BD4 6D8BC9F7 53F3F6E5 C55491F2 080D00D5 + 40F6E247 53AFB477 C33BC117 A0D6551A 16026D96 22F3AD50 6C379EFD + 85A075E7 C6D0DA46 442D6084 7095D43A 3E3C5EC2 6F523479 B3F2902B + 641A7B92 + " + ), + "bad v value" + ); + + let a = BigUint::from_bytes_be(&hex!( + " + 81010101 01010101 01010101 01010101 01010101 01010101 01010101 + 01010101 + " + )); + + let b = BigUint::from_bytes_be(&hex!( + " + E487CB59 D31AC550 471E81F0 0F6928E0 1DDA08E9 74A004F4 9E61F5D1 + 05284D20 + " + )); + + let a_pub = client.compute_a_pub(&a); + + assert_eq!( + a_pub.to_bytes_be(), + hex!( + " + 01F9D75A 9DF8AC07 FB3684C4 DDF9ABD9 4CC03C10 1A381976 F16C92B5 + 8083BB98 4137AD44 7D815819 529E0313 FCC4EDD4 5F31D033 CBC0FB4B + F0CBB75A B0A2A10C 4C0C1C23 A62BA798 AB308818 C94F017C 2015BD3A + 4B2334B0 E2125F57 E12A2D31 936856B2 7BE1A615 8D32FC65 48A6B4BB + 62E63A13 8EF89664 CC1F43E7 457DE565 E1551F34 29A4B73A 7FAB0D9D + 821EF749 7A8A1D84 D637FA8E 443F57CE AF12D0B0 54A67726 3D7C15A4 + C88D87B9 136684BA 4AAB3466 524D9A47 30FBE924 1194B3E4 E61EB9A7 + 67401AA3 E2D66AD0 B07CFF63 B41CD665 E0EC8BC2 75D16A49 E6ECAC4F + 2BB3AF76 BC2CBA64 83B665A6 CA804DB8 5093B091 77114E70 9DD8DFB1 + 7A8B98CD + " + ), + "bad a_pub value" + ); + + let server = SrpServer::::new(group); + let b_pub = server.compute_b_pub(&b, &k, &v); + + assert_eq!( + b_pub.to_bytes_be(), + hex!( + " + 7F75618C 8C3EC5D7 CDD11D6A C2C24157 0D3254FA 39CFF9C0 DBDD39BC + B6161B2D 12FEE512 0814D17C 6CD56E37 EC9AFC86 8213C60F 672CA6D7 + 436AEE09 11F59AC6 30DAE4F0 70B15E84 86200B1B 0163900D 2EBB612E + 963F1AC8 E083F70F E5484F83 559C11A2 C1936C79 361FCCA7 E9B21192 + 14416D3F 487C3874 A76D3B23 29A0690B DA774225 104D06B9 418D6207 + 75D64CE8 54004E07 50F64299 D13B5AA2 3AEBF69B 56814E17 EBC1784B + 1881E6BE 651DEABE 2C6E78DE 0A84C032 0DE266B4 1444DC2F 397F0436 + 8B62BDDE F573D274 EA304F40 B7CDB74A 345AD036 C5ED746F B3F7D627 + 597FC9C6 A453BF05 D11185E0 EBB74797 D4490903 2D9749F7 8AB8ED63 + FCBFCA58 + " + ), + "bad b_pub value" + ); + + let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + + assert_eq!( + u.to_bytes_be(), + hex!("6B527E30 667D330D 84874755 1E17E271 BA465393 CA48264C D37E59DF 18267B37"), + "bad u value" + ); + + assert_eq!( + compute_hash::( + &client + .compute_premaster_secret(&b_pub, &k, &x, &a, &u) + .to_bytes_be() + ) + .as_slice(), + hex!( + " + CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6 + EE706231 + " + ), + "bad client premaster" + ); + + assert_eq!( + compute_hash::( + &server + .compute_premaster_secret(&a_pub, &v, &u, &b) + .to_bytes_be() + ) + .as_slice(), + hex!( + " + CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6 + EE706231 + " + ), + "bad server premaster" + ); +} From f4ffa0f6372724eedff170e332315ef89a3c4832 Mon Sep 17 00:00:00 2001 From: Masih Yeganeh Date: Fri, 15 Sep 2023 21:31:27 +0330 Subject: [PATCH 2/5] Make clippy happy --- srp/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srp/src/client.rs b/srp/src/client.rs index c255d80..ce35407 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -250,7 +250,7 @@ impl<'a, D: Digest> SrpClient<'a, D> { let m1 = if self.by_the_spec { compute_m1_std::( - &self.params, + self.params, username, salt, &a_pub.to_bytes_be(), From 15cb64113f9110d180ce47fd17815ec3b5983bb0 Mon Sep 17 00:00:00 2001 From: Masih Yeganeh Date: Thu, 21 Sep 2023 12:37:21 +0330 Subject: [PATCH 3/5] Alternate implementation --- srp/src/client.rs | 138 ++++++++++++++++++++---------- srp/src/server.rs | 85 ++++++++++++++++++- srp/src/utils.rs | 4 +- srp/tests/rfc5054_standard.rs | 152 ---------------------------------- srp/tests/srp.rs | 82 ++++++++++++++++++ 5 files changed, 264 insertions(+), 197 deletions(-) delete mode 100644 srp/tests/rfc5054_standard.rs diff --git a/srp/src/client.rs b/srp/src/client.rs index ce35407..238f313 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -101,13 +101,13 @@ use num_bigint::BigUint; use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; -use crate::utils::{compute_k, compute_m1, compute_m1_std, compute_m2, compute_u}; +use crate::utils::{ + compute_hash, compute_k, compute_m1, compute_m1_rfc5054, compute_m2, compute_u, +}; /// SRP client state before handshake with the server. pub struct SrpClient<'a, D: Digest> { params: &'a SrpGroup, - by_the_spec: bool, - no_user_in_x: bool, d: PhantomData, } @@ -118,27 +118,20 @@ pub struct SrpClientVerifier { key: Vec, } +/// RFC 5054 SRP client state after handshake with the server. +pub struct SrpClientVerifierRfc5054 { + m1: Output, + m2: Output, + premaster_secret: Vec, + session_key: Vec, +} + impl<'a, D: Digest> SrpClient<'a, D> { /// Create new SRP client instance. #[must_use] pub const fn new(params: &'a SrpGroup) -> Self { Self { params, - by_the_spec: false, - no_user_in_x: false, - d: PhantomData, - } - } - - pub const fn new_with_options( - params: &'a SrpGroup, - by_the_spec: bool, - no_user_in_x: bool, - ) -> Self { - Self { - params, - by_the_spec, - no_user_in_x, d: PhantomData, } } @@ -198,7 +191,6 @@ impl<'a, D: Digest> SrpClient<'a, D> { /// Get password verifier (v in RFC5054) for user registration on the server. #[must_use] pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec { - let username = if self.no_user_in_x { &[] } else { username }; let identity_hash = Self::compute_identity_hash(username, password); let x = Self::compute_x(identity_hash.as_slice(), salt); self.compute_v(&x).to_bytes_be() @@ -234,36 +226,75 @@ impl<'a, D: Digest> SrpClient<'a, D> { let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); let k = compute_k::(self.params); - let username = if self.no_user_in_x { &[] } else { username }; let identity_hash = Self::compute_identity_hash(username, password); let x = Self::compute_x(identity_hash.as_slice(), salt); let key = self.compute_premaster_secret(&b_pub, &k, &x, &a, &u); - let key = if self.by_the_spec { - let mut u = D::new(); - u.update(key.to_bytes_be()); - u.finalize().to_vec() - } else { - key.to_bytes_be() - }; - - let m1 = if self.by_the_spec { - compute_m1_std::( - self.params, - username, - salt, - &a_pub.to_bytes_be(), - &b_pub.to_bytes_be(), - key.as_slice(), - ) - } else { - compute_m1::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be(), key.as_slice()) - }; + let m1 = compute_m1::( + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + &key.to_bytes_be(), + ); + + let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be()); + + Ok(SrpClientVerifier { + m1, + m2, + key: key.to_bytes_be(), + }) + } + + /// Process server reply to the handshake according to RFC 5054. + /// `a` is a random value, + /// `username`, `password` is supplied by the user + /// `salt` and `b_pub` come from the server + pub fn process_reply_rfc5054( + &self, + a: &[u8], + username: &[u8], + password: &[u8], + salt: &[u8], + b_pub: &[u8], + ) -> Result, SrpAuthError> { + let a = BigUint::from_bytes_be(a); + let a_pub = self.compute_a_pub(&a); + let b_pub = BigUint::from_bytes_be(b_pub); + + // Safeguard against malicious B + if &b_pub % &self.params.n == BigUint::default() { + return Err(SrpAuthError::IllegalParameter("b_pub".to_owned())); + } - let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, key.as_slice()); + let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + let k = compute_k::(self.params); + let identity_hash = Self::compute_identity_hash(username, password); + let x = Self::compute_x(identity_hash.as_slice(), salt); - Ok(SrpClientVerifier { m1, m2, key }) + let premaster_secret = self + .compute_premaster_secret(&b_pub, &k, &x, &a, &u) + .to_bytes_be(); + + let session_key = compute_hash::(&premaster_secret); + + let m1 = compute_m1_rfc5054::( + 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(SrpClientVerifierRfc5054 { + m1, + m2, + premaster_secret, + session_key: session_key.to_vec(), + }) } } @@ -289,3 +320,26 @@ impl SrpClientVerifier { } } } + +impl SrpClientVerifierRfc5054 { + /// Get shared secret key without authenticating server, e.g. for using with + /// authenticated encryption modes. DO NOT USE this method without + /// some kind of secure authentication + pub fn premaster_secret(&self) -> &[u8] { + &self.premaster_secret + } + + /// Verification data for sending to the server. + pub fn proof(&self) -> &[u8] { + self.m1.as_slice() + } + + /// Verify server reply to verification data and return shared session key. + pub fn verify_server(&self, reply: &[u8]) -> Result<&[u8], SrpAuthError> { + if self.m2.ct_eq(reply).unwrap_u8() == 1 { + Ok(self.session_key.as_slice()) + } else { + Err(SrpAuthError::BadRecordMac("server".to_owned())) + } + } +} diff --git a/srp/src/server.rs b/srp/src/server.rs index be490a7..9de4f85 100644 --- a/srp/src/server.rs +++ b/srp/src/server.rs @@ -66,7 +66,9 @@ use num_bigint::BigUint; use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; -use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; +use crate::utils::{ + compute_hash, compute_k, compute_m1, compute_m1_rfc5054, compute_m2, compute_u, +}; /// SRP server state pub struct SrpServer<'a, D: Digest> { @@ -81,6 +83,14 @@ pub struct SrpServerVerifier { key: Vec, } +/// RFC 5054 SRP server state after handshake with the client. +pub struct SrpServerVerifierRfc5054 { + m1: Output, + m2: Output, + premaster_secret: Vec, + session_key: Vec, +} + impl<'a, D: Digest> SrpServer<'a, D> { /// Create new server state. #[must_use] @@ -162,6 +172,56 @@ impl<'a, D: Digest> SrpServer<'a, D> { key: key.to_bytes_be(), }) } + + /// 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_rfc5054( + &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::(self.params); + let b_pub = self.compute_b_pub(&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_rfc5054::( + 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, + premaster_secret, + session_key: session_key.to_vec(), + }) + } } impl SrpServerVerifier { @@ -186,3 +246,26 @@ impl SrpServerVerifier { } } } + +impl SrpServerVerifierRfc5054 { + /// Get shared secret between user and the server. (do not forget to verify + /// that keys are the same!) + pub fn premaster_secret(&self) -> &[u8] { + &self.premaster_secret + } + + /// Verification data for sending to the client. + pub fn proof(&self) -> &[u8] { + // TODO not Output + self.m2.as_slice() + } + + /// Process user proof of having the same shared secret and return shared session key. + pub fn verify_client(&self, reply: &[u8]) -> Result<&[u8], SrpAuthError> { + if self.m1.ct_eq(reply).unwrap_u8() == 1 { + Ok(self.session_key.as_slice()) + } else { + Err(SrpAuthError::BadRecordMac("client".to_owned())) + } + } +} diff --git a/srp/src/utils.rs b/srp/src/utils.rs index d74907e..8232a73 100644 --- a/srp/src/utils.rs +++ b/srp/src/utils.rs @@ -62,9 +62,9 @@ pub fn compute_hash(data: &[u8]) -> Output { d.finalize() } -// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) this follows the spec +// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) following RFC5054 #[must_use] -pub fn compute_m1_std( +pub fn compute_m1_rfc5054( params: &SrpGroup, username: &[u8], salt: &[u8], diff --git a/srp/tests/rfc5054_standard.rs b/srp/tests/rfc5054_standard.rs deleted file mode 100644 index f9f68d5..0000000 --- a/srp/tests/rfc5054_standard.rs +++ /dev/null @@ -1,152 +0,0 @@ -use hex_literal::hex; -use num_bigint::BigUint; -use sha2::Sha256; -use srp::client::SrpClient; -use srp::groups::G_2048; -use srp::server::SrpServer; -use srp::utils::{compute_hash, compute_k, compute_u}; - -#[test] -#[allow(clippy::many_single_char_names)] -fn rfc5054_standard() { - let i = b""; - let p = hex!("CCAF1BCA 820E8E6B 392C0EBA 7014CC9C DF49A650 84B39A53 834CC090 92BCDA20"); - let s = hex!("BC01D972 31E5A4BC 79171C6D 83783FF2"); - let group = &G_2048; - - let k = compute_k::(group); - - assert_eq!( - k.to_bytes_be(), - hex!("05B9E8EF 059C6B32 EA59FC1D 322D37F0 4AA30BAE 5AA9003B 8321E21D DB04E300"), - "bad k value" - ); - - let identity_hash = SrpClient::::compute_identity_hash(i, &p); - let x = SrpClient::::compute_x(identity_hash.as_slice(), &s); - - assert_eq!( - x.to_bytes_be(), - hex!("FD1709C7 30244792 F33348CB FBBD4AB4 39AC8090 FCDDC474 46244073 0D85ADDB"), - "bad x value" - ); - - let client = SrpClient::::new_with_options(group, true, true); - let v = client.compute_v(&x); - - assert_eq!( - v.to_bytes_be(), - hex!( - " - A2E59E34 4EC9AB6D 611BFC12 4A2E5DC7 46174702 9AC44A6F 6A8DB9E2 - 7326A5CB E370C469 A20D59CD 63FA13E4 1F0F1968 61A0AA3A 778AB5F5 - 2A0D57E9 BC3E9494 7ACDA1BD 3E62785D DB51FCE1 D2A34C87 E95CAD5A - 30731035 269E72AF 235E4537 62F94011 C965E1D3 F940A196 43B56810 - D7CD38AE 4DBB7CFF 80E529FE E33CBB88 C7877096 62342D98 314687BF - 0A5B0AE2 E6595B9A DC61B1BE 691E3176 62A01A24 BE963C70 8565694F - 575DCEA2 791364C7 465B1BD4 6D8BC9F7 53F3F6E5 C55491F2 080D00D5 - 40F6E247 53AFB477 C33BC117 A0D6551A 16026D96 22F3AD50 6C379EFD - 85A075E7 C6D0DA46 442D6084 7095D43A 3E3C5EC2 6F523479 B3F2902B - 641A7B92 - " - ), - "bad v value" - ); - - let a = BigUint::from_bytes_be(&hex!( - " - 81010101 01010101 01010101 01010101 01010101 01010101 01010101 - 01010101 - " - )); - - let b = BigUint::from_bytes_be(&hex!( - " - E487CB59 D31AC550 471E81F0 0F6928E0 1DDA08E9 74A004F4 9E61F5D1 - 05284D20 - " - )); - - let a_pub = client.compute_a_pub(&a); - - assert_eq!( - a_pub.to_bytes_be(), - hex!( - " - 01F9D75A 9DF8AC07 FB3684C4 DDF9ABD9 4CC03C10 1A381976 F16C92B5 - 8083BB98 4137AD44 7D815819 529E0313 FCC4EDD4 5F31D033 CBC0FB4B - F0CBB75A B0A2A10C 4C0C1C23 A62BA798 AB308818 C94F017C 2015BD3A - 4B2334B0 E2125F57 E12A2D31 936856B2 7BE1A615 8D32FC65 48A6B4BB - 62E63A13 8EF89664 CC1F43E7 457DE565 E1551F34 29A4B73A 7FAB0D9D - 821EF749 7A8A1D84 D637FA8E 443F57CE AF12D0B0 54A67726 3D7C15A4 - C88D87B9 136684BA 4AAB3466 524D9A47 30FBE924 1194B3E4 E61EB9A7 - 67401AA3 E2D66AD0 B07CFF63 B41CD665 E0EC8BC2 75D16A49 E6ECAC4F - 2BB3AF76 BC2CBA64 83B665A6 CA804DB8 5093B091 77114E70 9DD8DFB1 - 7A8B98CD - " - ), - "bad a_pub value" - ); - - let server = SrpServer::::new(group); - let b_pub = server.compute_b_pub(&b, &k, &v); - - assert_eq!( - b_pub.to_bytes_be(), - hex!( - " - 7F75618C 8C3EC5D7 CDD11D6A C2C24157 0D3254FA 39CFF9C0 DBDD39BC - B6161B2D 12FEE512 0814D17C 6CD56E37 EC9AFC86 8213C60F 672CA6D7 - 436AEE09 11F59AC6 30DAE4F0 70B15E84 86200B1B 0163900D 2EBB612E - 963F1AC8 E083F70F E5484F83 559C11A2 C1936C79 361FCCA7 E9B21192 - 14416D3F 487C3874 A76D3B23 29A0690B DA774225 104D06B9 418D6207 - 75D64CE8 54004E07 50F64299 D13B5AA2 3AEBF69B 56814E17 EBC1784B - 1881E6BE 651DEABE 2C6E78DE 0A84C032 0DE266B4 1444DC2F 397F0436 - 8B62BDDE F573D274 EA304F40 B7CDB74A 345AD036 C5ED746F B3F7D627 - 597FC9C6 A453BF05 D11185E0 EBB74797 D4490903 2D9749F7 8AB8ED63 - FCBFCA58 - " - ), - "bad b_pub value" - ); - - let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); - - assert_eq!( - u.to_bytes_be(), - hex!("6B527E30 667D330D 84874755 1E17E271 BA465393 CA48264C D37E59DF 18267B37"), - "bad u value" - ); - - assert_eq!( - compute_hash::( - &client - .compute_premaster_secret(&b_pub, &k, &x, &a, &u) - .to_bytes_be() - ) - .as_slice(), - hex!( - " - CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6 - EE706231 - " - ), - "bad client premaster" - ); - - assert_eq!( - compute_hash::( - &server - .compute_premaster_secret(&a_pub, &v, &u, &b) - .to_bytes_be() - ) - .as_slice(), - hex!( - " - CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6 - EE706231 - " - ), - "bad server premaster" - ); -} diff --git a/srp/tests/srp.rs b/srp/tests/srp.rs index 1cedd67..01cc713 100644 --- a/srp/tests/srp.rs +++ b/srp/tests/srp.rs @@ -68,6 +68,77 @@ fn auth_test(true_pwd: &[u8], auth_pwd: &[u8]) { ); } +fn auth_test_rfc5054(true_pwd: &[u8], auth_pwd: &[u8]) { + let mut rng = rand::rngs::OsRng; + let username = b"alice"; + + // Client instance creation + let client = SrpClient::::new(&G_2048); + + // Begin Registration + + let mut salt = [0u8; 16]; + rng.fill_bytes(&mut salt); + let verifier = client.compute_verifier(username, true_pwd, &salt); + + // Client sends username and verifier and salt to the Server for storage + + // Registration Ends + + // Begin Authentication + + // User sends username + + // Server instance creation + let server = SrpServer::::new(&G_2048); + + // Server retrieves verifier, salt and computes a public B value + let mut b = [0u8; 64]; + rng.fill_bytes(&mut b); + let (salt, b_pub) = (&salt, server.compute_public_ephemeral(&b, &verifier)); + + // Server sends salt and b_pub to client + + // Client computes the public A value and the clientVerifier containing the key, m1, and m2 + let mut a = [0u8; 64]; + rng.fill_bytes(&mut a); + let client_verifier = client + .process_reply_rfc5054(&a, username, auth_pwd, salt, &b_pub) + .unwrap(); + let a_pub = client.compute_public_ephemeral(&a); + let client_proof = client_verifier.proof(); + + // Client sends a_pub and client_proof to server (M1) + + // Server processes verification data + let server_verifier = server + .process_reply_rfc5054(username, salt, &b, &verifier, &a_pub) + .unwrap(); + println!("Client verification on server"); + let server_session_key = server_verifier.verify_client(client_proof).unwrap(); + let server_proof = server_verifier.proof(); + let server_shared_secret = server_verifier.premaster_secret(); + + // Server sends server_proof to server (M2) + + // Client verifies server + println!("Server verification on client"); + let client_session_key = client_verifier.verify_server(server_proof).unwrap(); + let client_shared_secret = client_verifier.premaster_secret(); + + // our shared secrets almost must equal but just an extra check + assert_eq!( + server_shared_secret, client_shared_secret, + "server and client shared secrets are not equal" + ); + + // our session keys almost must equal but just an extra check + assert_eq!( + server_session_key, client_session_key, + "server and client session keys are not equal" + ); +} + #[test] fn good_password() { auth_test(b"password", b"password"); @@ -78,3 +149,14 @@ fn good_password() { fn bad_password() { auth_test(b"password", b"paSsword"); } + +#[test] +fn rfc5054_good_password() { + auth_test_rfc5054(b"password", b"password"); +} + +#[test] +#[should_panic] +fn rfc5054_bad_password() { + auth_test_rfc5054(b"password", b"paSsword"); +} From 6cd45bb6a8693b8558545477727762576df0cd44 Mon Sep 17 00:00:00 2001 From: Masih Yeganeh Date: Thu, 21 Sep 2023 12:58:55 +0330 Subject: [PATCH 4/5] Fix documentation and unify some names --- srp/src/client.rs | 36 +++++++++++++++++++++--------------- srp/src/server.rs | 28 +++++++++++++++++++++++----- srp/tests/srp.rs | 4 ++-- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/srp/src/client.rs b/srp/src/client.rs index 238f313..33e36b9 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -64,7 +64,23 @@ //! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap(); //! //! verifier.key(); -//!``` +//! ``` +//! +//! +//! Alternatively, you can use `process_reply_rfc5054` method to process parameters +//! according to RFC 5054 if the server is using it. This way, it generates M1 and +//! M2 differently and also the `verify_server` method will return a shared session +//! key in case of correct server data. +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply_rfc5054(b"", b"", b"", b"", b"1").unwrap(); +//! # fn send_proof(_: &[u8]) -> Vec { vec![173, 202, 13, 26, 207, 73, 0, 46, 121, 238, 48, 170, 96, 146, 60, 49, 88, 76, 12, 184, 152, 76, 207, 220, 140, 205, 190, 189, 117, 6, 131, 63] } +//! +//! let client_proof = verifier.proof(); +//! let server_proof = send_proof(client_proof); +//! let session_key = verifier.verify_server(&server_proof).unwrap(); +//! ``` //! //! //! For user registration on the server first generate salt (e.g. 32 bytes long) @@ -83,16 +99,6 @@ //! send_registration_data(username, salt, &pwd_verifier); //! ``` //! -//! -//! Alternatively, you can instantiate the client with options to use the implementation -//! from the specification of SRP (that generates M1 differently) or omit the username -//! when calculating X to be able to authenticate the same users with their different IDs. -//! -//! ```rust -//! let by_the_spec = true; -//! let no_user_in_x = true; -//! let client = crate::srp::client::SrpClient::::new_with_options(&crate::srp::groups::G_2048, by_the_spec, no_user_in_x); -//! ``` use std::marker::PhantomData; @@ -122,7 +128,7 @@ pub struct SrpClientVerifier { pub struct SrpClientVerifierRfc5054 { m1: Output, m2: Output, - premaster_secret: Vec, + key: Vec, session_key: Vec, } @@ -292,7 +298,7 @@ impl<'a, D: Digest> SrpClient<'a, D> { Ok(SrpClientVerifierRfc5054 { m1, m2, - premaster_secret, + key: premaster_secret, session_key: session_key.to_vec(), }) } @@ -325,8 +331,8 @@ impl SrpClientVerifierRfc5054 { /// Get shared secret key without authenticating server, e.g. for using with /// authenticated encryption modes. DO NOT USE this method without /// some kind of secure authentication - pub fn premaster_secret(&self) -> &[u8] { - &self.premaster_secret + pub fn key(&self) -> &[u8] { + &self.key } /// Verification data for sending to the server. diff --git a/srp/src/server.rs b/srp/src/server.rs index 9de4f85..94c4a0c 100644 --- a/srp/src/server.rs +++ b/srp/src/server.rs @@ -57,7 +57,25 @@ //! # let verifier = server.process_reply(b"", b"", b"1").unwrap(); //! //! verifier.key(); -//!``` +//! ``` +//! +//! +//! Alternatively, you can use `process_reply_rfc5054` method to process parameters +//! according to RFC 5054 if the client is using it. You need to pass `username` and +//! `salt` in addition to other parameters to this method. This way, it generates M1 +//! and M2 differently and also the `verify_client` method will return a shared session +//! key in case of correct server data. +//! +//! ```rust +//! # let server = crate::srp::server::SrpServer::::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply_rfc5054(b"", b"", b"", b"", b"1").unwrap(); +//! # fn get_client_proof()-> Vec { vec![26, 80, 8, 243, 111, 162, 238, 171, 208, 237, 207, 46, 46, 137, 44, 213, 105, 208, 84, 224, 244, 216, 103, 145, 14, 103, 182, 56, 242, 4, 179, 57] }; +//! # fn send_proof(_: &[u8]) { }; +//! +//! let client_proof = get_client_proof(); +//! let session_key = verifier.verify_client(&client_proof).unwrap(); +//! send_proof(verifier.proof()); +//! ``` //! use std::marker::PhantomData; @@ -87,7 +105,7 @@ pub struct SrpServerVerifier { pub struct SrpServerVerifierRfc5054 { m1: Output, m2: Output, - premaster_secret: Vec, + key: Vec, session_key: Vec, } @@ -218,7 +236,7 @@ impl<'a, D: Digest> SrpServer<'a, D> { Ok(SrpServerVerifierRfc5054 { m1, m2, - premaster_secret, + key: premaster_secret, session_key: session_key.to_vec(), }) } @@ -250,8 +268,8 @@ impl SrpServerVerifier { impl SrpServerVerifierRfc5054 { /// Get shared secret between user and the server. (do not forget to verify /// that keys are the same!) - pub fn premaster_secret(&self) -> &[u8] { - &self.premaster_secret + pub fn key(&self) -> &[u8] { + &self.key } /// Verification data for sending to the client. diff --git a/srp/tests/srp.rs b/srp/tests/srp.rs index 01cc713..c3a26f5 100644 --- a/srp/tests/srp.rs +++ b/srp/tests/srp.rs @@ -117,14 +117,14 @@ fn auth_test_rfc5054(true_pwd: &[u8], auth_pwd: &[u8]) { println!("Client verification on server"); let server_session_key = server_verifier.verify_client(client_proof).unwrap(); let server_proof = server_verifier.proof(); - let server_shared_secret = server_verifier.premaster_secret(); + let server_shared_secret = server_verifier.key(); // Server sends server_proof to server (M2) // Client verifies server println!("Server verification on client"); let client_session_key = client_verifier.verify_server(server_proof).unwrap(); - let client_shared_secret = client_verifier.premaster_secret(); + let client_shared_secret = client_verifier.key(); // our shared secrets almost must equal but just an extra check assert_eq!( From 8c163b4966948f2bc711bf50763779637feddb96 Mon Sep 17 00:00:00 2001 From: Masih Yeganeh Date: Thu, 21 Sep 2023 13:05:52 +0330 Subject: [PATCH 5/5] Fix documentation tests --- srp/src/client.rs | 2 +- srp/src/server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/srp/src/client.rs b/srp/src/client.rs index 33e36b9..08936d2 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -75,7 +75,7 @@ //! ```rust //! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); //! # let verifier = client.process_reply_rfc5054(b"", b"", b"", b"", b"1").unwrap(); -//! # fn send_proof(_: &[u8]) -> Vec { vec![173, 202, 13, 26, 207, 73, 0, 46, 121, 238, 48, 170, 96, 146, 60, 49, 88, 76, 12, 184, 152, 76, 207, 220, 140, 205, 190, 189, 117, 6, 131, 63] } +//! # fn send_proof(_: &[u8]) -> Vec { vec![10, 215, 214, 40, 136, 200, 122, 121, 68, 160, 38, 32, 85, 82, 128, 30, 199, 194, 126, 222, 61, 55, 2, 28, 120, 181, 155, 102, 141, 65, 17, 64] } //! //! let client_proof = verifier.proof(); //! let server_proof = send_proof(client_proof); diff --git a/srp/src/server.rs b/srp/src/server.rs index 94c4a0c..b8e4780 100644 --- a/srp/src/server.rs +++ b/srp/src/server.rs @@ -69,7 +69,7 @@ //! ```rust //! # let server = crate::srp::server::SrpServer::::new(&crate::srp::groups::G_2048); //! # let verifier = server.process_reply_rfc5054(b"", b"", b"", b"", b"1").unwrap(); -//! # fn get_client_proof()-> Vec { vec![26, 80, 8, 243, 111, 162, 238, 171, 208, 237, 207, 46, 46, 137, 44, 213, 105, 208, 84, 224, 244, 216, 103, 145, 14, 103, 182, 56, 242, 4, 179, 57] }; +//! # fn get_client_proof()-> Vec { vec![53, 91, 252, 129, 223, 201, 97, 145, 208, 243, 229, 232, 20, 118, 108, 126, 244, 68, 237, 38, 121, 24, 181, 53, 155, 103, 134, 44, 107, 204, 56, 50] }; //! # fn send_proof(_: &[u8]) { }; //! //! let client_proof = get_client_proof();