diff options
Diffstat (limited to 'srp/src')
-rw-r--r-- | srp/src/client.rs | 327 | ||||
-rw-r--r-- | srp/src/lib.rs | 7 | ||||
-rw-r--r-- | srp/src/prime.bin | 1 | ||||
-rw-r--r-- | srp/src/server.rs | 243 | ||||
-rw-r--r-- | srp/src/test/k_sha1_1024.bin (renamed from srp/src/k_sha1_1024.bin) | 0 | ||||
-rw-r--r-- | srp/src/types.rs | 72 | ||||
-rw-r--r-- | srp/src/utils.rs | 45 |
7 files changed, 350 insertions, 345 deletions
diff --git a/srp/src/client.rs b/srp/src/client.rs index 815b195..ee8fd8d 100644 --- a/srp/src/client.rs +++ b/srp/src/client.rs @@ -2,275 +2,242 @@ //! //! # Usage //! First create SRP client struct by passing to it SRP parameters (shared -//! between client and server) and randomly generated `a`: +//! between client and server). //! -//! ```ignore -//! use srp::groups::G_2048; -//! use sha2::Sha256; +//! You can use SHA1 from SRP-6a, but it's highly recommended to use specialized +//! password hashing algorithm instead (e.g. PBKDF2, argon2 or scrypt). //! -//! let mut a = [0u8; 64]; -//! rng.fill_bytes(&mut a); -//! let client = SrpClient::<Sha256>::new(&a, &G_2048); +//! ```rust +//! use crate::srp::groups::G_2048; +//! use sha2::Sha256; // Note: You should probably use a proper password KDF +//! # use crate::srp::client::SrpClient; +//! +//! let client = SrpClient::<Sha256>::new(&G_2048); //! ``` //! //! Next send handshake data (username and `a_pub`) to the server and receive //! `salt` and `b_pub`: //! -//! ```ignore -//! let a_pub = client.get_a_pub(); -//! let (salt, b_pub) = conn.send_handshake(username, a_pub); +//! ```rust +//! # let client = crate::srp::client::SrpClient::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # fn server_response()-> (Vec<u8>, Vec<u8>) { (vec![], vec![]) } +//! +//! let mut a = [0u8; 64]; +//! // rng.fill_bytes(&mut a); +//! let a_pub = client.compute_public_ephemeral(&a); +//! let (salt, b_pub) = server_response(); //! ``` //! -//! Compute private key using `salt` with any password hashing function. -//! You can use method from SRP-6a, but it's recommended to use specialized -//! password hashing algorithm instead (e.g. PBKDF2, argon2 or scrypt). -//! Next create verifier instance, note that `get_verifier` consumes client and -//! can return error in case of malicious `b_pub`. +//! Process the server response and create verifier instance. +//! process_reply can return error in case of malicious `b_pub`. +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let a = [0u8; 64]; +//! # let username = b"username"; +//! # let password = b"password"; +//! # let salt = b"salt"; +//! # let b_pub = b"b_pub"; //! -//! ```ignore -//! let private_key = srp_private_key::<Sha256>(username, password, salt); -//! let verifier = client.get_verifier(&private_key, &b_pub)?; +//! let private_key = (username, password, salt); +//! let verifier = client.process_reply(&a, username, password, salt, b_pub); //! ``` //! //! Finally verify the server: first generate user proof, //! send it to the server and verify server proof in the reply. Note that //! `verify_server` method will return error in case of incorrect server reply. //! -//! ```ignore -//! let user_proof = verifier.get_proof(); -//! let server_proof = conn.send_proof(user_proof); -//! let key = verifier.verify_server(server_proof)?; +//! ```rust +//! # let client = crate::srp::client::SrpClient::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap(); +//! # fn send_proof(_: &[u8]) -> Vec<u8> { 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); +//! verifier.verify_server(&server_proof).unwrap(); //! ``` //! -//! `key` contains shared secret key between user and the server. Alternatively -//! you can directly extract shared secret key using `get_key()` method and -//! handle authentication through different (secure!) means (e.g. by using -//! authenticated cipher mode). +//! `key` contains shared secret key between user and the server. You can extract shared secret +//! key using `key()` method. +//! ```rust +//! # let client = crate::srp::client::SrpClient::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap(); +//! +//! verifier.key(); +//!``` +//! //! //! For user registration on the server first generate salt (e.g. 32 bytes long) //! and get password verifier which depends on private key. Send username, salt //! and password verifier over protected channel to protect against //! Man-in-the-middle (MITM) attack for registration. //! -//! ```ignore -//! let pwd_verifier = client.get_password_verifier(&private_key); -//! conn.send_registration_data(username, salt, pwd_verifier); +//! ```rust +//! # let client = crate::srp::client::SrpClient::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let username = b"username"; +//! # let password = b"password"; +//! # let salt = b"salt"; +//! # fn send_registration_data(_: &[u8], _: &[u8], _: &[u8]) {} +//! +//! let pwd_verifier = client.compute_verifier(username, password, salt); +//! send_registration_data(username, salt, &pwd_verifier); //! ``` use std::marker::PhantomData; use digest::{Digest, Output}; use num_bigint::BigUint; +use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; +use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; /// SRP client state before handshake with the server. pub struct SrpClient<'a, D: Digest> { params: &'a SrpGroup, - - a: BigUint, - a_pub: BigUint, - d: PhantomData<D>, } /// SRP client state after handshake with the server. pub struct SrpClientVerifier<D: Digest> { - proof: Output<D>, - server_proof: Output<D>, - key: Output<D>, -} - -/// Compute user private key as described in the RFC 5054. Consider using proper -/// password hashing algorithm instead. -pub fn srp_private_key<D: Digest>(username: &[u8], password: &[u8], salt: &[u8]) -> Output<D> { - let p = { - let mut d = D::new(); - d.update(username); - d.update(b":"); - d.update(password); - d.finalize() - }; - let mut d = D::new(); - d.update(salt); - d.update(p.as_slice()); - d.finalize() + m1: Output<D>, + m2: Output<D>, + key: Vec<u8>, } impl<'a, D: Digest> SrpClient<'a, D> { /// Create new SRP client instance. - pub fn new(a: &[u8], params: &'a SrpGroup) -> Self { - let a = BigUint::from_bytes_be(a); - let a_pub = params.modpow(&a); - + pub fn new(params: &'a SrpGroup) -> Self { Self { params, - a, - a_pub, d: Default::default(), } } - /// Get password verfier for user registration on the server - pub fn get_password_verifier(&self, private_key: &[u8]) -> Vec<u8> { - let x = BigUint::from_bytes_be(private_key); - let v = self.params.modpow(&x); - v.to_bytes_be() + pub fn compute_a_pub(&self, a: &BigUint) -> BigUint { + self.params.g.modpow(a, &self.params.n) } - fn calc_key(&self, b_pub: &BigUint, x: &BigUint, u: &BigUint) -> Output<D> { - let n = &self.params.n; - let k = self.params.compute_k::<D>(); - let interm = (k * self.params.modpow(x)) % n; - // Because we do operation in modulo N we can get: (kv + g^b) < kv - let v = if *b_pub > interm { - (b_pub - &interm) % n - } else { - (n + b_pub - &interm) % n - }; - // S = |B - kg^x| ^ (a + ux) - let s = v.modpow(&(&self.a + (u * x) % n), n); - D::digest(&s.to_bytes_be()) + // H(<username> | ":" | <raw password>) + pub fn compute_identity_hash(username: &[u8], password: &[u8]) -> Output<D> { + let mut d = D::new(); + d.update(username); + d.update(b":"); + d.update(password); + d.finalize() } - /// Process server reply to the handshake. - pub fn process_reply( - self, - private_key: &[u8], - b_pub: &[u8], - ) -> Result<SrpClientVerifier<D>, SrpAuthError> { - let u = { - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(b_pub); - let h = d.finalize(); - BigUint::from_bytes_be(h.as_slice()) - }; - - let b_pub = BigUint::from_bytes_be(b_pub); + // x = H(<salt> | H(<username> | ":" | <raw password>)) + pub fn compute_x(identity_hash: &[u8], salt: &[u8]) -> BigUint { + let mut x = D::new(); + x.update(salt); + x.update(identity_hash); + BigUint::from_bytes_be(&x.finalize()) + } - // Safeguard against malicious B - if &b_pub % &self.params.n == BigUint::default() { - return Err(SrpAuthError { - description: "Malicious b_pub value", - }); - } + // (B - (k * g^x)) ^ (a + (u * x)) % N + pub fn compute_premaster_secret( + &self, + b_pub: &BigUint, + k: &BigUint, + x: &BigUint, + a: &BigUint, + u: &BigUint, + ) -> BigUint { + // (k * g^x) + let base = (k * (self.params.g.modpow(x, &self.params.n))) % &self.params.n; + // Because we do operation in modulo N we can get: b_pub > base. That's not good. So we add N to b_pub to make sure. + // B - kg^x + let base = ((&self.params.n + b_pub) - &base) % &self.params.n; + let exp = (u * x) + a; + // S = (B - kg^x) ^ (a + ux) + // or + // S = base ^ exp + base.modpow(&exp, &self.params.n) + } - let x = BigUint::from_bytes_be(private_key); - let key = self.calc_key(&b_pub, &x, &u); - // M1 = H(A, B, K) - let proof = { - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(&b_pub.to_bytes_be()); - d.update(&key); - d.finalize() - }; + // v = g^x % N + pub fn compute_v(&self, x: &BigUint) -> BigUint { + self.params.g.modpow(x, &self.params.n) + } - // M2 = H(A, M1, K) - let server_proof = { - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(&proof); - d.update(&key); - d.finalize() - }; + /// Get password verifier (v in RFC5054) for user registration on the server. + pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec<u8> { + 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() + } - Ok(SrpClientVerifier { - proof, - server_proof, - key, - }) + /// Get public ephemeral value for handshaking with the server. + /// g^a % N + pub fn compute_public_ephemeral(&self, a: &[u8]) -> Vec<u8> { + self.compute_a_pub(&BigUint::from_bytes_be(a)).to_bytes_be() } - /// Process server reply to the handshake with username and salt. - #[allow(non_snake_case)] - pub fn process_reply_with_username_and_salt( - self, + /// Process server reply to the handshake. + /// a is a random value, + /// username, password is supplied by the user + /// salt and b_pub come from the server + pub fn process_reply( + &self, + a: &[u8], username: &[u8], + password: &[u8], salt: &[u8], - private_key: &[u8], b_pub: &[u8], ) -> Result<SrpClientVerifier<D>, SrpAuthError> { - let u = { - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(b_pub); - let h = d.finalize(); - BigUint::from_bytes_be(h.as_slice()) - }; - + 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 { - description: "Malicious b_pub value", - }); + return Err(SrpAuthError::IllegalParameter("b_pub".to_owned())); } - let x = BigUint::from_bytes_be(private_key); - let key = self.calc_key(&b_pub, &x, &u); - // M1 = H(H(N)^H(g), H(I), salt, A, B, K) - let proof = { - let mut d = D::new(); - d.update(username); - let h = d.finalize_reset(); - let I: &[u8] = h.as_slice(); + let u = compute_u::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + let k = compute_k::<D>(self.params); + 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); - d.update(self.params.compute_hash_n_xor_hash_g::<D>()); - d.update(I); - d.update(salt); - d.update(&self.a_pub.to_bytes_be()); - d.update(&b_pub.to_bytes_be()); - d.update(&key.to_vec()); - d.finalize() - }; + let m1 = compute_m1::<D>( + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + &key.to_bytes_be(), + ); - // M2 = H(A, M1, K) - let server_proof = { - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(&proof); - d.update(&key); - d.finalize() - }; + let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be()); Ok(SrpClientVerifier { - proof, - server_proof, - key, + m1, + m2, + key: key.to_bytes_be(), }) } - - /// Get public ephemeral value for handshaking with the server. - pub fn get_a_pub(&self) -> Vec<u8> { - self.a_pub.to_bytes_be() - } } impl<D: Digest> SrpClientVerifier<D> { /// 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 get_key(self) -> Output<D> { - self.key + pub fn key(&self) -> &[u8] { + &self.key } /// Verification data for sending to the server. - pub fn get_proof(&self) -> Output<D> { - self.proof.clone() + pub fn proof(&self) -> &[u8] { + self.m1.as_slice() } - /// Verify server reply to verification data. It will return shared secret - /// key in case of success. - pub fn verify_server(self, reply: &[u8]) -> Result<Output<D>, SrpAuthError> { - if self.server_proof.as_slice() != reply { - Err(SrpAuthError { - description: "Incorrect server proof", - }) + /// Verify server reply to verification data. + pub fn verify_server(&self, reply: &[u8]) -> Result<(), SrpAuthError> { + if self.m2.ct_eq(reply).unwrap_u8() != 1 { + // aka == 0 + Err(SrpAuthError::BadRecordMac("server".to_owned())) } else { - Ok(self.key) + Ok(()) } } } diff --git a/srp/src/lib.rs b/srp/src/lib.rs index 375dfb3..9ce0346 100644 --- a/srp/src/lib.rs +++ b/srp/src/lib.rs @@ -10,12 +10,6 @@ //! srp = "0.4" //! ``` //! -//! and this to your crate root: -//! -//! ```rust -//! extern crate srp; -//! ``` -//! //! Next read documentation for [`client`](client/index.html) and //! [`server`](server/index.html) modules. //! @@ -60,3 +54,4 @@ pub mod client; pub mod groups; pub mod server; pub mod types; +pub mod utils; diff --git a/srp/src/prime.bin b/srp/src/prime.bin deleted file mode 100644 index d2109b7..0000000 --- a/srp/src/prime.bin +++ /dev/null @@ -1 +0,0 @@ -_P<=èyCp^ >Hhw
Bb{+2,ZuHEXrӋto 'Tw;䪗Dl'$2/A-tcK2-懘2u7
\ No newline at end of file diff --git a/srp/src/server.rs b/srp/src/server.rs index 299c1ce..0ecc17c 100644 --- a/srp/src/server.rs +++ b/srp/src/server.rs @@ -2,145 +2,184 @@ //! //! # Usage //! First receive user's username and public value `a_pub`, retrieve from a -//! database `UserRecord` for a given username, generate `b` (e.g. 512 bits -//! long) and initialize SRP server instance: +//! database the salt and verifier for a given username. Generate `b` and public value `b_pub`. //! -//! ```ignore -//! use srp::groups::G_2048; //! -//! let (username, a_pub) = conn.receive_handshake(); -//! let user = db.retrieve_user_record(username); -//! let b = [0u8; 64]; -//! rng.fill_bytes(&mut b); -//! let server = SrpServer::<Sha256>::new(&user, &a_pub, &b, &G_2048)?; +//! ```rust +//! use crate::srp::groups::G_2048; +//! use sha2::Sha256; // Note: You should probably use a proper password KDF +//! # use crate::srp::server::SrpServer; +//! # fn get_client_request()-> (Vec<u8>, Vec<u8>) { (vec![], vec![])} +//! # fn get_user(_: &[u8])-> (Vec<u8>, Vec<u8>) { (vec![], vec![])} +//! +//! let server = SrpServer::<Sha256>::new(&G_2048); +//! let (username, a_pub) = get_client_request(); +//! let (salt, v) = get_user(&username); +//! let mut b = [0u8; 64]; +//! // rng.fill_bytes(&mut b); +//! let b_pub = server.compute_public_ephemeral(&b, &v); //! ``` //! -//! Next send to user `b_pub` and `salt` from user record: +//! Next send to user `b_pub` and `salt` from user record +//! +//! Next process the user response: +//! +//! ```rust +//! # let server = crate::srp::server::SrpServer::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # fn get_client_response() -> Vec<u8> { vec![1] } +//! # let b = [0u8; 64]; +//! # let v = b""; //! -//! ```ignore -//! let b_pub = server.get_b_pub(); -//! conn.reply_to_handshake(&user.salt, b_pub); +//! let a_pub = get_client_response(); +//! let verifier = server.process_reply(&b, v, &a_pub).unwrap(); //! ``` //! +//! //! And finally receive user proof, verify it and send server proof in the //! reply: //! -//! ```ignore -//! let user_proof = conn.receive_proof(); -//! let server_proof = server.verify(user_proof)?; -//! conn.send_proof(server_proof); +//! ```rust +//! # let server = crate::srp::server::SrpServer::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply(b"", b"", b"1").unwrap(); +//! # fn get_client_proof()-> Vec<u8> { 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(); +//! verifier.verify_client(&client_proof).unwrap(); +//! send_proof(verifier.proof()); //! ``` //! -//! To get the shared secret use `get_key` method. As alternative to using -//! `verify` method it's also possible to use this key for authentificated -//! encryption. +//! +//! `key` contains shared secret key between user and the server. You can extract shared secret +//! key using `key()` method. +//! ```rust +//! # let server = crate::srp::server::SrpServer::<sha2::Sha256>::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply(b"", b"", b"1").unwrap(); +//! +//! verifier.key(); +//!``` +//! use std::marker::PhantomData; use digest::{Digest, Output}; use num_bigint::BigUint; +use subtle::ConstantTimeEq; use crate::types::{SrpAuthError, SrpGroup}; - -/// Data provided by users upon registration, usually stored in the database. -pub struct UserRecord<'a> { - pub username: &'a [u8], - pub salt: &'a [u8], - /// Password verifier - pub verifier: &'a [u8], -} +use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; /// SRP server state -pub struct SrpServer<D: Digest> { - b: BigUint, - a_pub: BigUint, - b_pub: BigUint, - - key: Output<D>, - +pub struct SrpServer<'a, D: Digest> { + params: &'a SrpGroup, d: PhantomData<D>, } -impl<D: Digest> SrpServer<D> { +/// SRP server state after handshake with the client. +pub struct SrpServerVerifier<D: Digest> { + m1: Output<D>, + m2: Output<D>, + key: Vec<u8>, +} + +impl<'a, D: Digest> SrpServer<'a, D> { /// Create new server state. - pub fn new( - user: &UserRecord<'_>, - a_pub: &[u8], + pub fn new(params: &'a SrpGroup) -> Self { + Self { + params, + d: Default::default(), + } + } + + // k*v + g^b % N + pub fn compute_b_pub(&self, b: &BigUint, k: &BigUint, v: &BigUint) -> BigUint { + let inter = (k * v) % &self.params.n; + (inter + self.params.g.modpow(b, &self.params.n)) % &self.params.n + } + + // <premaster secret> = (A * v^u) ^ b % N + pub fn compute_premaster_secret( + &self, + a_pub: &BigUint, + v: &BigUint, + u: &BigUint, + b: &BigUint, + ) -> BigUint { + // (A * v^u) + let base = (a_pub * v.modpow(u, &self.params.n)) % &self.params.n; + base.modpow(b, &self.params.n) + } + + /// Get public ephemeral value for sending to the client. + pub fn compute_public_ephemeral(&self, b: &[u8], v: &[u8]) -> Vec<u8> { + self.compute_b_pub( + &BigUint::from_bytes_be(b), + &compute_k::<D>(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 + pub fn process_reply( + &self, b: &[u8], - params: &SrpGroup, - ) -> Result<Self, SrpAuthError> { + v: &[u8], + a_pub: &[u8], + ) -> Result<SrpServerVerifier<D>, 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::<D>(self.params); + let b_pub = self.compute_b_pub(&b, &k, &v); + // Safeguard against malicious A - if &a_pub % ¶ms.n == BigUint::default() { - return Err(SrpAuthError { - description: "Malicious a_pub value", - }); + if &a_pub % &self.params.n == BigUint::default() { + return Err(SrpAuthError::IllegalParameter("a_pub".to_owned())); } - let v = BigUint::from_bytes_be(user.verifier); - let b = BigUint::from_bytes_be(b) % ¶ms.n; - let k = params.compute_k::<D>(); - // kv + g^b - let interm = (k * &v) % ¶ms.n; - let b_pub = (interm + ¶ms.modpow(&b)) % ¶ms.n; - // H(A || B) - let u = { - let mut d = D::new(); - d.update(&a_pub.to_bytes_be()); - d.update(&b_pub.to_bytes_be()); - d.finalize() - }; - let d = Default::default(); - //(Av^u) ^ b - let key = { - let u = BigUint::from_bytes_be(u.as_slice()); - let t = (&a_pub * v.modpow(&u, ¶ms.n)) % ¶ms.n; - let s = t.modpow(&b, ¶ms.n); - D::digest(&s.to_bytes_be()) - }; - Ok(Self { - b, - a_pub, - b_pub, - key, - d, - }) - } - /// Get private `b` value. (see `new_with_b` documentation) - pub fn get_b(&self) -> Vec<u8> { - self.b.to_bytes_be() - } + let u = compute_u::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + + let key = self.compute_premaster_secret(&a_pub, &v, &u, &b); + + let m1 = compute_m1::<D>( + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + &key.to_bytes_be(), + ); + + let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be()); - /// Get public `b_pub` value for sending to the user. - pub fn get_b_pub(&self) -> Vec<u8> { - self.b_pub.to_bytes_be() + Ok(SrpServerVerifier { + m1, + m2, + key: key.to_bytes_be(), + }) } +} +impl<D: Digest> SrpServerVerifier<D> { /// Get shared secret between user and the server. (do not forget to verify /// that keys are the same!) - pub fn get_key(&self) -> Output<D> { - self.key.clone() + 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 compute - /// server proof for sending to the user. - pub fn verify(&self, user_proof: &[u8]) -> Result<Output<D>, SrpAuthError> { - // M = H(A, B, K) - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(&self.b_pub.to_bytes_be()); - d.update(&self.key); - - if user_proof == d.finalize().as_slice() { - // H(A, M, K) - let mut d = D::new(); - d.update(&self.a_pub.to_bytes_be()); - d.update(user_proof); - d.update(&self.key); - Ok(d.finalize()) + /// Process user proof of having the same shared secret. + pub fn verify_client(&self, reply: &[u8]) -> Result<(), SrpAuthError> { + if self.m1.ct_eq(reply).unwrap_u8() != 1 { + // aka == 0 + Err(SrpAuthError::BadRecordMac("client".to_owned())) } else { - Err(SrpAuthError { - description: "Incorrect user proof", - }) + Ok(()) } } } diff --git a/srp/src/k_sha1_1024.bin b/srp/src/test/k_sha1_1024.bin index 4408438..4408438 100644 --- a/srp/src/k_sha1_1024.bin +++ b/srp/src/test/k_sha1_1024.bin diff --git a/srp/src/types.rs b/srp/src/types.rs index a23954e..a356caf 100644 --- a/srp/src/types.rs +++ b/srp/src/types.rs @@ -1,23 +1,24 @@ //! Additional SRP types. -use digest::Digest; use num_bigint::BigUint; -use std::{error, fmt}; +use std::fmt; /// SRP authentication error. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct SrpAuthError { - pub(crate) description: &'static str, +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum SrpAuthError { + IllegalParameter(String), + BadRecordMac(String), } impl fmt::Display for SrpAuthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SRP authentication error") - } -} - -impl error::Error for SrpAuthError { - fn description(&self) -> &str { - self.description + match self { + SrpAuthError::IllegalParameter(param) => { + write!(f, "illegal_parameter: bad '{}' value", param) + } + SrpAuthError::BadRecordMac(param) => { + write!(f, "bad_record_mac: incorrect '{}' proof", param) + } + } } } @@ -30,56 +31,15 @@ pub struct SrpGroup { pub g: BigUint, } -impl SrpGroup { - pub(crate) fn modpow(&self, v: &BigUint) -> BigUint { - self.g.modpow(v, &self.n) - } - - /// Compute `k` with given hash function and return SRP parameters - pub(crate) fn compute_k<D: Digest>(&self) -> BigUint { - let n = self.n.to_bytes_be(); - let g_bytes = self.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 mut d = D::new(); - d.update(&n); - d.update(&buf); - BigUint::from_bytes_be(d.finalize().as_slice()) - } - - /// Compute `Hash(N) xor Hash(g)` with given hash function and return SRP parameters - pub(crate) fn compute_hash_n_xor_hash_g<D: Digest>(&self) -> Vec<u8> { - let n = self.n.to_bytes_be(); - let g_bytes = self.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 mut d = D::new(); - d.update(&n); - let h = d.finalize_reset(); - let h_n: &[u8] = h.as_slice(); - d.update(&buf); - let h = d.finalize_reset(); - let h_g: &[u8] = h.as_slice(); - - h_n.iter() - .zip(h_g.iter()) - .map(|(&x1, &x2)| x1 ^ x2) - .collect() - } -} - #[cfg(test)] mod tests { use crate::groups::G_1024; + use crate::utils::compute_k; use sha1::Sha1; #[test] fn test_k_1024_sha1() { - let k = G_1024.compute_k::<Sha1>().to_bytes_be(); - assert_eq!(&k, include_bytes!("k_sha1_1024.bin")); + let k = compute_k::<Sha1>(&G_1024).to_bytes_be(); + assert_eq!(&k, include_bytes!("test/k_sha1_1024.bin")); } } diff --git a/srp/src/utils.rs b/srp/src/utils.rs new file mode 100644 index 0000000..a9372bd --- /dev/null +++ b/srp/src/utils.rs @@ -0,0 +1,45 @@ +use digest::{Digest, Output}; +use num_bigint::BigUint; + +use crate::types::SrpGroup; + +// u = H(PAD(A) | PAD(B)) +pub fn compute_u<D: Digest>(a_pub: &[u8], b_pub: &[u8]) -> BigUint { + let mut u = D::new(); + u.update(a_pub); + u.update(b_pub); + BigUint::from_bytes_be(&u.finalize()) +} + +// k = H(N | PAD(g)) +pub fn compute_k<D: Digest>(params: &SrpGroup) -> BigUint { + 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 mut d = D::new(); + d.update(&n); + d.update(&buf); + BigUint::from_bytes_be(d.finalize().as_slice()) +} + +// 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 +pub fn compute_m1<D: Digest>(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output<D> { + let mut d = D::new(); + d.update(a_pub); + d.update(b_pub); + d.update(key); + d.finalize() +} + +// M2 = H(A, M1, K) +pub fn compute_m2<D: Digest>(a_pub: &[u8], m1: &Output<D>, key: &[u8]) -> Output<D> { + let mut d = D::new(); + d.update(&a_pub); + d.update(&m1); + d.update(&key); + d.finalize() +} |