vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit ebb66c374e3b8b6404929c4b90bfaab4a48f4fae
parent 89e3c41043c49da2e906181e50cd1e2686d62abe
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Wed, 19 Sep 2018 17:30:14 +0200

Implement KDF iterations change (Fixes #195)

Diffstat:
Amigrations/2018-09-19-144557_add_kdf_columns/down.sql | 0
Amigrations/2018-09-19-144557_add_kdf_columns/up.sql | 7+++++++
Msrc/api/core/accounts.rs | 52+++++++++++++++++++++++++++++++++++++++++-----------
Msrc/api/core/mod.rs | 1+
Msrc/db/models/user.rs | 16+++++++++++-----
Msrc/db/schema.rs | 15+++++++++------
6 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/migrations/2018-09-19-144557_add_kdf_columns/down.sql b/migrations/2018-09-19-144557_add_kdf_columns/down.sql diff --git a/migrations/2018-09-19-144557_add_kdf_columns/up.sql b/migrations/2018-09-19-144557_add_kdf_columns/up.sql @@ -0,0 +1,7 @@ +ALTER TABLE users + ADD COLUMN + client_kdf_type INTEGER NOT NULL DEFAULT 0; -- PBKDF2 + +ALTER TABLE users + ADD COLUMN + client_kdf_iter INTEGER NOT NULL DEFAULT 5000; diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -14,6 +14,8 @@ use CONFIG; #[allow(non_snake_case)] struct RegisterData { Email: String, + Kdf: Option<i32>, + KdfIterations: Option<i32>, Key: String, Keys: Option<KeysData>, MasterPasswordHash: String, @@ -56,6 +58,14 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { } }; + 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.set_password(&data.MasterPasswordHash); user.key = data.Key; @@ -165,6 +175,35 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon Ok(()) } +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct ChangeKdfData { + Kdf: i32, + KdfIterations: i32, + + MasterPasswordHash: String, + NewMasterPasswordHash: String, + Key: String, +} + +#[post("/accounts/kdf", data = "<data>")] +fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { + let data: ChangeKdfData = data.into_inner().data; + let mut user = headers.user; + + if !user.check_valid_password(&data.MasterPasswordHash) { + err!("Invalid password") + } + + user.client_kdf_iter = data.KdfIterations; + user.client_kdf_type = data.Kdf; + user.set_password(&data.NewMasterPasswordHash); + user.key = data.Key; + user.save(&conn); + + Ok(()) +} + #[post("/accounts/security-stamp", data = "<data>")] fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { let data: PasswordData = data.into_inner().data; @@ -325,17 +364,9 @@ struct PreloginData { fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> JsonResult { let data: PreloginData = data.into_inner().data; - const KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0 - const KDF_ITER_DEFAULT: i32 = 5_000; - let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn) { - Some(user) => { - let _server_iter = user.password_iterations; - let client_iter = KDF_ITER_DEFAULT; // TODO: Make iterations user configurable - - (KDF_TYPE_DEFAULT, client_iter) - }, - None => (KDF_TYPE_DEFAULT, KDF_ITER_DEFAULT), // Return default values when no user + Some(user) => (user.client_kdf_type, user.client_kdf_iter), + None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT), }; Ok(Json(json!({ @@ -343,4 +374,3 @@ fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> JsonResult { "KdfIterations": kdf_iter }))) } - diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -19,6 +19,7 @@ pub fn routes() -> Vec<Route> { get_public_keys, post_keys, post_password, + post_kdf, post_sstamp, post_email_token, post_email, diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -35,17 +35,20 @@ pub struct User { pub equivalent_domains: String, pub excluded_globals: String, + + pub client_kdf_type: i32, + pub client_kdf_iter: i32, } /// Local methods impl User { + pub const CLIENT_KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0 + pub const CLIENT_KDF_ITER_DEFAULT: i32 = 5_000; + pub fn new(mail: String) -> Self { let now = Utc::now().naive_utc(); let email = mail.to_lowercase(); - let iterations = CONFIG.password_iterations; - let salt = crypto::get_random_64(); - Self { uuid: Uuid::new_v4().to_string(), created_at: now, @@ -55,8 +58,8 @@ impl User { key: String::new(), password_hash: Vec::new(), - salt, - password_iterations: iterations, + salt: crypto::get_random_64(), + password_iterations: CONFIG.password_iterations, security_stamp: Uuid::new_v4().to_string(), @@ -69,6 +72,9 @@ impl User { equivalent_domains: "[]".to_string(), excluded_globals: "[]".to_string(), + + client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT, + client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT, } } diff --git a/src/db/schema.rs b/src/db/schema.rs @@ -73,6 +73,12 @@ table! { } table! { + invitations (email) { + email -> Text, + } +} + +table! { organizations (uuid) { uuid -> Text, name -> Text, @@ -110,12 +116,8 @@ table! { security_stamp -> Text, equivalent_domains -> Text, excluded_globals -> Text, - } -} - -table! { - invitations (email) { - email -> Text, + client_kdf_type -> Integer, + client_kdf_iter -> Integer, } } @@ -164,6 +166,7 @@ allow_tables_to_appear_in_same_query!( devices, folders, folders_ciphers, + invitations, organizations, twofactor, users,