easy-srp 0.3.0

easy-srp wraps the rust srp crate and provides an easy to use API.
Documentation
//! 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<ClientStep1Result> {
    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<ClientStep3Result<D>> {
    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<u8>,

  /// The public key of the client's ephemeral key
  #[serde(with = "crate::util::base64_vec_u8")]
  pub client_public_a: Vec<u8>,
}

/// 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<D: Digest> {
  verifier: SrpClientVerifier<D>,
}

impl<D: Digest> ClientStep3Result<D> {
  /// 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<Verifier> {
    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<Vec<u8>>,
}

/// 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<u8>,
  #[serde(with = "crate::util::base64_vec_u8")]
  pub verifier: Vec<u8>,
}

#[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;
  }
}