Skip to content

Commit ef2e809

Browse files
committed
Yield while salting password for PG SASL
To prevent spenting too much time doing synchronous work on the event loop, we yield every few iterations of the hmac-sha256.
1 parent 69bb595 commit ef2e809

File tree

1 file changed

+19
-7
lines changed
  • sqlx-postgres/src/connection

1 file changed

+19
-7
lines changed

sqlx-postgres/src/connection/sasl.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::connection::stream::PgStream;
22
use crate::error::Error;
33
use crate::message::{Authentication, AuthenticationSasl, SaslInitialResponse, SaslResponse};
4+
use crate::rt;
45
use crate::PgConnectOptions;
56
use hmac::{Hmac, Mac};
67
use rand::Rng;
@@ -90,7 +91,8 @@ pub(crate) async fn authenticate(
9091
options.password.as_deref().unwrap_or_default(),
9192
&cont.salt,
9293
cont.iterations,
93-
)?;
94+
)
95+
.await?;
9496

9597
// ClientKey := HMAC(SaltedPassword, "Client Key")
9698
let mut mac = Hmac::<Sha256>::new_from_slice(&salted_password).map_err(Error::protocol)?;
@@ -187,7 +189,7 @@ fn gen_nonce() -> String {
187189
}
188190

189191
// Hi(str, salt, i):
190-
fn hi<'a>(s: &'a str, salt: &'a [u8], iter_count: u32) -> Result<[u8; 32], Error> {
192+
async fn hi<'a>(s: &'a str, salt: &'a [u8], iter_count: u32) -> Result<[u8; 32], Error> {
191193
let mut mac = Hmac::<Sha256>::new_from_slice(s.as_bytes()).map_err(Error::protocol)?;
192194

193195
mac.update(salt);
@@ -196,30 +198,40 @@ fn hi<'a>(s: &'a str, salt: &'a [u8], iter_count: u32) -> Result<[u8; 32], Error
196198
let mut u = mac.finalize_reset().into_bytes();
197199
let mut hi = u;
198200

199-
for _ in 1..iter_count {
201+
for i in 1..iter_count {
200202
mac.update(u.as_slice());
201203
u = mac.finalize_reset().into_bytes();
202204
hi = hi.iter().zip(u.iter()).map(|(&a, &b)| a ^ b).collect();
205+
206+
// For large iteration counts, this process can take a long time and block the event loop.
207+
// It was measured as taking ~50ms for 4096 iterations (the default) on a developer machine.
208+
// If we want to yield every 10-100us (as generally advised for tokio), then we can yield
209+
// every 5 iterations which should be every ~50us.
210+
if i % 5 == 0 {
211+
rt::yield_now().await;
212+
}
203213
}
204214

205215
Ok(hi.into())
206216
}
207217

208218
#[cfg(all(test, not(debug_assertions)))]
209219
#[bench]
210-
fn bench_sasl_hi(b: &mut test::Bencher) {
220+
async fn bench_sasl_hi(b: &mut test::Bencher) {
211221
use test::black_box;
212222

223+
let runtime = tokio::runtime::Runtime::new().unwrap();
224+
213225
let mut rng = rand::thread_rng();
214226
let nonce: Vec<u8> = std::iter::repeat(())
215227
.map(|()| rng.sample(rand::distributions::Alphanumeric))
216228
.take(64)
217229
.collect();
218-
b.iter(|| {
219-
let _ = hi(
230+
b.to_async(&runtime).iter(|| {
231+
hi(
220232
test::black_box("secret_password"),
221233
test::black_box(&nonce),
222234
test::black_box(4096),
223-
);
235+
)
224236
});
225237
}

0 commit comments

Comments
 (0)