From cd45c52d0cfc4f18a215afe8b2a475434007afa9 Mon Sep 17 00:00:00 2001 From: Timshel Date: Fri, 18 Sep 2026 18:48:41 +0200 Subject: [PATCH] Allow to lower KDF iterations for tests --- Cargo.toml | 2 ++ playwright/compose/warden/build.sh | 2 +- playwright/docker-compose.yml | 4 ++++ playwright/test.env | 2 ++ src/api/core/accounts.rs | 12 ++++++------ src/config.rs | 20 ++++++++++++++++---- src/db/models/user.rs | 3 +-- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 764fbd53..44fd1554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,8 @@ s3 = [ "dep:reqsign-aws-v4", "dep:reqsign-core", ] +# Enable to allow to set lower KDF iterations settings +unsafe_test_mode = [] # OIDC specific features oidc-accept-rfc3339-timestamps = ["openidconnect/accept-rfc3339-timestamps"] diff --git a/playwright/compose/warden/build.sh b/playwright/compose/warden/build.sh index ee8b47fe..0a2d4694 100755 --- a/playwright/compose/warden/build.sh +++ b/playwright/compose/warden/build.sh @@ -24,7 +24,7 @@ if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then fi # Lower the KDF iterations default for faster tests. -sed -i 's/(6e5,2e6,6e5)/(1e5,2e6,1e5)/' /web-vault/app/main.*.js +sed -i 's/(6e5,2e6,6e5)/(6e3,2e6,6e3)/' /web-vault/app/main.*.js # Generate a self signed cert mkdir -p /data/ssl; cd /data/ssl diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml index 5bfc47a5..b6ac6f0f 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -6,6 +6,8 @@ services: build: context: .. dockerfile: Dockerfile + args: + DB: sqlite,mysql,postgresql,unsafe_test_mode entrypoint: /bin/bash restart: "no" @@ -23,12 +25,14 @@ services: env_file: ${DC_ENV_FILE:-.env} environment: - ADMIN_TOKEN + - CLIENT_KDF_ITER - DATABASE_URL - CLIENT_SUPPRESS_ONBOARDING - EMAIL_2FA_AUTO_FALLBACK - I_REALLY_WANT_VOLATILE_STORAGE - LOG_LEVEL - LOGIN_RATELIMIT_MAX_BURST + - PASSWORD_ITERATIONS - SMTP_HOST - SMTP_FROM - SMTP_DEBUG diff --git a/playwright/test.env b/playwright/test.env index 2260f860..c2fa354e 100644 --- a/playwright/test.env +++ b/playwright/test.env @@ -58,6 +58,8 @@ LOG_LEVEL=info,oidcwarden::sso=debug LOGIN_RATELIMIT_MAX_BURST=100 ADMIN_TOKEN=admin CLIENT_SUPPRESS_ONBOARDING=true +PASSWORD_ITERATIONS=6000 +CLIENT_KDF_ITER=6000 SMTP_SECURITY=off SMTP_PORT=${MAILDEV_SMTP_PORT} diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index dd2e5801..948964f4 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -688,8 +688,8 @@ async fn post_password(data: Json, headers: Headers, conn: DbCon } fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult { - if data.kdf == UserKdfType::Pbkdf2 as i32 && data.kdf_iterations < 100_000 { - err!("PBKDF2 KDF iterations must be at least 100000.") + if data.kdf == UserKdfType::Pbkdf2 as i32 && data.kdf_iterations < CONFIG.client_kdf_iter() { + err!(format!("PBKDF2 KDF iterations must be at least {}.", CONFIG.client_kdf_iter())) } if data.kdf == UserKdfType::Argon2id as i32 { @@ -1401,15 +1401,15 @@ fn unknown_email_kdf(email: &str) -> (i32, i32, Option, Option) { const PBKDF2: i32 = UserKdfType::Pbkdf2 as i32; const ARGON2ID: i32 = UserKdfType::Argon2id as i32; // Same list as upstream, the default is in it twice to give it more weight - const KDF_SETTINGS: [(i32, i32, Option, Option); 6] = [ - (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), - (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), + let kdf_settings: [(i32, i32, Option, Option); 6] = [ + (User::CLIENT_KDF_TYPE_DEFAULT, CONFIG.client_kdf_iter(), None, None), + (User::CLIENT_KDF_TYPE_DEFAULT, CONFIG.client_kdf_iter(), None, None), (PBKDF2, 100_000, None, None), (PBKDF2, 5_000, None, None), (ARGON2ID, 3, Some(64), Some(4)), (ARGON2ID, 6, Some(32), Some(4)), ]; - KDF_SETTINGS[crate::auth::prelogin_kdf_index(email, KDF_SETTINGS.len())] + kdf_settings[crate::auth::prelogin_kdf_index(email, kdf_settings.len())] } pub async fn prelogin(data: Json, ip: ClientIp, conn: DbConn) -> JsonResult { diff --git a/src/config.rs b/src/config.rs index a56748f4..92e1bba9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -661,7 +661,9 @@ make_config! { client { /// Control whether clients onboarding interstitials are suppressed |> post-login welcome dialogs, extension install prompts, setup extension redirects, and premium upsell modals - client_suppress_onboarding: bool, true, def, false; + client_suppress_onboarding: bool, true, def, false; + /// In most cases this value is set by the client |> Will block clients from setting too low of a value in `accounts::post_kdf` + client_kdf_iter: i32, false, def, 600_000; }, /// Advanced settings @@ -935,6 +937,17 @@ make_config! { }, } +fn check_iterations(iterations: i32, key: &str) -> Result<(), Error> { + if iterations < 100_000 { + #[cfg(feature = "unsafe_test_mode")] + println!("[WARNING] {key} should be at least 100000 or higher. The default is 600000!"); + #[cfg(not(feature = "unsafe_test_mode"))] + err!(format!("{key} should be at least 100000 or higher. The default is 600000!")); + } + + Ok(()) +} + fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { // Validate connection URL is valid and DB feature is enabled #[cfg(sqlite)] @@ -969,9 +982,8 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { } } - if cfg.password_iterations < 100_000 { - err!("PASSWORD_ITERATIONS should be at least 100000 or higher. The default is 600000!"); - } + check_iterations(cfg.password_iterations, "PASSWORD_ITERATIONS")?; + check_iterations(cfg.client_kdf_iter, "CLIENT_KDF_ITER")?; let limit = 256; if cfg.database_max_conns < 1 || cfg.database_max_conns > limit { diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 2f3afc02..75e7ca24 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -110,7 +110,6 @@ pub struct UserStampException { /// Local methods impl User { pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32; - pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000; pub fn new(email: &str, name: Option) -> Self { let now = Utc::now().naive_utc(); @@ -148,7 +147,7 @@ impl User { excluded_globals: "[]".to_owned(), client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT, - client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT, + client_kdf_iter: CONFIG.client_kdf_iter(), client_kdf_memory: None, client_kdf_parallelism: None,