From 7d515138d5c76a653cfadbb0e1900770fde252d3 Mon Sep 17 00:00:00 2001 From: Luca Palmieri Date: Sun, 29 Aug 2021 22:09:43 +0200 Subject: [PATCH] Implement password hashing using Argon2, with PHC string format as storage encoding. --- Cargo.lock | 52 ++++++++++++++++++- Cargo.toml | 1 + .../20210829175741_add_salt_to_users.sql | 1 + .../20210829200701_remove_salt_from_users.sql | 1 + src/routes/newsletters.rs | 36 ++++++++----- 5 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 migrations/20210829175741_add_salt_to_users.sql create mode 100644 migrations/20210829200701_remove_salt_from_users.sql diff --git a/Cargo.lock b/Cargo.lock index e9bb42d..1124576 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,6 +250,17 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +[[package]] +name = "argon2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d805bb12b532be9ce066df7913311f43716b41d8d780e9322113e8a6ae7c41ab" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -305,6 +316,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bitflags" version = "1.2.1" @@ -323,6 +340,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac 0.8.0", + "digest", + "opaque-debug", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -537,6 +565,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "crypto-mac" version = "0.10.0" @@ -930,7 +968,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" dependencies = [ - "crypto-mac", + "crypto-mac 0.10.0", "digest", ] @@ -1380,6 +1418,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "password-hash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad7268ef9bc463fddde8361d358fbfae1aeeb1fb62eca111cd8c763bf1c5891" +dependencies = [ + "base64ct", + "rand_core 0.6.2", + "subtle", +] + [[package]] name = "paste" version = "1.0.5" @@ -2701,6 +2750,7 @@ dependencies = [ "actix-rt", "actix-web", "anyhow", + "argon2", "base64", "chrono", "claim", diff --git a/Cargo.toml b/Cargo.toml index 94ef92c..e897d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tracing-actix-web = "0.4.0-beta.8" anyhow = "1.0.40" base64 = "0.13.0" sha3 = "0.9" +argon2 = { version = "0.3", features = ["std"] } [dev-dependencies] once_cell = "1.7.2" diff --git a/migrations/20210829175741_add_salt_to_users.sql b/migrations/20210829175741_add_salt_to_users.sql new file mode 100644 index 0000000..30f1a8f --- /dev/null +++ b/migrations/20210829175741_add_salt_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN salt TEXT NOT NULL; diff --git a/migrations/20210829200701_remove_salt_from_users.sql b/migrations/20210829200701_remove_salt_from_users.sql new file mode 100644 index 0000000..dac7f66 --- /dev/null +++ b/migrations/20210829200701_remove_salt_from_users.sql @@ -0,0 +1 @@ +ALTER TABLE users DROP COLUMN salt; diff --git a/src/routes/newsletters.rs b/src/routes/newsletters.rs index 18791b0..50bebf7 100644 --- a/src/routes/newsletters.rs +++ b/src/routes/newsletters.rs @@ -4,7 +4,7 @@ use crate::routes::error_chain_fmt; use actix_web::http::{HeaderMap, HeaderValue, StatusCode}; use actix_web::{web, HttpResponse, ResponseError}; use anyhow::Context; -use sha3::Digest; +use argon2::{Argon2, PasswordHash, PasswordVerifier}; use sqlx::PgPool; #[derive(serde::Deserialize)] @@ -88,26 +88,38 @@ async fn validate_credentials( credentials: Credentials, pool: &PgPool, ) -> Result { - let password_hash = sha3::Sha3_256::digest(credentials.password.as_bytes()); - let password_hash = format!("{:x}", password_hash); - let user_id: Option<_> = sqlx::query!( + let row: Option<_> = sqlx::query!( r#" - SELECT user_id + SELECT user_id, password_hash FROM users - WHERE username = $1 AND password_hash = $2 + WHERE username = $1 "#, credentials.username, - password_hash ) .fetch_optional(pool) .await - .context("Failed to performed a query to validate auth credentials.") + .context("Failed to performed a query to retrieve stored credentials.") .map_err(PublishError::UnexpectedError)?; - user_id - .map(|row| row.user_id) - .ok_or_else(|| anyhow::anyhow!("Invalid username or password.")) - .map_err(PublishError::AuthError) + let (expected_password_hash, user_id) = match row { + Some(row) => (row.password_hash, row.user_id), + None => { + return Err(PublishError::AuthError(anyhow::anyhow!( + "Unknown username." + ))) + } + }; + + let expected_password_hash = PasswordHash::new(&expected_password_hash) + .context("Failed to parse hash in PHC string format.") + .map_err(PublishError::UnexpectedError)?; + + Argon2::default() + .verify_password(credentials.password.as_bytes(), &expected_password_hash) + .context("Invalid password.") + .map_err(PublishError::AuthError)?; + + Ok(user_id) } #[tracing::instrument(