mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-12-22 22:45:12 +01:00
Implement KDF iterations change (Fixes #195)
This commit is contained in:
parent
89e3c41043
commit
ebb66c374e
6 changed files with 69 additions and 22 deletions
0
migrations/2018-09-19-144557_add_kdf_columns/down.sql
Normal file
0
migrations/2018-09-19-144557_add_kdf_columns/down.sql
Normal file
7
migrations/2018-09-19-144557_add_kdf_columns/up.sql
Normal file
7
migrations/2018-09-19-144557_add_kdf_columns/up.sql
Normal 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;
|
|
@ -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
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue