Skip to content

Commit 20031d7

Browse files
make password hashing async aware (#29)
* make password hashing async aware
1 parent 988d0dd commit 20031d7

File tree

8 files changed

+34
-20
lines changed

8 files changed

+34
-20
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
- run: docker compose up -d
5858
- uses: sfackler/actions/rustup@master
5959
with:
60-
version: 1.67.0
60+
version: 1.75.0
6161
- run: echo "::set-output name=version::$(rustc --version)"
6262
id: rust-version
6363
- uses: actions/cache@v1

postgres-protocol/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ memchr = "2.0"
2020
rand = "0.8"
2121
sha2 = "0.10"
2222
stringprep = "0.1"
23+
tokio = { version = "1.0", features = ["rt"] }
24+
25+
[dev-dependencies]
26+
tokio = { version = "1.0", features = ["full"] }

postgres-protocol/src/authentication/sasl.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::io;
99
use std::iter;
1010
use std::mem;
1111
use std::str;
12+
use tokio::task::yield_now;
1213

1314
const NONCE_LENGTH: usize = 24;
1415

@@ -32,7 +33,7 @@ fn normalize(pass: &[u8]) -> Vec<u8> {
3233
}
3334
}
3435

35-
pub(crate) fn hi(str: &[u8], salt: &[u8], i: u32) -> [u8; 32] {
36+
pub(crate) async fn hi(str: &[u8], salt: &[u8], iterations: u32) -> [u8; 32] {
3637
let mut hmac =
3738
Hmac::<Sha256>::new_from_slice(str).expect("HMAC is able to accept all key sizes");
3839
hmac.update(salt);
@@ -41,14 +42,19 @@ pub(crate) fn hi(str: &[u8], salt: &[u8], i: u32) -> [u8; 32] {
4142

4243
let mut hi = prev;
4344

44-
for _ in 1..i {
45+
for i in 1..iterations {
4546
let mut hmac = Hmac::<Sha256>::new_from_slice(str).expect("already checked above");
4647
hmac.update(&prev);
4748
prev = hmac.finalize().into_bytes();
4849

4950
for (hi, prev) in hi.iter_mut().zip(prev) {
5051
*hi ^= prev;
5152
}
53+
// yield every ~250us
54+
// hopefully reduces tail latencies
55+
if i % 1024 == 0 {
56+
yield_now().await
57+
}
5258
}
5359

5460
hi.into()
@@ -200,7 +206,7 @@ impl ScramSha256 {
200206
/// Updates the state machine with the response from the backend.
201207
///
202208
/// This should be called when an `AuthenticationSASLContinue` message is received.
203-
pub fn update(&mut self, message: &[u8]) -> io::Result<()> {
209+
pub async fn update(&mut self, message: &[u8]) -> io::Result<()> {
204210
let (client_nonce, password, channel_binding) =
205211
match mem::replace(&mut self.state, State::Done) {
206212
State::Update {
@@ -227,7 +233,7 @@ impl ScramSha256 {
227233
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidInput, e)),
228234
};
229235

230-
let salted_password = hi(&password, &salt, parsed.iteration_count);
236+
let salted_password = hi(&password, &salt, parsed.iteration_count).await;
231237

232238
let make_key = |name| {
233239
let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)
@@ -481,8 +487,8 @@ mod test {
481487
}
482488

483489
// recorded auth exchange from psql
484-
#[test]
485-
fn exchange() {
490+
#[tokio::test]
491+
async fn exchange() {
486492
let password = "foobar";
487493
let nonce = "9IZ2O01zb9IgiIZ1WJ/zgpJB";
488494

@@ -502,7 +508,7 @@ mod test {
502508
);
503509
assert_eq!(str::from_utf8(scram.message()).unwrap(), client_first);
504510

505-
scram.update(server_first.as_bytes()).unwrap();
511+
scram.update(server_first.as_bytes()).await.unwrap();
506512
assert_eq!(str::from_utf8(scram.message()).unwrap(), client_final);
507513

508514
scram.finish(server_final.as_bytes()).unwrap();

postgres-protocol/src/password/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,19 @@ const SCRAM_DEFAULT_SALT_LEN: usize = 16;
2424
///
2525
/// The client may assume the returned string doesn't contain any
2626
/// special characters that would require escaping in an SQL command.
27-
pub fn scram_sha_256(password: &[u8]) -> String {
27+
pub async fn scram_sha_256(password: &[u8]) -> String {
2828
let mut salt: [u8; SCRAM_DEFAULT_SALT_LEN] = [0; SCRAM_DEFAULT_SALT_LEN];
2929
let mut rng = rand::thread_rng();
3030
rng.fill_bytes(&mut salt);
31-
scram_sha_256_salt(password, salt)
31+
scram_sha_256_salt(password, salt).await
3232
}
3333

3434
// Internal implementation of scram_sha_256 with a caller-provided
3535
// salt. This is useful for testing.
36-
pub(crate) fn scram_sha_256_salt(password: &[u8], salt: [u8; SCRAM_DEFAULT_SALT_LEN]) -> String {
36+
pub(crate) async fn scram_sha_256_salt(
37+
password: &[u8],
38+
salt: [u8; SCRAM_DEFAULT_SALT_LEN],
39+
) -> String {
3740
// Prepare the password, per [RFC
3841
// 4013](https://tools.ietf.org/html/rfc4013), if possible.
3942
//
@@ -58,7 +61,7 @@ pub(crate) fn scram_sha_256_salt(password: &[u8], salt: [u8; SCRAM_DEFAULT_SALT_
5861
};
5962

6063
// salt password
61-
let salted_password = sasl::hi(&prepared, &salt, SCRAM_DEFAULT_ITERATIONS);
64+
let salted_password = sasl::hi(&prepared, &salt, SCRAM_DEFAULT_ITERATIONS).await;
6265

6366
// client key
6467
let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)

postgres-protocol/src/password/test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::password;
22

3-
#[test]
4-
fn test_encrypt_scram_sha_256() {
3+
#[tokio::test]
4+
async fn test_encrypt_scram_sha_256() {
55
// Specify the salt to make the test deterministic. Any bytes will do.
66
let salt: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
77
assert_eq!(
8-
password::scram_sha_256_salt(b"secret", salt),
8+
password::scram_sha_256_salt(b"secret", salt).await,
99
"SCRAM-SHA-256$4096:AQIDBAUGBwgJCgsMDQ4PEA==$8rrDg00OqaiWXJ7p+sCgHEIaBSHY89ZJl3mfIsf32oY=:05L1f+yZbiN8O0AnO40Og85NNRhvzTS57naKRWCcsIA="
1010
);
1111
}

postgres-types/src/chrono_04.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ impl<'a> FromSql<'a> for NaiveDate {
111111
let jd = types::date_from_sql(raw)?;
112112
base()
113113
.date()
114-
.checked_add_signed(Duration::days(i64::from(jd)))
114+
.checked_add_signed(Duration::try_days(i64::from(jd)).unwrap())
115115
.ok_or_else(|| "value too large to decode".into())
116116
}
117117

tokio-postgres/src/connect_raw.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ where
300300

301301
scram
302302
.update(body.data())
303+
.await
303304
.map_err(|e| Error::authentication(e.into()))?;
304305

305306
let mut buf = BytesMut::new();

tokio-postgres/tests/test/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,17 @@ async fn simple_query() {
486486
}
487487
match &messages[2] {
488488
SimpleQueryMessage::Row(row) => {
489-
assert_eq!(row.columns().get(0).map(|c| c.name()), Some("id"));
490-
assert_eq!(row.columns().get(1).map(|c| c.name()), Some("name"));
489+
assert_eq!(row.columns()[0].name(), "id");
490+
assert_eq!(row.columns()[1].name(), "name");
491491
assert_eq!(row.get(0), Some("1"));
492492
assert_eq!(row.get(1), Some("steven"));
493493
}
494494
_ => panic!("unexpected message"),
495495
}
496496
match &messages[3] {
497497
SimpleQueryMessage::Row(row) => {
498-
assert_eq!(row.columns().get(0).map(|c| c.name()), Some("id"));
499-
assert_eq!(row.columns().get(1).map(|c| c.name()), Some("name"));
498+
assert_eq!(row.columns()[0].name(), "id");
499+
assert_eq!(row.columns()[1].name(), "name");
500500
assert_eq!(row.get(0), Some("2"));
501501
assert_eq!(row.get(1), Some("joe"));
502502
}

0 commit comments

Comments
 (0)