From: Josh Brown Date: Sat, 22 Jan 2022 14:38:33 +0000 (-0500) Subject: srp: rebuild library (#79) X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=6d963225520f0d8e5948457b8ba25bd563382f5e;p=PAKEs.git srp: rebuild library (#79) Complete rewrite of the SRP library. Includes many improvements over the old library: - Improved file and code organization - Access to individual SRP computations - Consistent sever and client API - Simpler API - Improved documentation with tests in documentation - New tests for compatibility with the RFC - Bumps dependencies - Timing safe verification comparisons - Modernized error handling --- diff --git a/Cargo.lock b/Cargo.lock index 5789c25..74a3c15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,6 +35,15 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -43,9 +52,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cfg-if" @@ -80,6 +89,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "crypto-mac" version = "0.11.1" @@ -121,6 +139,17 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "digest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +dependencies = [ + "block-buffer 0.10.0", + "crypto-common", + "generic-array 0.14.4", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -152,6 +181,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hkdf" version = "0.11.0" @@ -180,15 +215,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "num-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg 1.0.1", "num-integer", @@ -337,15 +372,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ - "block-buffer", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.1", ] [[package]] @@ -354,13 +387,24 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.1", +] + [[package]] name = "spake2" version = "0.3.0-pre" @@ -371,20 +415,23 @@ dependencies = [ "hkdf", "num-bigint", "rand", - "sha2", + "sha2 0.9.8", ] [[package]] name = "srp" version = "0.6.0-pre" dependencies = [ - "digest 0.9.0", + "digest 0.10.1", "generic-array 0.14.4", + "hex-literal", "lazy_static", "num-bigint", + "num-traits", "rand", "sha-1", - "sha2", + "sha2 0.10.1", + "subtle", ] [[package]] @@ -395,9 +442,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "version_check" diff --git a/srp/Cargo.toml b/srp/Cargo.toml index 9230f8e..88ea5b1 100644 --- a/srp/Cargo.toml +++ b/srp/Cargo.toml @@ -9,19 +9,19 @@ repository = "https://github.com/RustCrypto/PAKEs" keywords = ["crypto", "pake", "authentication"] categories = ["cryptography", "authentication"] readme = "README.md" -edition = "2018" +edition = "2021" rust-version = "1.56" [dependencies] num-bigint = "0.4" generic-array = "0.14" -digest = "0.9" +digest = "0.10" lazy_static = "1.2" +subtle = "2.4" [dev-dependencies] rand = "0.6" -sha2 = "0.9" -sha-1 = "0.9" - -[badges] -travis-ci = { repository = "RustCrypto/PAKEs" } +sha2 = "0.10" +sha-1 = "0.10" +num-traits = "0.2" +hex-literal = "0.3" \ No newline at end of file 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::::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::::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::::new(&crate::srp::groups::G_2048); +//! # fn server_response()-> (Vec, Vec) { (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::::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::(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::::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply(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); +//! 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::::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::::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, } /// SRP client state after handshake with the server. pub struct SrpClientVerifier { - proof: Output, - server_proof: Output, - key: Output, -} - -/// Compute user private key as described in the RFC 5054. Consider using proper -/// password hashing algorithm instead. -pub fn srp_private_key(username: &[u8], password: &[u8], salt: &[u8]) -> Output { - 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, + m2: Output, + key: Vec, } 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 { - 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 { - let n = &self.params.n; - let k = self.params.compute_k::(); - 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( | ":" | ) + pub fn compute_identity_hash(username: &[u8], password: &[u8]) -> Output { + 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, 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( | H( | ":" | )) + 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 { + 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 { + 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, 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::(&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 key = self.compute_premaster_secret(&b_pub, &k, &x, &a, &u); - d.update(self.params.compute_hash_n_xor_hash_g::()); - 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::( + &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::(&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 { - self.a_pub.to_bytes_be() - } } impl SrpClientVerifier { /// 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 { - self.key + pub fn key(&self) -> &[u8] { + &self.key } /// Verification data for sending to the server. - pub fn get_proof(&self) -> Output { - 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, 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/k_sha1_1024.bin b/srp/src/k_sha1_1024.bin deleted file mode 100644 index 4408438..0000000 --- a/srp/src/k_sha1_1024.bin +++ /dev/null @@ -1 +0,0 @@ -uVªZï,Ý«¯f\>‰o \ No newline at end of file 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^ >Hšh„w Ãâ¼Bñèb{+2,Z›uš©HEX«øõÉrçӋtý×Ño Ç'³ßTw¿¸§ü;¦†äª—Dˆýl'$ ¿Óð2Â/ AŠ-ítùc¸¤K2£-懘2´ˆuÃ7À \ 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::::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, Vec) { (vec![], vec![])} +//! # fn get_user(_: &[u8])-> (Vec, Vec) { (vec![], vec![])} +//! +//! let server = SrpServer::::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::::new(&crate::srp::groups::G_2048); +//! # fn get_client_response() -> Vec { 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::::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply(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(); +//! 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::::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 { - b: BigUint, - a_pub: BigUint, - b_pub: BigUint, - - key: Output, - +pub struct SrpServer<'a, D: Digest> { + params: &'a SrpGroup, d: PhantomData, } -impl SrpServer { +/// SRP server state after handshake with the client. +pub struct SrpServerVerifier { + m1: Output, + m2: Output, + key: Vec, +} + +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 + } + + // = (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 { + self.compute_b_pub( + &BigUint::from_bytes_be(b), + &compute_k::(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 { + 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 % ¶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::(); - // 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 { - self.b.to_bytes_be() - } + let u = compute_u::(&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::( + &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()); - /// Get public `b_pub` value for sending to the user. - pub fn get_b_pub(&self) -> Vec { - self.b_pub.to_bytes_be() + Ok(SrpServerVerifier { + m1, + m2, + key: key.to_bytes_be(), + }) } +} +impl SrpServerVerifier { /// Get shared secret between user and the server. (do not forget to verify /// that keys are the same!) - pub fn get_key(&self) -> Output { - 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, 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/test/k_sha1_1024.bin b/srp/src/test/k_sha1_1024.bin new file mode 100644 index 0000000..4408438 --- /dev/null +++ b/srp/src/test/k_sha1_1024.bin @@ -0,0 +1 @@ +uVªZï,Ý«¯f\>‰o \ No newline at end of file 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(&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(&self) -> Vec { - 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::().to_bytes_be(); - assert_eq!(&k, include_bytes!("k_sha1_1024.bin")); + let k = compute_k::(&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(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(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(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output { + 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(a_pub: &[u8], m1: &Output, key: &[u8]) -> Output { + let mut d = D::new(); + d.update(&a_pub); + d.update(&m1); + d.update(&key); + d.finalize() +} diff --git a/srp/tests/bad_public.rs b/srp/tests/bad_public.rs new file mode 100644 index 0000000..e18b38d --- /dev/null +++ b/srp/tests/bad_public.rs @@ -0,0 +1,24 @@ +use num_bigint::BigUint; +use num_traits::identities::Zero; +use sha1::Sha1; +use srp::client::SrpClient; +use srp::groups::G_1024; +use srp::server::SrpServer; + +#[test] +#[should_panic] +fn bad_a_pub() { + let server = SrpServer::::new(&G_1024); + server + .process_reply(b"", b"", &BigUint::zero().to_bytes_be()) + .unwrap(); +} + +#[test] +#[should_panic] +fn bad_b_pub() { + let client = SrpClient::::new(&G_1024); + client + .process_reply(b"", b"", b"", b"", &BigUint::zero().to_bytes_be()) + .unwrap(); +} diff --git a/srp/tests/rfc5054.rs b/srp/tests/rfc5054.rs new file mode 100644 index 0000000..e1aa711 --- /dev/null +++ b/srp/tests/rfc5054.rs @@ -0,0 +1,135 @@ +use hex_literal::hex; +use num_bigint::BigUint; +use sha1::Sha1; +use srp::client::SrpClient; +use srp::groups::G_1024; +use srp::server::SrpServer; +use srp::utils::{compute_k, compute_u}; + +#[test] +#[allow(clippy::many_single_char_names)] +fn rfc5054() { + let i = b"alice"; + let p = b"password123"; + let s = hex!("BEB25379 D1A8581E B5A72767 3A2441EE"); + let group = &G_1024; + + let k = compute_k::(group); + + assert_eq!( + k.to_bytes_be(), + hex!("7556AA04 5AEF2CDD 07ABAF0F 665C3E81 8913186F"), + "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!("94B7555A ABE9127C C58CCF49 93DB6CF8 4D16C124"), + "bad x value" + ); + + let client = SrpClient::::new(group); + let v = client.compute_v(&x); + + assert_eq!( + v.to_bytes_be(), + hex!( + " + 7E273DE8 696FFC4F 4E337D05 B4B375BE B0DDE156 9E8FA00A 9886D812 + 9BADA1F1 822223CA 1A605B53 0E379BA4 729FDC59 F105B478 7E5186F5 + C671085A 1447B52A 48CF1970 B4FB6F84 00BBF4CE BFBB1681 52E08AB5 + EA53D15C 1AFF87B2 B9DA6E04 E058AD51 CC72BFC9 033B564E 26480D78 + E955A5E2 9E7AB245 DB2BE315 E2099AFB + " + ), + "bad v value" + ); + + let a = BigUint::from_bytes_be(&hex!( + " + 60975527 035CF2AD 1989806F 0407210B C81EDC04 E2762A56 AFD529DD + DA2D4393" + )); + + 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!( + " + 61D5E490 F6F1B795 47B0704C 436F523D D0E560F0 C64115BB 72557EC4 + 4352E890 3211C046 92272D8B 2D1A5358 A2CF1B6E 0BFCF99F 921530EC + 8E393561 79EAE45E 42BA92AE ACED8251 71E1E8B9 AF6D9C03 E1327F44 + BE087EF0 6530E69F 66615261 EEF54073 CA11CF58 58F0EDFD FE15EFEA + B349EF5D 76988A36 72FAC47B 0769447B + " + ), + "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!( + " + BD0C6151 2C692C0C B6D041FA 01BB152D 4916A1E7 7AF46AE1 05393011 + BAF38964 DC46A067 0DD125B9 5A981652 236F99D9 B681CBF8 7837EC99 + 6C6DA044 53728610 D0C6DDB5 8B318885 D7D82C7F 8DEB75CE 7BD4FBAA + 37089E6F 9C6059F3 88838E7A 00030B33 1EB76840 910440B1 B27AAEAE + EB4012B7 D7665238 A8E3FB00 4B117B58 + " + ), + "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!("CE38B959 3487DA98 554ED47D 70A7AE5F 462EF019"), + "bad u value" + ); + + assert_eq!( + client + .compute_premaster_secret(&b_pub, &k, &x, &a, &u) + .to_bytes_be(), + hex!( + " + B0DC82BA BCF30674 AE450C02 87745E79 90A3381F 63B387AA F271A10D + 233861E3 59B48220 F7C4693C 9AE12B0A 6F67809F 0876E2D0 13800D6C + 41BB59B6 D5979B5C 00A172B4 A2A5903A 0BDCAF8A 709585EB 2AFAFA8F + 3499B200 210DCC1F 10EB3394 3CD67FC8 8A2F39A4 BE5BEC4E C0A3212D + C346D7E4 74B29EDE 8A469FFE CA686E5A + " + ), + "bad client premaster" + ); + + assert_eq!( + server + .compute_premaster_secret(&a_pub, &v, &u, &b) + .to_bytes_be(), + hex!( + " + B0DC82BA BCF30674 AE450C02 87745E79 90A3381F 63B387AA F271A10D + 233861E3 59B48220 F7C4693C 9AE12B0A 6F67809F 0876E2D0 13800D6C + 41BB59B6 D5979B5C 00A172B4 A2A5903A 0BDCAF8A 709585EB 2AFAFA8F + 3499B200 210DCC1F 10EB3394 3CD67FC8 8A2F39A4 BE5BEC4E C0A3212D + C346D7E4 74B29EDE 8A469FFE CA686E5A + " + ), + "bad server premaster" + ); +} diff --git a/srp/tests/srp.rs b/srp/tests/srp.rs index bf6c30f..b59a4dd 100644 --- a/srp/tests/srp.rs +++ b/srp/tests/srp.rs @@ -1,53 +1,71 @@ use rand::RngCore; use sha2::Sha256; +use srp::client::SrpClient; -use srp::client::{srp_private_key, SrpClient}; use srp::groups::G_2048; -use srp::server::{SrpServer, UserRecord}; +use srp::server::SrpServer; -fn auth_test(reg_pwd: &[u8], auth_pwd: &[u8]) { +fn auth_test(true_pwd: &[u8], auth_pwd: &[u8]) { let mut rng = rand::rngs::OsRng::new().unwrap(); let username = b"alice"; // Client instance creation - let mut a = [0u8; 64]; - rng.fill_bytes(&mut a); - let client = SrpClient::::new(&a, &G_2048); + let client = SrpClient::::new(&G_2048); + + // Begin Registration - // Registration let mut salt = [0u8; 16]; rng.fill_bytes(&mut salt); - let reg_priv_key = srp_private_key::(username, reg_pwd, &salt); - let verif = client.get_password_verifier(®_priv_key); - - // User sends handshake - let a_pub = client.get_a_pub(); - - // Server retrieve user record from db and processes handshake - let user = UserRecord { - username, - salt: &salt, - verifier: &verif, - }; + 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 server = SrpServer::::new(&user, &a_pub, &b, &G_2048).unwrap(); - let (salt, b_pub) = (&user.salt, server.get_b_pub()); + let (salt, b_pub) = (&salt, server.compute_public_ephemeral(&b, &verifier)); + + // Server sends salt and b_pub to client - // Client processes handshake reply - let auth_priv_key = srp_private_key::(username, auth_pwd, salt); - let client2 = client.process_reply(&auth_priv_key, &b_pub).unwrap(); - let proof = client2.get_proof(); + // 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(&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 - println!("Client verification"); - let proof2 = server.verify(&proof).unwrap(); - let server_key = server.get_key(); + let server_verifier = server.process_reply(&b, &verifier, &a_pub).unwrap(); + println!("Client verification on server"); + server_verifier.verify_client(client_proof).unwrap(); + let server_proof = server_verifier.proof(); + let server_key = server_verifier.key(); + + // Server sends server_proof to server (M2) // Client verifies server - println!("Server verification"); - let user_key = client2.verify_server(&proof2).unwrap(); - assert_eq!(server_key, user_key, "server and client keys are not equal"); + println!("Server verification on client"); + client_verifier.verify_server(server_proof).unwrap(); + let client_key = client_verifier.key(); + + // our keys almost must equal but just an extra check + assert_eq!( + server_key, client_key, + "server and client keys are not equal" + ); } #[test]