vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit f71f10eac6f9111217e9ed8016ce9baf9b06804e
parent f1acc1e05a12320240eced699e720ecfad7f562a
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Sat, 24 Nov 2018 23:00:41 +0100

Implemented key rotation with the latest vault

Diffstat:
Msrc/api/core/accounts.rs | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/api/core/ciphers.rs | 2+-
Msrc/db/models/user.rs | 1-
Msrc/main.rs | 2+-
4 files changed, 100 insertions(+), 27 deletions(-)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -1,15 +1,15 @@ use rocket_contrib::json::Json; -use db::DbConn; use db::models::*; +use db::DbConn; -use api::{PasswordData, JsonResult, EmptyResult, JsonUpcase, NumberOrString}; +use api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData, UpdateType, WebSocketUsers}; use auth::Headers; use mail; use CONFIG; -use rocket::Route; +use rocket::{Route, State}; pub fn routes() -> Vec<Route> { routes![ @@ -21,6 +21,7 @@ pub fn routes() -> Vec<Route> { post_keys, post_password, post_kdf, + post_rotatekey, post_sstamp, post_email_token, post_email, @@ -56,23 +57,22 @@ struct KeysData { fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { let data: RegisterData = data.into_inner().data; - let mut user = match User::find_by_mail(&data.Email, &conn) { - Some(mut user) => { + Some(user) => { if Invitation::take(&data.Email, &conn) { for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).iter_mut() { user_org.status = UserOrgStatus::Accepted as i32; if user_org.save(&conn).is_err() { err!("Failed to accept user to organization") } - }; + } user } else if CONFIG.signups_allowed { - err!("Account with this email already exists") + err!("Account with this email already exists") } else { - err!("Registration not allowed") + err!("Registration not allowed") } - }, + } None => { if CONFIG.signups_allowed || Invitation::take(&data.Email, &conn) { User::new(data.Email) @@ -109,7 +109,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { match user.save(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed to save user") + Err(_) => err!("Failed to save user"), } } @@ -122,7 +122,7 @@ fn profile(headers: Headers, conn: DbConn) -> JsonResult { #[allow(non_snake_case)] struct ProfileData { #[serde(rename = "Culture")] - _Culture: String, // Ignored, always use en-US + _Culture: String, // Ignored, always use en-US MasterPasswordHint: Option<String>, Name: String, } @@ -145,7 +145,7 @@ fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) - }; match user.save(&conn) { Ok(()) => Ok(Json(user.to_json(&conn))), - Err(_) => err!("Failed to save user profile") + Err(_) => err!("Failed to save user profile"), } } @@ -153,7 +153,7 @@ fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) - fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { let user = match User::find_by_uuid(&uuid, &conn) { Some(user) => user, - None => err!("User doesn't exist") + None => err!("User doesn't exist"), }; Ok(Json(json!({ @@ -174,12 +174,10 @@ fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> Json match user.save(&conn) { Ok(()) => Ok(Json(user.to_json(&conn))), - Err(_) => err!("Failed to save the user's keys") + Err(_) => err!("Failed to save the user's keys"), } } - - #[derive(Deserialize)] #[allow(non_snake_case)] struct ChangePassData { @@ -201,7 +199,7 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon user.key = data.Key; match user.save(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed to save password") + Err(_) => err!("Failed to save password"), } } @@ -231,10 +229,86 @@ fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> user.key = data.Key; match user.save(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed to save password settings") + Err(_) => err!("Failed to save password settings"), } } +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct UpdateFolderData { + Id: String, + Name: String, +} + +use super::ciphers::CipherData; + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct KeyData { + Ciphers: Vec<CipherData>, + Folders: Vec<UpdateFolderData>, + Key: String, + PrivateKey: String, + MasterPasswordHash: String, +} + +#[post("/accounts/key", data = "<data>")] +fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult { + let data: KeyData = data.into_inner().data; + + if !headers.user.check_valid_password(&data.MasterPasswordHash) { + err!("Invalid password") + } + + let user_uuid = &headers.user.uuid; + + // Update folder data + for folder_data in data.Folders { + let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn) { + Some(folder) => folder, + None => err!("Folder doesn't exist"), + }; + + if &saved_folder.user_uuid != user_uuid { + err!("The folder is not owned by the user") + } + + saved_folder.name = folder_data.Name; + if saved_folder.save(&conn).is_err() { + err!("Failed to save folder") + } + } + + // Update cipher data + use super::ciphers::update_cipher_from_data; + + for cipher_data in data.Ciphers { + let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn) { + Some(cipher) => cipher, + None => err!("Cipher doesn't exist"), + }; + + if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid { + err!("The cipher is not owned by the user") + } + + update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &ws, UpdateType::SyncCipherUpdate)? + } + + // Update user data + let mut user = headers.user; + + user.key = data.Key; + user.private_key = Some(data.PrivateKey); + user.reset_security_stamp(); + + if user.save(&conn).is_err() { + err!("Failed modify user key"); + } + + 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; @@ -247,7 +321,7 @@ fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) - user.reset_security_stamp(); match user.save(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed to reset security stamp") + Err(_) => err!("Failed to reset security stamp"), } } @@ -278,7 +352,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db struct ChangeEmailData { MasterPasswordHash: String, NewEmail: String, - + Key: String, NewMasterPasswordHash: String, #[serde(rename = "Token")] @@ -299,13 +373,13 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) } user.email = data.NewEmail; - + user.set_password(&data.NewMasterPasswordHash); user.key = data.Key; match user.save(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed to save email address") + Err(_) => err!("Failed to save email address"), } } @@ -322,10 +396,10 @@ fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } - + match user.delete(&conn) { Ok(()) => Ok(()), - Err(_) => err!("Failed deleting user account, are you the only owner of some organization?") + Err(_) => err!("Failed deleting user account, are you the only owner of some organization?"), } } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -136,7 +136,7 @@ fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResul #[allow(non_snake_case)] pub struct CipherData { // Id is optional as it is included only in bulk share - Id: Option<String>, + pub Id: Option<String>, // Folder id is not included in import FolderId: Option<String>, // TODO: Some of these might appear all the time, no need for Option diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -97,7 +97,6 @@ impl User { self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32); - self.reset_security_stamp(); } pub fn reset_security_stamp(&mut self) { diff --git a/src/main.rs b/src/main.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro_hygiene, decl_macro, custom_derive, vec_remove_item, try_trait)] +#![feature(proc_macro_hygiene, decl_macro, custom_derive, vec_remove_item, try_trait, nll)] #![recursion_limit="128"] #![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings