//! Client-side SRP implementation //! //! Contains the `ClientAuthenticationWorkflow` for implementing the client //! side of the SRP authentication. //! It also includes the `ClientRegistrationWorkflow` for generating a //! `salt` and `verifier` from the user's `username` and `password`. use base64::Engine; use digest::Digest; use srp::client::{SrpClient, SrpClientVerifier}; use getrandom::getrandom; #[cfg(feature = "serde")] use serde::{Serialize, Deserialize}; use crate::types::Result; /// Client-side of the authentication workflow /// /// This struct should be used on the client side to authenticate against a /// compatible SRP6a server. /// Digest `D` specifies the digest algorithm used and therefore also implicitly /// defines the key size of the common secret key. pub struct ClientAuthenticationWorkflow<'c, D: Digest> { delegate: SrpClient<'c, D>, } impl<'c, D: Digest> ClientAuthenticationWorkflow<'c, D> { /// Create a new instance /// /// `group` specifies the group (safe prime `n` and generator `g`) used /// for SRP. pub fn new(group: &'c crate::groups::SrpGroup) -> Self { return Self { delegate: SrpClient::<'c, D>::new(group), }; } /// step 1: create ephemeral key /// /// The generated private key `client_private_a` must not be exposed, but needs to be stored /// for future steps. /// The generated public key `client_public_a` and the `username` of the /// user should be sent to the server. /// /// keep: `client_private_a` /// send: `(username, client_public_a)` to the server pub fn step1(&self) -> Result { let mut client_private_a = vec![0 as u8; 128]; getrandom(client_private_a.as_mut_slice())?; let client_public_a = self.delegate.compute_public_ephemeral(client_private_a.as_slice()); return Ok(ClientStep1Result { client_private_a, client_public_a }); } /// step 3: process server reply and send proof /// /// receive: `(salt, server_public_b)` from server /// keep: `ClientStep3Result` struct or at least `key` /// send: `proof` to server. pub fn step3(&self, params: ClientStep3Params) -> Result> { let verifier = self.delegate.process_reply( params.client_a, params.username.as_bytes(), params.password.as_bytes(), params.salt, params.server_public_b )?; return Ok(ClientStep3Result { verifier }); } // step5 would be to verify the server proof with the result of step3. } /// Result of step1 /// /// Step 1 generates the asymetric client ephemeral key. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ClientStep1Result { /// The private key of the client's ephemeral key #[serde(with = "crate::util::base64_vec_u8")] pub client_private_a: Vec, /// The public key of the client's ephemeral key #[serde(with = "crate::util::base64_vec_u8")] pub client_public_a: Vec, } /// Params of step 3 /// /// Contains the parameters used for step 3 of the protocol. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct ClientStep3Params<'p> { /// Client's secret ephemeral key #[serde(with = "crate::util::base64_u8")] pub client_a: &'p [u8], /// The username (identity) of the user pub username: String, /// The password of the user pub password: String, /// The salt received from the server in step 2 #[serde(with = "crate::util::base64_u8")] pub salt: &'p [u8], /// The server's public ephemeral key received from the server in step 2 #[serde(with = "crate::util::base64_u8")] pub server_public_b: &'p [u8], } /// Result of step 3 /// /// The result of step 3 can be used to verify the server's proof and /// to generate a client proof, as well as to generate the common key. pub struct ClientStep3Result { verifier: SrpClientVerifier, } impl ClientStep3Result { /// Get the common secret key /// /// SRP6a authentication results in a secret key shared by the client /// and server. pub fn key(&self) -> &[u8] { return self.verifier.key(); } /// Generate a client proof /// /// The generated value can be used to proof to the server that the client /// is using the same secret key. pub fn proof(&self) -> &[u8] { return self.verifier.proof(); } /// Verify the server's proof /// /// This method verifies the the server's `proof` is correct, thereby /// verifying that the server uses the same secret key as the client. pub fn verify_server(&self, proof: &[u8]) -> Result<()> { return self.verifier.verify_server(proof) .or_else(|err| Err(err.into())); } } /// Workflow for generating the salt and the verifier for the server /// /// In SRP6a, the server stores as salt and a verifier. The salt is a random /// binary value that will be sent to the client during authentication. /// The verifier will be stored secretly on the server and used to derive the /// common secret key during authentication. pub struct ClientRegistrationWorkflow<'c, D: Digest> { delegate: SrpClient<'c, D>, } impl<'c, D: Digest> ClientRegistrationWorkflow<'c, D> { /// Create a new instance /// /// `group` specifies the group (safe prime `n` and generator `g`) used /// for SRP. pub fn new(group: &'c crate::groups::SrpGroup) -> Self { return Self { delegate: SrpClient::<'c, D>::new(group), }; } /// Generates the verifier /// /// If `None` is provided as `salt`, a new random salt value is generated. /// A verifier is then generated from the given user credentials `username` /// and `password`. pub fn generate_verifier(&self, params: GenerateVerifierParams) -> Result { let GenerateVerifierParams { username, password, salt } = params; let salt = if let Some(salt) = salt { salt } else { let mut random_salt = vec![0 as u8; 32]; getrandom::getrandom(random_salt.as_mut_slice())?; random_salt }; let verifier = self.delegate.compute_verifier( username.as_bytes(), password.as_bytes(), salt.as_slice()); return Ok(Verifier { username, salt, verifier }); } } /// Parameters for verifier generation #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GenerateVerifierParams { /// The username (identity) of the user pub username: String, /// The secret password /// /// The password will never be sent to the server. pub password: String, /// The salt /// /// This should be a random value. It may be `None`, resulting in generation /// of a random salt value by the `generate_verifier` method. #[serde(with = "crate::util::base64_opt_vec_u8")] pub salt: Option>, } /// A generated verifier /// /// All values in this verifier need to be sent to and stored on the server. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Verifier { pub username: String, #[serde(with = "crate::util::base64_vec_u8")] pub salt: Vec, #[serde(with = "crate::util::base64_vec_u8")] pub verifier: Vec, } #[cfg(feature = "base64")] impl Verifier { pub fn salt_base64(&self) -> String { let engine = base64::engine::general_purpose::STANDARD_NO_PAD; let result = engine.encode(self.salt.as_slice()); return result; } pub fn verifier_base64(&self) -> String { let engine = base64::engine::general_purpose::STANDARD_NO_PAD; let result = engine.encode(self.verifier.as_slice()); return result; } }