From 80a286113a79f54335a70df44bfbc3e72b5efd26 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Mon, 30 Aug 2021 18:27:56 +0200 Subject: [PATCH] Prevent user enumeration via timing attacks. --- src/routes/newsletters.rs | 20 +++++++++++++++----- tests/api/helpers.rs | 15 ++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/routes/newsletters.rs b/src/routes/newsletters.rs index a21d3ca..d7b0633 100644 --- a/src/routes/newsletters.rs +++ b/src/routes/newsletters.rs @@ -110,10 +110,20 @@ async fn validate_credentials( credentials: Credentials, pool: &PgPool, ) -> Result { - let (user_id, expected_password_hash) = get_stored_credentials(&credentials.username, &pool) - .await - .map_err(PublishError::UnexpectedError)? - .ok_or_else(|| PublishError::AuthError(anyhow::anyhow!("Unknown username.")))?; + let mut user_id = None; + let mut expected_password_hash = "$argon2id$v=19$m=15000,t=2,p=1$\ + gZiV/M1gPc22ElAH/Jh1Hw$\ + CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno" + .to_string(); + + if let Some((stored_user_id, stored_password_hash)) = + get_stored_credentials(&credentials.username, &pool) + .await + .map_err(PublishError::UnexpectedError)? + { + user_id = Some(stored_user_id); + expected_password_hash = stored_password_hash; + } spawn_blocking_with_tracing(move || { verify_password_hash(expected_password_hash, credentials.password) @@ -122,7 +132,7 @@ async fn validate_credentials( .context("Failed to spawn blocking task.") .map_err(PublishError::UnexpectedError)??; - Ok(user_id) + user_id.ok_or_else(|| PublishError::AuthError(anyhow::anyhow!("Unknown username."))) } #[tracing::instrument( diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index 8b0b09c..1a32163 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -1,5 +1,5 @@ use argon2::password_hash::SaltString; -use argon2::{Argon2, PasswordHasher}; +use argon2::{Algorithm, Argon2, Params, PasswordHasher, Version}; use once_cell::sync::Lazy; use sqlx::{Connection, Executor, PgConnection, PgPool}; use uuid::Uuid; @@ -163,10 +163,15 @@ impl TestUser { async fn store(&self, pool: &PgPool) { let salt = SaltString::generate(&mut rand::thread_rng()); - let password_hash = Argon2::default() - .hash_password(self.password.as_bytes(), &salt) - .unwrap() - .to_string(); + // Match production parameters + let password_hash = Argon2::new( + Algorithm::Argon2id, + Version::V0x13, + Params::new(15000, 2, 1, None).unwrap(), + ) + .hash_password(self.password.as_bytes(), &salt) + .unwrap() + .to_string(); sqlx::query!( "INSERT INTO users (user_id, username, password_hash) VALUES ($1, $2, $3)",