diff --git a/Cargo.lock b/Cargo.lock index 564e47a..6a1aee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,15 +37,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -56,16 +47,15 @@ dependencies = [ ] [[package]] -name = "arrayref" -version = "0.3.6" +name = "argon2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] [[package]] name = "async-stream" @@ -137,6 +127,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" + [[package]] name = "binascii" version = "0.1.4" @@ -150,14 +146,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "blake2b_simd" -version = "1.0.0" +name = "blake2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq 0.1.5", + "digest", ] [[package]] @@ -261,18 +255,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df04a53a7e91248c27eb6bfc1db165e8f47453e98478e4609f9cce020bb3c65a" - [[package]] name = "cookie" version = "0.16.0" @@ -300,16 +282,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" -dependencies = [ - "cfg-if", - "lazy_static", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -424,20 +396,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "djangohashers" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3972be19526a5cde01c01988f152cd98af8c35bd93228a8ee637715a36a38c" -dependencies = [ - "base64 0.13.0", - "constant_time_eq 0.2.1", - "lazy_static", - "rand", - "regex", - "rust-argon2", -] - [[package]] name = "either" version = "1.6.1" @@ -979,13 +937,13 @@ dependencies = [ name = "nomilo" version = "0.1.0-dev" dependencies = [ + "argon2", "base64 0.13.0", "chrono", "clap", "diesel", "diesel-derive-enum", "diesel_migrations", - "djangohashers", "figment", "humantime", "jsonwebtoken", @@ -1124,6 +1082,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "password-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e029e94abc8fb0065241c308f1ac6bc8d20f450e8f7c5f0b25cd9b8d526ba294" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pear" version = "0.2.3" @@ -1341,8 +1310,6 @@ version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ - "aho-corasick", - "memchr", "regex-syntax", ] @@ -1485,18 +1452,6 @@ dependencies = [ "quote", ] -[[package]] -name = "rust-argon2" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" -dependencies = [ - "base64 0.13.0", - "blake2b_simd", - "constant_time_eq 0.1.5", - "crossbeam-utils", -] - [[package]] name = "rustversion" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index 8909672..e1f7cf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ uuid = { version = "0.8.2", features = ["v4", "serde"] } diesel = { version = "1.4", features = ["sqlite"] } diesel_migrations = "1.4" diesel-derive-enum = { version = "1", features = ["sqlite"] } -djangohashers = { version = "1.4.0", features = ["with_argon2"], default-features = false } jsonwebtoken = "7.2.0" chrono = { version = "0.4", features = ["serde"] } humantime = "2.1.0" tokio = "1" figment = { version = "0.10.6", features = ["toml", "env"] } clap = {version = "3", features = ["derive", "cargo"]} +argon2 = "0.4.0" diff --git a/src/models/errors.rs b/src/models/errors.rs index 7667b13..09c7c61 100644 --- a/src/models/errors.rs +++ b/src/models/errors.rs @@ -5,8 +5,9 @@ use rocket::request::{Request, Outcome}; use rocket::response::{self, Response, Responder}; use rocket::serde::json::Json; use serde_json::Value; -use djangohashers::{HasherError}; use diesel::result::Error as DieselError; +use argon2::password_hash::errors::Error as PasswordHashError; + use crate::dns::ConnectorError; use crate::models; @@ -21,13 +22,7 @@ pub enum UserError { MalformedHeader, PermissionDenied, DbError(DieselError), - PasswordError(HasherError), -} - -impl From for UserError { - fn from(e: HasherError) -> Self { - UserError::PasswordError(e) - } + PasswordError(PasswordHashError), } impl From for UserError { @@ -36,6 +31,12 @@ impl From for UserError { } } +impl From for UserError { + fn from(e: PasswordHashError) -> Self { + UserError::PasswordError(e) + } +} + #[derive(Serialize, Debug)] pub struct ErrorResponse { diff --git a/src/models/user.rs b/src/models/user.rs index 108f1c7..b888bab 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -5,8 +5,10 @@ use diesel_derive_enum::DbEnum; use rocket::{State, request::{FromRequest, Request, Outcome}}; use rocket::outcome::try_outcome; use serde::{Deserialize}; -// TODO: Maybe just use argon2 crate directly -use djangohashers::{make_password_with_algorithm, check_password, Algorithm}; +use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; +use argon2::password_hash::errors::Error as PasswordHashError; +use argon2::password_hash::rand_core::OsRng; +use argon2::{Algorithm, Argon2, Version, Params}; use jsonwebtoken::{ errors::ErrorKind as JwtErrorKind }; @@ -158,6 +160,25 @@ impl<'r> FromRequest<'r> for UserInfo { } impl LocalUser { + pub fn hash_password(password: &str) -> String { + let salt = SaltString::generate(&mut OsRng); + + // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + let argon2 = Argon2::new( + Algorithm::Argon2id, + Version::V0x13, // v19 + Params::new(15000, 2, 1, None).expect("password param error"), + ); + argon2.hash_password(password.as_bytes(), &salt).expect("password hash failed").to_string() + } + + pub fn verify_password(password: &str, password_hash: &str) -> Result { + let parsed_hash = PasswordHash::new(&password_hash)?; + + Ok(Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok()) + } + + pub fn create_user(conn: &diesel::SqliteConnection, user_request: CreateUserRequest) -> Result { use crate::schema::localuser::dsl::*; use crate::schema::user::dsl::*; @@ -171,8 +192,7 @@ impl LocalUser { let new_localuser = LocalUser { user_id: new_user_id, username: user_request.username.clone(), - password: make_password_with_algorithm(&user_request.password, Algorithm::Argon2), - // TODO: Use role from request + password: LocalUser::hash_password(&user_request.password), role: if let Some(user_role) = user_request.role { user_role } else { Role::ZoneAdmin }, }; @@ -217,7 +237,7 @@ impl LocalUser { other => UserError::DbError(other) })?; - if !check_password(&request_password, &client_localuser.password)? { + if !LocalUser::verify_password(&request_password, &client_localuser.password)? { return Err(UserError::BadCreds); }