Skip to content

Commit 04e3edb

Browse files
committed
srp: refactor to eliminate statics
Previously the crate used `once_cell` (and before that `lazy_static`) but the relevant group parameters can easily be stored as associated constants of traits, then turned into `BoxedMontyForm` when the `Client` or `Server` type is initialized. This gets rid of the `once_cell` dependency which makes it much simpler for the crate to actually support `no_std`(+`alloc`) environments. Also adds CI for `no_std`.
1 parent 838f2bd commit 04e3edb

File tree

11 files changed

+195
-194
lines changed

11 files changed

+195
-194
lines changed

.github/workflows/srp.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ env:
1919
RUSTFLAGS: "-Dwarnings"
2020

2121
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
strategy:
25+
matrix:
26+
rust:
27+
- 1.85.0 # MSRV
28+
- stable
29+
target:
30+
- thumbv7em-none-eabi
31+
- wasm32-unknown-unknown
32+
steps:
33+
- uses: actions/checkout@v6
34+
- uses: dtolnay/rust-toolchain@master
35+
with:
36+
toolchain: ${{ matrix.rust }}
37+
targets: ${{ matrix.target }}
38+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features
39+
2240
test:
2341
runs-on: ubuntu-latest
2442
strategy:

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

srp/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ rust-version = "1.85"
1515
[dependencies]
1616
crypto-bigint = { version = "0.7.0-rc.17", features = ["alloc"] }
1717
digest = "0.11.0-rc.5"
18-
once_cell = "1"
19-
subtle = "2.4"
18+
subtle = { version = "2.4", default-features = false }
2019

2120
[dev-dependencies]
2221
getrandom = { version = "0.4.0-rc.0", features = ["sys_rng"] }

srp/src/client.rs

Lines changed: 63 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
//! password hashing algorithm instead (e.g. PBKDF2, argon2 or scrypt).
99
//!
1010
//! ```rust
11-
//! use crate::srp::groups::G_2048;
11+
//! use srp::groups::G2048;
1212
//! use sha2::Sha256; // Note: You should probably use a proper password KDF
13-
//! # use crate::srp::client::Client;
13+
//! # use srp::client::Client;
1414
//!
15-
//! let client = Client::<Sha256>::new(&G_2048);
15+
//! let client = Client::<G2048, Sha256>::new();
1616
//! ```
1717
//!
1818
//! Next send handshake data (username and `a_pub`) to the server and receive
1919
//! `salt` and `b_pub`:
2020
//!
2121
//! ```rust
22-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
22+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
2323
//! # fn server_response()-> (Vec<u8>, Vec<u8>) { (vec![], vec![]) }
2424
//!
2525
//! let mut a = [0u8; 64];
@@ -32,7 +32,7 @@
3232
//! `process_reply` can return error in case of malicious `b_pub`.
3333
//!
3434
//! ```rust
35-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
35+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
3636
//! # let a = [0u8; 64];
3737
//! # let username = b"username";
3838
//! # let password = b"password";
@@ -47,8 +47,8 @@
4747
//! send it to the server and verify server proof in the reply. Note that
4848
//! `verify_server` method will return error in case of incorrect server reply.
4949
//!
50-
//! ```rust
51-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
50+
//! ```ignore
51+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
5252
//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap();
5353
//! # 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] }
5454
//!
@@ -60,7 +60,7 @@
6060
//! `key` contains shared secret key between user and the server. You can extract shared secret
6161
//! key using `key()` method.
6262
//! ```rust
63-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
63+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
6464
//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap();
6565
//!
6666
//! verifier.key();
@@ -72,8 +72,8 @@
7272
//! M2 differently and also the `verify_server` method will return a shared session
7373
//! key in case of correct server data.
7474
//!
75-
//! ```rust
76-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
75+
//! ```ignore
76+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
7777
//! # let verifier = client.process_reply_rfc5054(b"", b"", b"", b"", b"1").unwrap();
7878
//! # fn send_proof(_: &[u8]) -> Vec<u8> { vec![10, 215, 214, 40, 136, 200, 122, 121, 68, 160, 38, 32, 85, 82, 128, 30, 199, 194, 126, 222, 61, 55, 2, 28, 120, 181, 155, 102, 141, 65, 17, 64] }
7979
//!
@@ -89,7 +89,7 @@
8989
//! Man-in-the-middle (MITM) attack for registration.
9090
//!
9191
//! ```rust
92-
//! # let client = crate::srp::client::Client::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
92+
//! # let client = srp::Client::<srp::G2048, sha2::Sha256>::new();
9393
//! # let username = b"username";
9494
//! # let password = b"password";
9595
//! # let salt = b"salt";
@@ -102,7 +102,7 @@
102102
103103
use alloc::{borrow::ToOwned, vec::Vec};
104104
use core::marker::PhantomData;
105-
use crypto_bigint::{BoxedUint, ConcatenatingMul, Resize, modular::BoxedMontyForm};
105+
use crypto_bigint::{BoxedUint, ConcatenatingMul, Odd, Resize, modular::BoxedMontyForm};
106106
use digest::{Digest, Output};
107107
use subtle::ConstantTimeEq;
108108

@@ -113,42 +113,23 @@ use crate::{
113113
};
114114

115115
/// SRP client state before handshake with the server.
116-
pub struct Client<D: Digest> {
117-
params: &'static Group,
116+
pub struct Client<G: Group, D: Digest> {
117+
g: BoxedMontyForm,
118118
username_in_x: bool,
119-
d: PhantomData<D>,
119+
d: PhantomData<(G, D)>,
120120
}
121121

122-
/// SRP client state after handshake with the server.
123-
pub struct ClientVerifier<D: Digest> {
124-
m1: Output<D>,
125-
m2: Output<D>,
126-
key: Vec<u8>,
127-
}
128-
129-
/// RFC 5054 SRP client state after handshake with the server.
130-
pub struct ClientVerifierRfc5054<D: Digest> {
131-
m1: Output<D>,
132-
m2: Output<D>,
133-
key: Vec<u8>,
134-
session_key: Vec<u8>,
135-
}
136-
137-
impl<D: Digest> Client<D> {
122+
impl<G: Group, D: Digest> Client<G, D> {
138123
/// Create new SRP client instance.
139124
#[must_use]
140-
pub const fn new(params: &'static Group) -> Self {
141-
Self {
142-
params,
143-
username_in_x: true,
144-
d: PhantomData,
145-
}
125+
pub fn new() -> Self {
126+
Self::new_with_options(true)
146127
}
147128

148129
#[must_use]
149-
pub const fn new_with_options(params: &'static Group, username_in_x: bool) -> Self {
130+
pub fn new_with_options(username_in_x: bool) -> Self {
150131
Self {
151-
params,
132+
g: G::g(),
152133
username_in_x,
153134
d: PhantomData,
154135
}
@@ -157,7 +138,7 @@ impl<D: Digest> Client<D> {
157138
// v = g^x % N
158139
#[must_use]
159140
pub fn compute_g_x(&self, x: &BoxedUint) -> BoxedUint {
160-
self.params.g.pow(x).retrieve()
141+
self.g.pow(x).retrieve()
161142
}
162143

163144
// H(<username> | ":" | <raw password>)
@@ -189,17 +170,11 @@ impl<D: Digest> Client<D> {
189170
a: &BoxedUint,
190171
u: &BoxedUint,
191172
) -> BoxedUint {
192-
let b_pub = BoxedMontyForm::new(
193-
b_pub.resize(self.params.n.modulus().bits_precision()),
194-
&self.params.n,
195-
);
196-
let k = BoxedMontyForm::new(
197-
k.resize(self.params.n.modulus().bits_precision()),
198-
&self.params.n,
199-
);
173+
let b_pub = self.monty_form(b_pub);
174+
let k = self.monty_form(k);
200175

201176
// B - kg^x
202-
let base = b_pub - k * self.params.g.pow(x);
177+
let base = b_pub - k * self.g.pow(x);
203178

204179
// S = (B - kg^x) ^ (a + ux)
205180
// or
@@ -248,7 +223,7 @@ impl<D: Digest> Client<D> {
248223
&a_pub.to_be_bytes_trimmed_vartime(),
249224
&b_pub.to_be_bytes_trimmed_vartime(),
250225
);
251-
let k = compute_k::<D>(self.params);
226+
let k = compute_k::<D>(&self.g);
252227
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
253228
let x = Self::compute_x(identity_hash.as_slice(), salt);
254229

@@ -296,7 +271,7 @@ impl<D: Digest> Client<D> {
296271
&a_pub.to_be_bytes_trimmed_vartime(),
297272
&b_pub.to_be_bytes_trimmed_vartime(),
298273
);
299-
let k = compute_k::<D>(self.params);
274+
let k = compute_k::<D>(&self.g);
300275
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
301276
let x = Self::compute_x(identity_hash.as_slice(), salt);
302277

@@ -307,7 +282,7 @@ impl<D: Digest> Client<D> {
307282
let session_key = compute_hash::<D>(&premaster_secret);
308283

309284
let m1 = compute_m1_rfc5054::<D>(
310-
self.params,
285+
&self.g,
311286
username,
312287
salt,
313288
&a_pub.to_be_bytes_trimmed_vartime(),
@@ -329,23 +304,47 @@ impl<D: Digest> Client<D> {
329304
})
330305
}
331306

307+
/// Conditionally include username in the computation of `x`.
308+
fn identity_username<'a>(&self, username: &'a [u8]) -> &'a [u8] {
309+
if self.username_in_x { username } else { &[] }
310+
}
311+
312+
/// Convert an integer into the Montgomery domain, returning a [`BoxedMontyForm`] modulo `N`.
313+
fn monty_form(&self, x: &BoxedUint) -> BoxedMontyForm {
314+
let precision = self.n().bits_precision();
315+
BoxedMontyForm::new(x.resize(precision), self.g.params())
316+
}
317+
318+
/// Get the modulus `N`.
319+
fn n(&self) -> &Odd<BoxedUint> {
320+
self.g.params().modulus()
321+
}
322+
332323
/// Ensure `b_pub` is non-zero and therefore not maliciously crafted.
333324
fn validate_b_pub(&self, b_pub: &BoxedUint) -> Result<(), AuthError> {
334-
let n = self.params.n.modulus().as_nz_ref();
325+
let n = self.n().as_nz_ref();
335326

336327
if (b_pub.resize(n.bits_precision()) % n).is_zero().into() {
337328
return Err(AuthError::IllegalParameter("b_pub".to_owned()));
338329
}
339330

340331
Ok(())
341332
}
333+
}
342334

343-
/// Conditionally include username in the computation of `x`.
344-
fn identity_username<'a>(&self, username: &'a [u8]) -> &'a [u8] {
345-
if self.username_in_x { username } else { &[] }
335+
impl<G: Group, D: Digest> Default for Client<G, D> {
336+
fn default() -> Self {
337+
Self::new()
346338
}
347339
}
348340

341+
/// SRP client state after handshake with the server.
342+
pub struct ClientVerifier<D: Digest> {
343+
m1: Output<D>,
344+
m2: Output<D>,
345+
key: Vec<u8>,
346+
}
347+
349348
impl<D: Digest> ClientVerifier<D> {
350349
/// Get shared secret key without authenticating server, e.g. for using with
351350
/// authenticated encryption modes. DO NOT USE this method without
@@ -369,6 +368,14 @@ impl<D: Digest> ClientVerifier<D> {
369368
}
370369
}
371370

371+
/// RFC 5054 SRP client state after handshake with the server.
372+
pub struct ClientVerifierRfc5054<D: Digest> {
373+
m1: Output<D>,
374+
m2: Output<D>,
375+
key: Vec<u8>,
376+
session_key: Vec<u8>,
377+
}
378+
372379
impl<D: Digest> ClientVerifierRfc5054<D> {
373380
/// Get shared secret key without authenticating server, e.g. for using with
374381
/// authenticated encryption modes. DO NOT USE this method without

0 commit comments

Comments
 (0)