Skip to content

Commit 5b3bd1f

Browse files
tarcierir4v3n6101
andauthored
Reapply "srp: add HomeKit-compatible padding for u and M1/M2" (#285)
This reverts commit 22d096a (#283) This was originally added in #272 then reverted due to bugs which weren't caught in CI at the time, but are now captured by proptests --------- Co-authored-by: r4v3n6101 <raven6107@gmail.com>
1 parent 814725d commit 5b3bd1f

3 files changed

Lines changed: 64 additions & 7 deletions

File tree

srp/src/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl<G: Group, D: Digest> Client<G, D> {
215215
// Safeguard against malicious B
216216
self.validate_b_pub(&b_pub)?;
217217

218-
let u = compute_u::<D>(&a_pub_bytes, b_pub_bytes);
218+
let u = compute_u_padded::<D>(&self.g, &a_pub_bytes, b_pub_bytes);
219219
let k = compute_k::<D>(&self.g);
220220
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
221221
let x = Self::compute_x(identity_hash.as_slice(), salt);
@@ -228,6 +228,7 @@ impl<G: Group, D: Digest> Client<G, D> {
228228

229229
let m1 = compute_m1_rfc5054::<D>(
230230
&self.g,
231+
false,
231232
username,
232233
salt,
233234
&a_pub_bytes,

srp/src/server.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub type ServerG4096<D> = Server<G4096, D>;
8282
#[derive(Debug)]
8383
pub struct Server<G: Group, D: Digest> {
8484
g: BoxedMontyForm,
85+
g_no_pad: bool,
8586
d: PhantomData<(G, D)>,
8687
}
8788

@@ -90,6 +91,19 @@ impl<G: Group, D: Digest> Server<G, D> {
9091
#[must_use]
9192
pub fn new() -> Self {
9293
Self {
94+
g: G::generator(),
95+
g_no_pad: false,
96+
d: PhantomData,
97+
}
98+
}
99+
100+
/// Create a new SRP server instance.
101+
///
102+
/// Set `g_no_pad` to `false` for Apple's HomeKit compatibility.
103+
#[must_use]
104+
pub fn new_with_options(g_no_pad: bool) -> Self {
105+
Self {
106+
g_no_pad,
93107
g: G::generator(),
94108
d: PhantomData,
95109
}
@@ -156,7 +170,8 @@ impl<G: Group, D: Digest> Server<G, D> {
156170
// Safeguard against malicious A
157171
self.validate_a_pub(&a_pub)?;
158172

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

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

167182
let m1 = compute_m1_rfc5054::<D>(
168183
&self.g,
184+
self.g_no_pad,
169185
username,
170186
salt,
171187
a_pub_bytes,
172188
&b_pub_bytes,
173189
session_key.as_slice(),
174190
);
175-
176191
let m2 = compute_m2::<D>(a_pub_bytes, &m1, session_key.as_slice());
177192

178193
Ok(ServerVerifier {

srp/src/utils.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bigint::{
55
};
66
use digest::{Digest, Output};
77

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

17+
/// `u = H(PAD(A) | PAD(B))`
18+
#[must_use]
19+
pub fn compute_u_padded<D: Digest>(g: &BoxedMontyForm, a_pub: &[u8], b_pub: &[u8]) -> BoxedUint {
20+
let n = g.params().modulus().to_be_bytes();
21+
let mut buf_a = vec![0u8; n.len()];
22+
let mut buf_b = vec![0u8; n.len()];
23+
let l_a = n.len() - a_pub.len();
24+
let l_b = n.len() - b_pub.len();
25+
buf_a[l_a..].copy_from_slice(a_pub);
26+
buf_b[l_b..].copy_from_slice(b_pub);
27+
28+
let mut u = D::new();
29+
u.update(&buf_a);
30+
u.update(&buf_b);
31+
BoxedUint::from_be_slice_vartime(&u.finalize())
32+
}
33+
1734
/// `k = H(N | PAD(g))`
1835
#[must_use]
1936
pub fn compute_k<D: Digest>(g: &BoxedMontyForm) -> BoxedUint {
@@ -31,7 +48,7 @@ pub fn compute_k<D: Digest>(g: &BoxedMontyForm) -> BoxedUint {
3148

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

67+
/// `H(N) XOR H(g)`
68+
#[must_use]
69+
pub fn compute_hash_n_xor_hash_g<D: Digest>(g: &BoxedMontyForm) -> Vec<u8> {
70+
let n = g.params().modulus().to_be_bytes();
71+
let g_bytes = g.retrieve().to_be_bytes();
72+
let first = g_bytes
73+
.iter()
74+
.position(|&b| b != 0)
75+
.unwrap_or(g_bytes.len().saturating_sub(1));
76+
let g_bytes = &g_bytes[first..];
77+
78+
// H(N) and H(g) as byte strings
79+
let h_n = compute_hash::<D>(&n);
80+
let h_g = compute_hash::<D>(g_bytes);
81+
82+
h_n.iter().zip(h_g.iter()).map(|(&a, &b)| a ^ b).collect()
83+
}
84+
5085
#[must_use]
5186
pub fn compute_hash<D: Digest>(data: &[u8]) -> Output<D> {
5287
let mut d = D::new();
5388
d.update(data);
5489
d.finalize()
5590
}
5691

57-
/// `M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K)` following RFC5054
92+
/// `M1 = H(H(N) XOR H(PAD(g)) | H(U) | s | A | B | K)` following RFC5054
93+
/// Or `M1 = H(H(N) XOR H(g) | H(U) | s | A | B | K)` with `g_no_pad` set to true.
5894
#[must_use]
5995
pub fn compute_m1_rfc5054<D: Digest>(
6096
g: &BoxedMontyForm,
97+
g_no_pad: bool,
6198
username: &[u8],
6299
salt: &[u8],
63100
a_pub: &[u8],
64101
b_pub: &[u8],
65102
key: &[u8],
66103
) -> Output<D> {
67104
let mut d = D::new();
68-
d.update(compute_hash_n_xor_hash_g::<D>(g));
105+
if g_no_pad {
106+
d.update(compute_hash_n_xor_hash_g::<D>(g));
107+
} else {
108+
d.update(compute_hash_n_xor_hash_pad_g::<D>(g));
109+
}
69110
d.update(compute_hash::<D>(username));
70111
d.update(salt);
71112
d.update(a_pub);

0 commit comments

Comments
 (0)