diff --git a/srp/src/client.rs b/srp/src/client.rs index 53f904d..08936d2 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![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); +//! let session_key = verifier.verify_server(&server_proof).unwrap(); +//! ``` //! //! //! For user registration on the server first generate salt (e.g. 32 bytes long) @@ -82,6 +98,7 @@ //! let pwd_verifier = client.compute_verifier(username, password, salt); //! send_registration_data(username, salt, &pwd_verifier); //! ``` +//! use std::marker::PhantomData; @@ -90,7 +107,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 client state before handshake with the server. pub struct SrpClient<'a, D: Digest> { @@ -105,6 +124,14 @@ pub struct SrpClientVerifier { key: Vec, } +/// RFC 5054 SRP client state after handshake with the server. +pub struct SrpClientVerifierRfc5054 { + m1: Output, + m2: Output, + key: Vec, + session_key: Vec, +} + impl<'a, D: Digest> SrpClient<'a, D> { /// Create new SRP client instance. #[must_use] @@ -224,6 +251,57 @@ impl<'a, D: Digest> SrpClient<'a, D> { 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 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); + + 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, + key: premaster_secret, + session_key: session_key.to_vec(), + }) + } } impl SrpClientVerifier { @@ -248,3 +326,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 key(&self) -> &[u8] { + &self.key + } + + /// 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..b8e4780 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![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(); +//! let session_key = verifier.verify_client(&client_proof).unwrap(); +//! send_proof(verifier.proof()); +//! ``` //! use std::marker::PhantomData; @@ -66,7 +84,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 +101,14 @@ pub struct SrpServerVerifier { key: Vec, } +/// RFC 5054 SRP server state after handshake with the client. +pub struct SrpServerVerifierRfc5054 { + m1: Output, + m2: Output, + key: Vec, + session_key: Vec, +} + impl<'a, D: Digest> SrpServer<'a, D> { /// Create new server state. #[must_use] @@ -162,6 +190,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, + key: premaster_secret, + session_key: session_key.to_vec(), + }) + } } impl SrpServerVerifier { @@ -186,3 +264,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 key(&self) -> &[u8] { + &self.key + } + + /// 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 0d77129..8232a73 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) following RFC5054 +#[must_use] +pub fn compute_m1_rfc5054( + 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/srp.rs b/srp/tests/srp.rs index 1cedd67..c3a26f5 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.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.key(); + + // 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"); +}