Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard implementation option #153

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 53 additions & 12 deletions srp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@
//! let pwd_verifier = client.compute_verifier(username, password, salt);
//! send_registration_data(username, salt, &pwd_verifier);
//! ```
//!
//!
//! Alternatively, you can instantiate the client with options to use the implementation
//! from the specification of SRP (that generates M1 differently) or omit the username
//! when calculating X to be able to authenticate the same users with their different IDs.
//!
//! ```rust
//! let by_the_spec = true;
//! let no_user_in_x = true;
//! let client = crate::srp::client::SrpClient::<sha2::Sha256>::new_with_options(&crate::srp::groups::G_2048, by_the_spec, no_user_in_x);
//! ```

use std::marker::PhantomData;

Expand All @@ -90,11 +101,13 @@ use num_bigint::BigUint;
use subtle::ConstantTimeEq;

use crate::types::{SrpAuthError, SrpGroup};
use crate::utils::{compute_k, compute_m1, compute_m2, compute_u};
use crate::utils::{compute_k, compute_m1, compute_m1_std, compute_m2, compute_u};

/// SRP client state before handshake with the server.
pub struct SrpClient<'a, D: Digest> {
params: &'a SrpGroup,
by_the_spec: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An enum might be nice here for clarity

no_user_in_x: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a separate variable?

d: PhantomData<D>,
}

Expand All @@ -111,6 +124,21 @@ impl<'a, D: Digest> SrpClient<'a, D> {
pub const fn new(params: &'a SrpGroup) -> Self {
Self {
params,
by_the_spec: false,
no_user_in_x: false,
d: PhantomData,
}
}

pub const fn new_with_options(
params: &'a SrpGroup,
by_the_spec: bool,
no_user_in_x: bool,
) -> Self {
Self {
params,
by_the_spec,
no_user_in_x,
d: PhantomData,
}
}
Expand Down Expand Up @@ -170,6 +198,7 @@ impl<'a, D: Digest> SrpClient<'a, D> {
/// Get password verifier (v in RFC5054) for user registration on the server.
#[must_use]
pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec<u8> {
let username = if self.no_user_in_x { &[] } else { username };
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()
Expand Down Expand Up @@ -205,24 +234,36 @@ impl<'a, D: Digest> SrpClient<'a, D> {

let u = compute_u::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be());
let k = compute_k::<D>(self.params);
let username = if self.no_user_in_x { &[] } else { username };
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);

let m1 = compute_m1::<D>(
&a_pub.to_bytes_be(),
&b_pub.to_bytes_be(),
&key.to_bytes_be(),
);
let key = if self.by_the_spec {
let mut u = D::new();
u.update(key.to_bytes_be());
u.finalize().to_vec()
} else {
key.to_bytes_be()
};

let m1 = if self.by_the_spec {
compute_m1_std::<D>(
self.params,
username,
salt,
&a_pub.to_bytes_be(),
&b_pub.to_bytes_be(),
key.as_slice(),
)
} else {
compute_m1::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be(), key.as_slice())
};

let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be());
let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, key.as_slice());

Ok(SrpClientVerifier {
m1,
m2,
key: key.to_bytes_be(),
})
Ok(SrpClientVerifier { m1, m2, key })
}
}

Expand Down
46 changes: 45 additions & 1 deletion srp/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,25 @@ pub fn compute_k<D: Digest>(params: &SrpGroup) -> BigUint {
BigUint::from_bytes_be(d.finalize().as_slice())
}

// H(N) XOR H(PAD(g))
#[must_use]
pub fn compute_hash_n_xor_hash_g<D: Digest>(params: &SrpGroup) -> Vec<u8> {
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 h_n = compute_hash::<D>(&n).to_vec();
let h_g = compute_hash::<D>(&buf).to_vec();

h_n.iter()
.zip(h_g.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect()
}

// 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
#[must_use]
pub fn compute_m1<D: Digest>(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output<D> {
let mut d = D::new();
Expand All @@ -38,6 +55,33 @@ pub fn compute_m1<D: Digest>(a_pub: &[u8], b_pub: &[u8], key: &[u8]) -> Output<D
d.finalize()
}

#[must_use]
pub fn compute_hash<D: Digest>(data: &[u8]) -> Output<D> {
let mut d = D::new();
d.update(data);
d.finalize()
}

// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) this follows the spec
#[must_use]
pub fn compute_m1_std<D: Digest>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Spec" doesn't say which spec, and std in particular is easily confused with the Rust standard library.

Perhaps use RFC5054 instead?

Suggested change
// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) this follows the spec
#[must_use]
pub fn compute_m1_std<D: Digest>(
/// M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K) following RFC5054
#[must_use]
pub fn compute_m1_rfc5054<D: Digest>(

params: &SrpGroup,
username: &[u8],
salt: &[u8],
a_pub: &[u8],
b_pub: &[u8],
key: &[u8],
) -> Output<D> {
let mut d = D::new();
d.update(compute_hash_n_xor_hash_g::<D>(params));
d.update(compute_hash::<D>(username));
d.update(salt);
d.update(a_pub);
d.update(b_pub);
d.update(key);
d.finalize()
}

// M2 = H(A, M1, K)
#[must_use]
pub fn compute_m2<D: Digest>(a_pub: &[u8], m1: &Output<D>, key: &[u8]) -> Output<D> {
Expand Down
152 changes: 152 additions & 0 deletions srp/tests/rfc5054_standard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use hex_literal::hex;
use num_bigint::BigUint;
use sha2::Sha256;
use srp::client::SrpClient;
use srp::groups::G_2048;
use srp::server::SrpServer;
use srp::utils::{compute_hash, compute_k, compute_u};

#[test]
#[allow(clippy::many_single_char_names)]
fn rfc5054_standard() {
let i = b"";
let p = hex!("CCAF1BCA 820E8E6B 392C0EBA 7014CC9C DF49A650 84B39A53 834CC090 92BCDA20");
let s = hex!("BC01D972 31E5A4BC 79171C6D 83783FF2");
let group = &G_2048;

let k = compute_k::<Sha256>(group);

assert_eq!(
k.to_bytes_be(),
hex!("05B9E8EF 059C6B32 EA59FC1D 322D37F0 4AA30BAE 5AA9003B 8321E21D DB04E300"),
"bad k value"
);

let identity_hash = SrpClient::<Sha256>::compute_identity_hash(i, &p);
let x = SrpClient::<Sha256>::compute_x(identity_hash.as_slice(), &s);

assert_eq!(
x.to_bytes_be(),
hex!("FD1709C7 30244792 F33348CB FBBD4AB4 39AC8090 FCDDC474 46244073 0D85ADDB"),
"bad x value"
);

let client = SrpClient::<Sha256>::new_with_options(group, true, true);
let v = client.compute_v(&x);

assert_eq!(
v.to_bytes_be(),
hex!(
"
A2E59E34 4EC9AB6D 611BFC12 4A2E5DC7 46174702 9AC44A6F 6A8DB9E2
7326A5CB E370C469 A20D59CD 63FA13E4 1F0F1968 61A0AA3A 778AB5F5
2A0D57E9 BC3E9494 7ACDA1BD 3E62785D DB51FCE1 D2A34C87 E95CAD5A
30731035 269E72AF 235E4537 62F94011 C965E1D3 F940A196 43B56810
D7CD38AE 4DBB7CFF 80E529FE E33CBB88 C7877096 62342D98 314687BF
0A5B0AE2 E6595B9A DC61B1BE 691E3176 62A01A24 BE963C70 8565694F
575DCEA2 791364C7 465B1BD4 6D8BC9F7 53F3F6E5 C55491F2 080D00D5
40F6E247 53AFB477 C33BC117 A0D6551A 16026D96 22F3AD50 6C379EFD
85A075E7 C6D0DA46 442D6084 7095D43A 3E3C5EC2 6F523479 B3F2902B
641A7B92
"
),
"bad v value"
);

let a = BigUint::from_bytes_be(&hex!(
"
81010101 01010101 01010101 01010101 01010101 01010101 01010101
01010101
"
));

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!(
"
01F9D75A 9DF8AC07 FB3684C4 DDF9ABD9 4CC03C10 1A381976 F16C92B5
8083BB98 4137AD44 7D815819 529E0313 FCC4EDD4 5F31D033 CBC0FB4B
F0CBB75A B0A2A10C 4C0C1C23 A62BA798 AB308818 C94F017C 2015BD3A
4B2334B0 E2125F57 E12A2D31 936856B2 7BE1A615 8D32FC65 48A6B4BB
62E63A13 8EF89664 CC1F43E7 457DE565 E1551F34 29A4B73A 7FAB0D9D
821EF749 7A8A1D84 D637FA8E 443F57CE AF12D0B0 54A67726 3D7C15A4
C88D87B9 136684BA 4AAB3466 524D9A47 30FBE924 1194B3E4 E61EB9A7
67401AA3 E2D66AD0 B07CFF63 B41CD665 E0EC8BC2 75D16A49 E6ECAC4F
2BB3AF76 BC2CBA64 83B665A6 CA804DB8 5093B091 77114E70 9DD8DFB1
7A8B98CD
"
),
"bad a_pub value"
);

let server = SrpServer::<Sha256>::new(group);
let b_pub = server.compute_b_pub(&b, &k, &v);

assert_eq!(
b_pub.to_bytes_be(),
hex!(
"
7F75618C 8C3EC5D7 CDD11D6A C2C24157 0D3254FA 39CFF9C0 DBDD39BC
B6161B2D 12FEE512 0814D17C 6CD56E37 EC9AFC86 8213C60F 672CA6D7
436AEE09 11F59AC6 30DAE4F0 70B15E84 86200B1B 0163900D 2EBB612E
963F1AC8 E083F70F E5484F83 559C11A2 C1936C79 361FCCA7 E9B21192
14416D3F 487C3874 A76D3B23 29A0690B DA774225 104D06B9 418D6207
75D64CE8 54004E07 50F64299 D13B5AA2 3AEBF69B 56814E17 EBC1784B
1881E6BE 651DEABE 2C6E78DE 0A84C032 0DE266B4 1444DC2F 397F0436
8B62BDDE F573D274 EA304F40 B7CDB74A 345AD036 C5ED746F B3F7D627
597FC9C6 A453BF05 D11185E0 EBB74797 D4490903 2D9749F7 8AB8ED63
FCBFCA58
"
),
"bad b_pub value"
);

let u = compute_u::<Sha256>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be());

assert_eq!(
u.to_bytes_be(),
hex!("6B527E30 667D330D 84874755 1E17E271 BA465393 CA48264C D37E59DF 18267B37"),
"bad u value"
);

assert_eq!(
compute_hash::<Sha256>(
&client
.compute_premaster_secret(&b_pub, &k, &x, &a, &u)
.to_bytes_be()
)
.as_slice(),
hex!(
"
CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6
EE706231
"
),
"bad client premaster"
);

assert_eq!(
compute_hash::<Sha256>(
&server
.compute_premaster_secret(&a_pub, &v, &u, &b)
.to_bytes_be()
)
.as_slice(),
hex!(
"
CD315DD4 2652B85B FFDD273E EF98FEDE 3C77E0AE 07898ABE A60FEEA6
EE706231
"
),
"bad server premaster"
);
}