Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions srp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ impl<G: Group, D: Digest> Client<G, D> {

let m1 = compute_m1_rfc5054::<D>(
&self.g,
false,
username,
salt,
&a_pub_bytes,
Expand Down
19 changes: 17 additions & 2 deletions srp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub type ServerG4096<D> = Server<G4096, D>;
#[derive(Debug)]
pub struct Server<G: Group, D: Digest> {
g: BoxedMontyForm,
g_no_pad: bool,
d: PhantomData<(G, D)>,
}

Expand All @@ -90,6 +91,19 @@ impl<G: Group, D: Digest> Server<G, D> {
#[must_use]
pub fn new() -> Self {
Self {
g: G::generator(),
g_no_pad: false,
d: PhantomData,
}
}

/// Create a new SRP server instance.
///
/// Set `g_no_pad` to `false` for Apple's HomeKit compatibility.
#[must_use]
pub fn new_with_options(g_no_pad: bool) -> Self {
Self {
g_no_pad,
g: G::generator(),
d: PhantomData,
}
Expand Down Expand Up @@ -156,7 +170,8 @@ impl<G: Group, D: Digest> Server<G, D> {
// Safeguard against malicious A
self.validate_a_pub(&a_pub)?;

let u = compute_u::<D>(a_pub_bytes, &b_pub_bytes);
// [RFC5054]: Section 2.6.
let u = compute_u_padded::<D>(&self.g, a_pub_bytes, &b_pub_bytes);

let premaster_secret = self
.compute_premaster_secret(&a_pub, &v, &u, &b)
Expand All @@ -166,13 +181,13 @@ impl<G: Group, D: Digest> Server<G, D> {

let m1 = compute_m1_rfc5054::<D>(
&self.g,
self.g_no_pad,
username,
salt,
a_pub_bytes,
&b_pub_bytes,
session_key.as_slice(),
);

let m2 = compute_m2::<D>(a_pub_bytes, &m1, session_key.as_slice());

Ok(ServerVerifier {
Expand Down
49 changes: 45 additions & 4 deletions srp/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bigint::{
};
use digest::{Digest, Output};

/// `u = H(PAD(A) | PAD(B))`
/// `u = H(A | B)`
#[must_use]
pub fn compute_u<D: Digest>(a_pub: &[u8], b_pub: &[u8]) -> BoxedUint {
let mut u = D::new();
Expand All @@ -14,6 +14,23 @@ pub fn compute_u<D: Digest>(a_pub: &[u8], b_pub: &[u8]) -> BoxedUint {
BoxedUint::from_be_slice_vartime(&u.finalize())
}

/// `u = H(PAD(A) | PAD(B))`
#[must_use]
pub fn compute_u_padded<D: Digest>(g: &BoxedMontyForm, a_pub: &[u8], b_pub: &[u8]) -> BoxedUint {
let n = g.params().modulus().to_be_bytes();
let mut buf_a = vec![0u8; n.len()];
let mut buf_b = vec![0u8; n.len()];
let l_a = n.len() - a_pub.len();
let l_b = n.len() - b_pub.len();
buf_a[l_a..].copy_from_slice(a_pub);
buf_b[l_b..].copy_from_slice(b_pub);

let mut u = D::new();
u.update(&buf_a);
u.update(&buf_b);
BoxedUint::from_be_slice_vartime(&u.finalize())
}

/// `k = H(N | PAD(g))`
#[must_use]
pub fn compute_k<D: Digest>(g: &BoxedMontyForm) -> BoxedUint {
Expand All @@ -31,7 +48,7 @@ pub fn compute_k<D: Digest>(g: &BoxedMontyForm) -> BoxedUint {

/// `H(N) XOR H(PAD(g))`
#[must_use]
pub fn compute_hash_n_xor_hash_g<D: Digest>(g: &BoxedMontyForm) -> Vec<u8> {
pub fn compute_hash_n_xor_hash_pad_g<D: Digest>(g: &BoxedMontyForm) -> Vec<u8> {
let n = g.params().modulus().to_be_bytes();
let g_bytes = g.retrieve().to_be_bytes();
let mut buf = vec![0u8; n.len()];
Expand All @@ -47,25 +64,49 @@ pub fn compute_hash_n_xor_hash_g<D: Digest>(g: &BoxedMontyForm) -> Vec<u8> {
.collect()
}

/// `H(N) XOR H(g)`
#[must_use]
pub fn compute_hash_n_xor_hash_g<D: Digest>(g: &BoxedMontyForm) -> Vec<u8> {
let n = g.params().modulus().to_be_bytes();
let g_bytes = g.retrieve().to_be_bytes();
let first = g_bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(g_bytes.len().saturating_sub(1));
let g_bytes = &g_bytes[first..];

// H(N) and H(g) as byte strings
let h_n = compute_hash::<D>(&n);
let h_g = compute_hash::<D>(g_bytes);

h_n.iter().zip(h_g.iter()).map(|(&a, &b)| a ^ b).collect()
}

#[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)` following RFC5054
/// `M1 = H(H(N) XOR H(PAD(g)) | H(U) | s | A | B | K)` following RFC5054
/// Or `M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K)` with `g_no_pad` set to true.
#[must_use]
pub fn compute_m1_rfc5054<D: Digest>(
g: &BoxedMontyForm,
g_no_pad: bool,
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>(g));
if g_no_pad {
d.update(compute_hash_n_xor_hash_g::<D>(g));
} else {
d.update(compute_hash_n_xor_hash_pad_g::<D>(g));
}
d.update(compute_hash::<D>(username));
d.update(salt);
d.update(a_pub);
Expand Down