From 68bcc7a4b8c47ca76eb03a0ce7db22e89afa2ce1 Mon Sep 17 00:00:00 2001 From: "Helmut K. C. Tessarek" Date: Tue, 31 Jan 2023 21:26:23 -0500 Subject: [PATCH] add argon2 kdf fields --- .../2023-01-31-222222_add_argon2/down.sql | 0 .../mysql/2023-01-31-222222_add_argon2/up.sql | 7 +++ .../2023-01-31-222222_add_argon2/down.sql | 0 .../2023-01-31-222222_add_argon2/up.sql | 7 +++ .../2023-01-31-222222_add_argon2/down.sql | 0 .../2023-01-31-222222_add_argon2/up.sql | 7 +++ src/api/core/accounts.rs | 57 +++++++++++++++---- src/api/core/emergency_access.rs | 22 +++++-- src/api/identity.rs | 33 +++++++++-- src/db/models/mod.rs | 2 +- src/db/models/user.rs | 11 +++- src/db/schemas/mysql/schema.rs | 2 + src/db/schemas/postgresql/schema.rs | 2 + src/db/schemas/sqlite/schema.rs | 2 + src/main.rs | 2 +- src/util.rs | 2 +- 16 files changed, 131 insertions(+), 25 deletions(-) create mode 100644 migrations/mysql/2023-01-31-222222_add_argon2/down.sql create mode 100644 migrations/mysql/2023-01-31-222222_add_argon2/up.sql create mode 100644 migrations/postgresql/2023-01-31-222222_add_argon2/down.sql create mode 100644 migrations/postgresql/2023-01-31-222222_add_argon2/up.sql create mode 100644 migrations/sqlite/2023-01-31-222222_add_argon2/down.sql create mode 100644 migrations/sqlite/2023-01-31-222222_add_argon2/up.sql diff --git a/migrations/mysql/2023-01-31-222222_add_argon2/down.sql b/migrations/mysql/2023-01-31-222222_add_argon2/down.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/mysql/2023-01-31-222222_add_argon2/up.sql b/migrations/mysql/2023-01-31-222222_add_argon2/up.sql new file mode 100644 index 00000000..35b8189e --- /dev/null +++ b/migrations/mysql/2023-01-31-222222_add_argon2/up.sql @@ -0,0 +1,7 @@ +ALTER TABLE users + ADD COLUMN + client_kdf_memory INTEGER DEFAULT NULL; + +ALTER TABLE users + ADD COLUMN + client_kdf_parallelism INTEGER DEFAULT NULL; diff --git a/migrations/postgresql/2023-01-31-222222_add_argon2/down.sql b/migrations/postgresql/2023-01-31-222222_add_argon2/down.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql b/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql new file mode 100644 index 00000000..35b8189e --- /dev/null +++ b/migrations/postgresql/2023-01-31-222222_add_argon2/up.sql @@ -0,0 +1,7 @@ +ALTER TABLE users + ADD COLUMN + client_kdf_memory INTEGER DEFAULT NULL; + +ALTER TABLE users + ADD COLUMN + client_kdf_parallelism INTEGER DEFAULT NULL; diff --git a/migrations/sqlite/2023-01-31-222222_add_argon2/down.sql b/migrations/sqlite/2023-01-31-222222_add_argon2/down.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql b/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql new file mode 100644 index 00000000..35b8189e --- /dev/null +++ b/migrations/sqlite/2023-01-31-222222_add_argon2/up.sql @@ -0,0 +1,7 @@ +ALTER TABLE users + ADD COLUMN + client_kdf_memory INTEGER DEFAULT NULL; + +ALTER TABLE users + ADD COLUMN + client_kdf_parallelism INTEGER DEFAULT NULL; diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index a2816a4c..0354f464 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -49,6 +49,8 @@ pub struct RegisterData { Email: String, Kdf: Option, KdfIterations: Option, + KdfMemory: Option, + KdfParallelism: Option, Key: String, Keys: Option, MasterPasswordHash: String, @@ -153,13 +155,16 @@ pub async fn _register(data: JsonUpcase, mut conn: DbConn) -> Json // Make sure we don't leave a lingering invitation. Invitation::take(&email, &mut conn).await; + if let Some(client_kdf_type) = data.Kdf { + user.client_kdf_type = client_kdf_type; + } + if let Some(client_kdf_iter) = data.KdfIterations { user.client_kdf_iter = client_kdf_iter; } - if let Some(client_kdf_type) = data.Kdf { - user.client_kdf_type = client_kdf_type; - } + user.client_kdf_parallelism = data.KdfMemory; + user.client_kdf_memory = data.KdfParallelism; user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None); user.password_hint = password_hint; @@ -337,6 +342,8 @@ async fn post_password( struct ChangeKdfData { Kdf: i32, KdfIterations: i32, + KdfMemory: Option, + KdfParallelism: Option, MasterPasswordHash: String, NewMasterPasswordHash: String, @@ -352,10 +359,31 @@ async fn post_kdf(data: JsonUpcase, headers: Headers, mut conn: D err!("Invalid password") } - if data.KdfIterations < 100_000 { - err!("KDF iterations lower then 100000 are not allowed.") + if data.Kdf == UserKdfType::Pbkdf2 as i32 && data.KdfIterations < 100_000 { + err!("PBKDF2 KDF iterations must be at least 100000.") } + if data.Kdf == UserKdfType::Argon2id as i32 { + if data.KdfIterations < 1 { + err!("Argon2 KDF iterations must be at least 1.") + } + if let Some(m) = data.KdfMemory { + if !(15..=1024).contains(&m) { + err!("Argon2 memory must be between 15 MB and 1024 MB.") + } + user.client_kdf_memory = data.KdfMemory; + } else { + err!("Argon2 memory parameter is required.") + } + if let Some(p) = data.KdfParallelism { + if !(1..=16).contains(&p) { + err!("Argon2 parallelism must be between 1 and 16.") + } + user.client_kdf_parallelism = data.KdfParallelism; + } else { + err!("Argon2 parallelism parameter is required.") + } + } user.client_kdf_iter = data.KdfIterations; user.client_kdf_type = data.Kdf; user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None); @@ -770,15 +798,22 @@ async fn prelogin(data: JsonUpcase, conn: DbConn) -> Json { pub async fn _prelogin(data: JsonUpcase, mut conn: DbConn) -> Json { let data: PreloginData = data.into_inner().data; - let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &mut conn).await { - Some(user) => (user.client_kdf_type, user.client_kdf_iter), - None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT), + let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &mut conn).await { + Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism), + None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), }; - Json(json!({ + let mut result = json!({ "Kdf": kdf_type, - "KdfIterations": kdf_iter - })) + "KdfIterations": kdf_iter, + }); + + if kdf_type == UserKdfType::Argon2id as i32 { + result["KdfMemory"] = Value::Number(kdf_mem.expect("Argon2 memory parameter is required.").into()); + result["KdfParallelism"] = Value::Number(kdf_para.expect("Argon2 parallelism parameter is required.").into()); + } + + Json(result) } // https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs index fcabc617..235ba8e8 100644 --- a/src/api/core/emergency_access.rs +++ b/src/api/core/emergency_access.rs @@ -618,12 +618,22 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn: None => err!("Grantor user not found."), }; - Ok(Json(json!({ - "Kdf": grantor_user.client_kdf_type, - "KdfIterations": grantor_user.client_kdf_iter, - "KeyEncrypted": &emergency_access.key_encrypted, - "Object": "emergencyAccessTakeover", - }))) + let mut result = json!({ + "Kdf": grantor_user.client_kdf_type, + "KdfIterations": grantor_user.client_kdf_iter, + "KeyEncrypted": &emergency_access.key_encrypted, + "Object": "emergencyAccessTakeover", + }); + + if grantor_user.client_kdf_type == UserKdfType::Argon2id as i32 { + result["KdfMemory"] = + Value::Number(grantor_user.client_kdf_memory.expect("Argon2 memory parameter is required.").into()); + result["KdfParallelism"] = Value::Number( + grantor_user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into(), + ); + } + + Ok(Json(result)) } #[derive(Deserialize)] diff --git a/src/api/identity.rs b/src/api/identity.rs index e52608e9..039e61d5 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -96,7 +96,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult { let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); device.save(conn).await?; - Ok(Json(json!({ + let mut result = json!({ "access_token": access_token, "expires_in": expires_in, "token_type": "Bearer", @@ -109,7 +109,16 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult { "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "scope": scope, "unofficialServer": true, - }))) + }); + + if user.client_kdf_type == UserKdfType::Argon2id as i32 { + result["KdfMemory"] = + Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into()); + result["KdfParallelism"] = + Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into()); + } + + Ok(Json(result)) } async fn _password_login( @@ -249,6 +258,13 @@ async fn _password_login( result["TwoFactorToken"] = Value::String(token); } + if user.client_kdf_type == UserKdfType::Argon2id as i32 { + result["KdfMemory"] = + Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into()); + result["KdfParallelism"] = + Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into()); + } + info!("User {} logged in successfully. IP: {}", username, ip.ip); Ok(Json(result)) } @@ -333,7 +349,7 @@ async fn _api_key_login( // Note: No refresh_token is returned. The CLI just repeats the // client_credentials login flow when the existing token expires. - Ok(Json(json!({ + let mut result = json!({ "access_token": access_token, "expires_in": expires_in, "token_type": "Bearer", @@ -345,7 +361,16 @@ async fn _api_key_login( "ResetMasterPassword": false, // TODO: Same as above "scope": scope, "unofficialServer": true, - }))) + }); + + if user.client_kdf_type == UserKdfType::Argon2id as i32 { + result["KdfMemory"] = + Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into()); + result["KdfParallelism"] = + Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into()); + } + + Ok(Json(result)) } /// Retrieves an existing device or creates a new device from ConnectData and the User diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 274d48e8..96dc27ce 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -28,4 +28,4 @@ pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrgan pub use self::send::{Send, SendType}; pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor_incomplete::TwoFactorIncomplete; -pub use self::user::{Invitation, User, UserStampException}; +pub use self::user::{Invitation, User, UserKdfType, UserStampException}; diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 5ce87e14..83a59524 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -44,6 +44,8 @@ db_object! { pub client_kdf_type: i32, pub client_kdf_iter: i32, + pub client_kdf_memory: Option, + pub client_kdf_parallelism: Option, pub api_key: Option, @@ -58,6 +60,11 @@ db_object! { } } +pub enum UserKdfType { + Pbkdf2 = 0, + Argon2id = 1, +} + enum UserStatus { Enabled = 0, Invited = 1, @@ -73,7 +80,7 @@ pub struct UserStampException { /// Local methods impl User { - pub const CLIENT_KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0 + pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32; pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000; pub fn new(email: String) -> Self { @@ -113,6 +120,8 @@ impl User { client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT, client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT, + client_kdf_memory: None, + client_kdf_parallelism: None, api_key: None, diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs index 27cd24c3..e418efb6 100644 --- a/src/db/schemas/mysql/schema.rs +++ b/src/db/schemas/mysql/schema.rs @@ -199,6 +199,8 @@ table! { excluded_globals -> Text, client_kdf_type -> Integer, client_kdf_iter -> Integer, + client_kdf_memory -> Nullable, + client_kdf_parallelism -> Nullable, api_key -> Nullable, avatar_color -> Nullable, } diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs index 0233e0c9..d5e394c4 100644 --- a/src/db/schemas/postgresql/schema.rs +++ b/src/db/schemas/postgresql/schema.rs @@ -199,6 +199,8 @@ table! { excluded_globals -> Text, client_kdf_type -> Integer, client_kdf_iter -> Integer, + client_kdf_memory -> Nullable, + client_kdf_parallelism -> Nullable, api_key -> Nullable, avatar_color -> Nullable, } diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs index 391e6700..9c902457 100644 --- a/src/db/schemas/sqlite/schema.rs +++ b/src/db/schemas/sqlite/schema.rs @@ -199,6 +199,8 @@ table! { excluded_globals -> Text, client_kdf_type -> Integer, client_kdf_iter -> Integer, + client_kdf_memory -> Nullable, + client_kdf_parallelism -> Nullable, api_key -> Nullable, avatar_color -> Nullable, } diff --git a/src/main.rs b/src/main.rs index 3c648231..cd17a2f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ // The more key/value pairs there are the more recursion occurs. // We want to keep this as low as possible, but not higher then 128. // If you go above 128 it will cause rust-analyzer to fail, -#![recursion_limit = "97"] +#![recursion_limit = "103"] // When enabled use MiMalloc as malloc instead of the default malloc #[cfg(feature = "enable_mimalloc")] diff --git a/src/util.rs b/src/util.rs index fe99e2d3..18adea70 100644 --- a/src/util.rs +++ b/src/util.rs @@ -58,7 +58,7 @@ impl Fairing for AppHeaders { base-uri 'self'; \ form-action 'self'; \ object-src 'self' blob:; \ - script-src 'self'; \ + script-src 'self' 'wasm-unsafe-eval'; \ style-src 'self' 'unsafe-inline'; \ child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \ frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \