From ebb66c374e3b8b6404929c4b90bfaab4a48f4fae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
 <dani-garcia@users.noreply.github.com>
Date: Wed, 19 Sep 2018 17:30:14 +0200
Subject: [PATCH] Implement KDF iterations change (Fixes #195)

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

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<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
index fdff83c1..2b527b19 100644
--- 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
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,