Implement KDF iterations change (Fixes #195)

This commit is contained in:
Daniel García 2018-09-19 17:30:14 +02:00
parent 89e3c41043
commit ebb66c374e
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A
6 changed files with 69 additions and 22 deletions

View File

@ -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;

View File

@ -14,6 +14,8 @@ use CONFIG;
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct RegisterData { struct RegisterData {
Email: String, Email: String,
Kdf: Option<i32>,
KdfIterations: Option<i32>,
Key: String, Key: String,
Keys: Option<KeysData>, Keys: Option<KeysData>,
MasterPasswordHash: String, 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.set_password(&data.MasterPasswordHash);
user.key = data.Key; user.key = data.Key;
@ -165,6 +175,35 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon
Ok(()) 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>")] #[post("/accounts/security-stamp", data = "<data>")]
fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
@ -325,17 +364,9 @@ struct PreloginData {
fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> JsonResult { fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> JsonResult {
let data: PreloginData = data.into_inner().data; 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) { let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn) {
Some(user) => { Some(user) => (user.client_kdf_type, user.client_kdf_iter),
let _server_iter = user.password_iterations; None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT),
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
}; };
Ok(Json(json!({ Ok(Json(json!({
@ -343,4 +374,3 @@ fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> JsonResult {
"KdfIterations": kdf_iter "KdfIterations": kdf_iter
}))) })))
} }

View File

@ -19,6 +19,7 @@ pub fn routes() -> Vec<Route> {
get_public_keys, get_public_keys,
post_keys, post_keys,
post_password, post_password,
post_kdf,
post_sstamp, post_sstamp,
post_email_token, post_email_token,
post_email, post_email,

View File

@ -35,17 +35,20 @@ pub struct User {
pub equivalent_domains: String, pub equivalent_domains: String,
pub excluded_globals: String, pub excluded_globals: String,
pub client_kdf_type: i32,
pub client_kdf_iter: i32,
} }
/// Local methods /// Local methods
impl User { 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 { pub fn new(mail: String) -> Self {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let email = mail.to_lowercase(); let email = mail.to_lowercase();
let iterations = CONFIG.password_iterations;
let salt = crypto::get_random_64();
Self { Self {
uuid: Uuid::new_v4().to_string(), uuid: Uuid::new_v4().to_string(),
created_at: now, created_at: now,
@ -55,8 +58,8 @@ impl User {
key: String::new(), key: String::new(),
password_hash: Vec::new(), password_hash: Vec::new(),
salt, salt: crypto::get_random_64(),
password_iterations: iterations, password_iterations: CONFIG.password_iterations,
security_stamp: Uuid::new_v4().to_string(), security_stamp: Uuid::new_v4().to_string(),
@ -69,6 +72,9 @@ impl User {
equivalent_domains: "[]".to_string(), equivalent_domains: "[]".to_string(),
excluded_globals: "[]".to_string(), excluded_globals: "[]".to_string(),
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
} }
} }

View File

@ -72,6 +72,12 @@ table! {
} }
} }
table! {
invitations (email) {
email -> Text,
}
}
table! { table! {
organizations (uuid) { organizations (uuid) {
uuid -> Text, uuid -> Text,
@ -110,12 +116,8 @@ table! {
security_stamp -> Text, security_stamp -> Text,
equivalent_domains -> Text, equivalent_domains -> Text,
excluded_globals -> Text, excluded_globals -> Text,
} client_kdf_type -> Integer,
} client_kdf_iter -> Integer,
table! {
invitations (email) {
email -> Text,
} }
} }
@ -164,6 +166,7 @@ allow_tables_to_appear_in_same_query!(
devices, devices,
folders, folders,
folders_ciphers, folders_ciphers,
invitations,
organizations, organizations,
twofactor, twofactor,
users, users,