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:
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,