easy_srp/
client.rs

1//! Client-side SRP implementation
2//! 
3//! Contains the `ClientAuthenticationWorkflow` for implementing the client
4//! side of the SRP authentication.
5//! It also includes the `ClientRegistrationWorkflow` for generating a
6//! `salt` and `verifier` from the user's `username` and `password`.
7
8use base64::Engine;
9use digest::Digest;
10use srp::client::{SrpClient, SrpClientVerifier};
11use getrandom::getrandom;
12
13#[cfg(feature = "serde")]
14use serde::{Serialize, Deserialize};
15
16use crate::types::Result;
17
18/// Client-side of the authentication workflow
19/// 
20/// This struct should be used on the client side to authenticate against a
21/// compatible SRP6a server.
22/// Digest `D` specifies the digest algorithm used and therefore also implicitly
23/// defines the key size of the common secret key.
24pub struct ClientAuthenticationWorkflow<'c, D: Digest> {
25  delegate: SrpClient<'c, D>,
26}
27
28impl<'c, D: Digest> ClientAuthenticationWorkflow<'c, D> {
29  /// Create a new instance
30  /// 
31  /// `group` specifies the group (safe prime `n` and generator `g`) used
32  /// for SRP.
33  pub fn new(group: &'c crate::groups::SrpGroup) -> Self {
34    return Self {
35      delegate: SrpClient::<'c, D>::new(group),
36    };
37  }
38
39  /// step 1: create ephemeral key
40  /// 
41  /// The generated private key `client_private_a` must not be exposed, but needs to be stored
42  /// for future steps.
43  /// The generated public key `client_public_a` and the `username` of the
44  /// user should be sent to the server.
45  /// 
46  /// keep: `client_private_a`
47  /// send: `(username, client_public_a)` to the server
48  pub fn step1(&self) -> Result<ClientStep1Result> {
49    let mut client_private_a = vec![0 as u8; 128];
50    getrandom(client_private_a.as_mut_slice())?;
51
52    let client_public_a = self.delegate.compute_public_ephemeral(client_private_a.as_slice());
53    
54    return Ok(ClientStep1Result {
55      client_private_a,
56      client_public_a
57    });
58  }
59
60  /// step 3: process server reply and send proof
61  /// 
62  /// receive: `(salt, server_public_b)` from server
63  /// keep: `ClientStep3Result` struct or at least `key`
64  /// send: `proof` to server.
65  pub fn step3(&self, params: ClientStep3Params) -> Result<ClientStep3Result<D>> {
66    let verifier = self.delegate.process_reply(
67      params.client_a, 
68      params.username.as_bytes(), 
69      params.password.as_bytes(), 
70      params.salt, 
71      params.server_public_b
72    )?;
73
74    return Ok(ClientStep3Result {
75      verifier
76    });
77  }
78
79  // step5 would be to verify the server proof with the result of step3.
80}
81
82/// Result of step1
83/// 
84/// Step 1 generates the asymetric client ephemeral key.
85#[derive(Debug, Clone)]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87pub struct ClientStep1Result {
88  /// The private key of the client's ephemeral key
89  #[serde(with = "crate::util::base64_vec_u8")]
90  pub client_private_a: Vec<u8>,
91
92  /// The public key of the client's ephemeral key
93  #[serde(with = "crate::util::base64_vec_u8")]
94  pub client_public_a: Vec<u8>,
95}
96
97/// Params of step 3
98/// 
99/// Contains the parameters used for step 3 of the protocol.
100#[derive(Debug, Clone)]
101#[cfg_attr(feature = "serde", derive(Serialize))]
102pub struct ClientStep3Params<'p> {
103  /// Client's secret ephemeral key
104  #[serde(with = "crate::util::base64_u8")]
105  pub client_a: &'p [u8],
106
107  /// The username (identity) of the user
108  pub username: String,
109
110  /// The password of the user
111  pub password: String,
112
113  /// The salt received from the server in step 2
114  #[serde(with = "crate::util::base64_u8")]
115  pub salt: &'p [u8],
116
117  /// The server's public ephemeral key received from the server in step 2
118  #[serde(with = "crate::util::base64_u8")]
119  pub server_public_b: &'p [u8],
120}
121
122/// Result of step 3
123/// 
124/// The result of step 3 can be used to verify the server's proof and
125/// to generate a client proof, as well as to generate the common key.
126pub struct ClientStep3Result<D: Digest> {
127  verifier: SrpClientVerifier<D>,
128}
129
130impl<D: Digest> ClientStep3Result<D> {
131  /// Get the common secret key
132  /// 
133  /// SRP6a authentication results in a secret key shared by the client
134  /// and server.
135  pub fn key(&self) -> &[u8] {
136    return self.verifier.key();
137  }
138
139  /// Generate a client proof
140  /// 
141  /// The generated value can be used to proof to the server that the client
142  /// is using the same secret key.
143  pub fn proof(&self) -> &[u8] {
144    return self.verifier.proof();
145  }
146
147  /// Verify the server's proof
148  /// 
149  /// This method verifies the the server's `proof` is correct, thereby 
150  /// verifying that the server uses the same secret key as the client.
151  pub fn verify_server(&self, proof: &[u8]) -> Result<()> {
152    return self.verifier.verify_server(proof)
153      .or_else(|err| Err(err.into()));
154  }
155}
156
157/// Workflow for generating the salt and the verifier for the server
158/// 
159/// In SRP6a, the server stores as salt and a verifier. The salt is a random
160/// binary value that will be sent to the client during authentication.
161/// The verifier will be stored secretly on the server and used to derive the
162/// common secret key during authentication.
163pub struct ClientRegistrationWorkflow<'c, D: Digest> {
164  delegate: SrpClient<'c, D>,
165}
166
167impl<'c, D: Digest> ClientRegistrationWorkflow<'c, D> {
168  /// Create a new instance
169  /// 
170  /// `group` specifies the group (safe prime `n` and generator `g`) used
171  /// for SRP.
172  pub fn new(group: &'c crate::groups::SrpGroup) -> Self {
173    return Self {
174      delegate: SrpClient::<'c, D>::new(group),
175    };
176  }
177
178  /// Generates the verifier
179  /// 
180  /// If `None` is provided as `salt`, a new random salt value is generated.
181  /// A verifier is then generated from the given user credentials `username`
182  /// and `password`.
183  pub fn generate_verifier(&self, params: GenerateVerifierParams) -> Result<Verifier> {
184    let GenerateVerifierParams {
185      username,
186      password,
187      salt
188    } = params;
189
190    let salt = if let Some(salt) = salt {
191      salt
192    } else {
193      let mut random_salt = vec![0 as u8; 32];
194      getrandom::getrandom(random_salt.as_mut_slice())?;
195      random_salt
196    };
197    
198    let verifier = self.delegate.compute_verifier(
199      username.as_bytes(), 
200      password.as_bytes(), 
201      salt.as_slice());
202
203    return Ok(Verifier {
204      username,
205      salt,
206      verifier
207    });
208  }
209}
210
211/// Parameters for verifier generation
212#[derive(Debug, Clone)]
213#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
214pub struct GenerateVerifierParams {
215  /// The username (identity) of the user
216  pub username: String,
217
218  /// The secret password
219  /// 
220  /// The password will never be sent to the server.
221  pub password: String,
222
223  /// The salt
224  /// 
225  /// This should be a random value. It may be `None`, resulting in generation
226  /// of a random salt value by the `generate_verifier` method.
227  #[serde(with = "crate::util::base64_opt_vec_u8")]
228  pub salt: Option<Vec<u8>>,
229}
230
231/// A generated verifier
232/// 
233/// All values in this verifier need to be sent to and stored on the server.
234#[derive(Debug, Clone)]
235#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
236pub struct Verifier {
237  pub username: String,
238  #[serde(with = "crate::util::base64_vec_u8")]
239  pub salt: Vec<u8>,
240  #[serde(with = "crate::util::base64_vec_u8")]
241  pub verifier: Vec<u8>,
242}
243
244#[cfg(feature = "base64")]
245impl Verifier {
246  pub fn salt_base64(&self) -> String {
247    let engine = base64::engine::general_purpose::STANDARD_NO_PAD;
248    let result = engine.encode(self.salt.as_slice());
249    return result;
250  }
251
252  pub fn verifier_base64(&self) -> String {
253    let engine = base64::engine::general_purpose::STANDARD_NO_PAD;
254    let result = engine.encode(self.verifier.as_slice());
255    return result;
256  }
257}