diff --git a/migrations/2018-09-19-144557_add_kdf_columns/down.sql b/migrations/2018-09-19-144557_add_kdf_columns/down.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/2018-09-19-144557_add_kdf_columns/up.sql b/migrations/2018-09-19-144557_add_kdf_columns/up.sql new file mode 100644 index 00000000..bd98f40e --- /dev/null +++ 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 index 1c1fae23..009269d9 100644 --- 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, + KdfIterations: Option, Key: String, Keys: Option, MasterPasswordHash: String, @@ -56,6 +58,14 @@ fn register(data: JsonUpcase, 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, 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 = "")] +fn post_kdf(data: JsonUpcase, 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 = "")] fn post_sstamp(data: JsonUpcase, headers: Headers, conn: DbConn) -> EmptyResult { let data: PasswordData = data.into_inner().data; @@ -325,17 +364,9 @@ struct PreloginData { fn prelogin(data: JsonUpcase, 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, conn: DbConn) -> JsonResult { "KdfIterations": kdf_iter }))) } - diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index fdff83c1..2b527b19 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -19,6 +19,7 @@ pub fn routes() -> Vec { 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 index 71611e1c..67c0f493 100644 --- 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 index 457b84c2..49880fe5 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -72,6 +72,12 @@ table! { } } +table! { + invitations (email) { + email -> Text, + } +} + table! { organizations (uuid) { uuid -> 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,