mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-11-25 13:20:23 +00:00
Merge pull request #3210 from tessus/feature/kdf-options
add argon2 kdf fields
This commit is contained in:
commit
3b0f643e9d
16 changed files with 131 additions and 25 deletions
0
migrations/mysql/2023-01-31-222222_add_argon2/down.sql
Normal file
0
migrations/mysql/2023-01-31-222222_add_argon2/down.sql
Normal file
7
migrations/mysql/2023-01-31-222222_add_argon2/up.sql
Normal file
7
migrations/mysql/2023-01-31-222222_add_argon2/up.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_memory INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_parallelism INTEGER DEFAULT NULL;
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_memory INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_parallelism INTEGER DEFAULT NULL;
|
0
migrations/sqlite/2023-01-31-222222_add_argon2/down.sql
Normal file
0
migrations/sqlite/2023-01-31-222222_add_argon2/down.sql
Normal file
7
migrations/sqlite/2023-01-31-222222_add_argon2/up.sql
Normal file
7
migrations/sqlite/2023-01-31-222222_add_argon2/up.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_memory INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN
|
||||||
|
client_kdf_parallelism INTEGER DEFAULT NULL;
|
|
@ -49,6 +49,8 @@ pub struct RegisterData {
|
||||||
Email: String,
|
Email: String,
|
||||||
Kdf: Option<i32>,
|
Kdf: Option<i32>,
|
||||||
KdfIterations: Option<i32>,
|
KdfIterations: Option<i32>,
|
||||||
|
KdfMemory: Option<i32>,
|
||||||
|
KdfParallelism: Option<i32>,
|
||||||
Key: String,
|
Key: String,
|
||||||
Keys: Option<KeysData>,
|
Keys: Option<KeysData>,
|
||||||
MasterPasswordHash: String,
|
MasterPasswordHash: String,
|
||||||
|
@ -153,13 +155,16 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
|
||||||
// Make sure we don't leave a lingering invitation.
|
// Make sure we don't leave a lingering invitation.
|
||||||
Invitation::take(&email, &mut conn).await;
|
Invitation::take(&email, &mut conn).await;
|
||||||
|
|
||||||
|
if let Some(client_kdf_type) = data.Kdf {
|
||||||
|
user.client_kdf_type = client_kdf_type;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(client_kdf_iter) = data.KdfIterations {
|
if let Some(client_kdf_iter) = data.KdfIterations {
|
||||||
user.client_kdf_iter = client_kdf_iter;
|
user.client_kdf_iter = client_kdf_iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(client_kdf_type) = data.Kdf {
|
user.client_kdf_parallelism = data.KdfMemory;
|
||||||
user.client_kdf_type = client_kdf_type;
|
user.client_kdf_memory = data.KdfParallelism;
|
||||||
}
|
|
||||||
|
|
||||||
user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None);
|
user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None);
|
||||||
user.password_hint = password_hint;
|
user.password_hint = password_hint;
|
||||||
|
@ -337,6 +342,8 @@ async fn post_password(
|
||||||
struct ChangeKdfData {
|
struct ChangeKdfData {
|
||||||
Kdf: i32,
|
Kdf: i32,
|
||||||
KdfIterations: i32,
|
KdfIterations: i32,
|
||||||
|
KdfMemory: Option<i32>,
|
||||||
|
KdfParallelism: Option<i32>,
|
||||||
|
|
||||||
MasterPasswordHash: String,
|
MasterPasswordHash: String,
|
||||||
NewMasterPasswordHash: String,
|
NewMasterPasswordHash: String,
|
||||||
|
@ -352,10 +359,31 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
|
||||||
err!("Invalid password")
|
err!("Invalid password")
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.KdfIterations < 100_000 {
|
if data.Kdf == UserKdfType::Pbkdf2 as i32 && data.KdfIterations < 100_000 {
|
||||||
err!("KDF iterations lower then 100000 are not allowed.")
|
err!("PBKDF2 KDF iterations must be at least 100000.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.Kdf == UserKdfType::Argon2id as i32 {
|
||||||
|
if data.KdfIterations < 1 {
|
||||||
|
err!("Argon2 KDF iterations must be at least 1.")
|
||||||
|
}
|
||||||
|
if let Some(m) = data.KdfMemory {
|
||||||
|
if !(15..=1024).contains(&m) {
|
||||||
|
err!("Argon2 memory must be between 15 MB and 1024 MB.")
|
||||||
|
}
|
||||||
|
user.client_kdf_memory = data.KdfMemory;
|
||||||
|
} else {
|
||||||
|
err!("Argon2 memory parameter is required.")
|
||||||
|
}
|
||||||
|
if let Some(p) = data.KdfParallelism {
|
||||||
|
if !(1..=16).contains(&p) {
|
||||||
|
err!("Argon2 parallelism must be between 1 and 16.")
|
||||||
|
}
|
||||||
|
user.client_kdf_parallelism = data.KdfParallelism;
|
||||||
|
} else {
|
||||||
|
err!("Argon2 parallelism parameter is required.")
|
||||||
|
}
|
||||||
|
}
|
||||||
user.client_kdf_iter = data.KdfIterations;
|
user.client_kdf_iter = data.KdfIterations;
|
||||||
user.client_kdf_type = data.Kdf;
|
user.client_kdf_type = data.Kdf;
|
||||||
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
|
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
|
||||||
|
@ -770,15 +798,22 @@ async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
|
||||||
pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json<Value> {
|
pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json<Value> {
|
||||||
let data: PreloginData = data.into_inner().data;
|
let data: PreloginData = data.into_inner().data;
|
||||||
|
|
||||||
let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &mut conn).await {
|
let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &mut conn).await {
|
||||||
Some(user) => (user.client_kdf_type, user.client_kdf_iter),
|
Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism),
|
||||||
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT),
|
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
Json(json!({
|
let mut result = json!({
|
||||||
"Kdf": kdf_type,
|
"Kdf": kdf_type,
|
||||||
"KdfIterations": kdf_iter
|
"KdfIterations": kdf_iter,
|
||||||
}))
|
});
|
||||||
|
|
||||||
|
if kdf_type == UserKdfType::Argon2id as i32 {
|
||||||
|
result["KdfMemory"] = Value::Number(kdf_mem.expect("Argon2 memory parameter is required.").into());
|
||||||
|
result["KdfParallelism"] = Value::Number(kdf_para.expect("Argon2 parallelism parameter is required.").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
|
// https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
|
||||||
|
|
|
@ -620,12 +620,22 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
|
||||||
None => err!("Grantor user not found."),
|
None => err!("Grantor user not found."),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Json(json!({
|
let mut result = json!({
|
||||||
"Kdf": grantor_user.client_kdf_type,
|
"Kdf": grantor_user.client_kdf_type,
|
||||||
"KdfIterations": grantor_user.client_kdf_iter,
|
"KdfIterations": grantor_user.client_kdf_iter,
|
||||||
"KeyEncrypted": &emergency_access.key_encrypted,
|
"KeyEncrypted": &emergency_access.key_encrypted,
|
||||||
"Object": "emergencyAccessTakeover",
|
"Object": "emergencyAccessTakeover",
|
||||||
})))
|
});
|
||||||
|
|
||||||
|
if grantor_user.client_kdf_type == UserKdfType::Argon2id as i32 {
|
||||||
|
result["KdfMemory"] =
|
||||||
|
Value::Number(grantor_user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
|
||||||
|
result["KdfParallelism"] = Value::Number(
|
||||||
|
grantor_user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -96,7 +96,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
|
||||||
let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec);
|
let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec);
|
||||||
device.save(conn).await?;
|
device.save(conn).await?;
|
||||||
|
|
||||||
Ok(Json(json!({
|
let mut result = json!({
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"expires_in": expires_in,
|
"expires_in": expires_in,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
|
@ -109,7 +109,16 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
|
||||||
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
|
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
|
||||||
"scope": scope,
|
"scope": scope,
|
||||||
"unofficialServer": true,
|
"unofficialServer": true,
|
||||||
})))
|
});
|
||||||
|
|
||||||
|
if user.client_kdf_type == UserKdfType::Argon2id as i32 {
|
||||||
|
result["KdfMemory"] =
|
||||||
|
Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
|
||||||
|
result["KdfParallelism"] =
|
||||||
|
Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _password_login(
|
async fn _password_login(
|
||||||
|
@ -249,6 +258,13 @@ async fn _password_login(
|
||||||
result["TwoFactorToken"] = Value::String(token);
|
result["TwoFactorToken"] = Value::String(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.client_kdf_type == UserKdfType::Argon2id as i32 {
|
||||||
|
result["KdfMemory"] =
|
||||||
|
Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
|
||||||
|
result["KdfParallelism"] =
|
||||||
|
Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
|
||||||
|
}
|
||||||
|
|
||||||
info!("User {} logged in successfully. IP: {}", username, ip.ip);
|
info!("User {} logged in successfully. IP: {}", username, ip.ip);
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
@ -333,7 +349,7 @@ async fn _api_key_login(
|
||||||
|
|
||||||
// Note: No refresh_token is returned. The CLI just repeats the
|
// Note: No refresh_token is returned. The CLI just repeats the
|
||||||
// client_credentials login flow when the existing token expires.
|
// client_credentials login flow when the existing token expires.
|
||||||
Ok(Json(json!({
|
let mut result = json!({
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"expires_in": expires_in,
|
"expires_in": expires_in,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
|
@ -345,7 +361,16 @@ async fn _api_key_login(
|
||||||
"ResetMasterPassword": false, // TODO: Same as above
|
"ResetMasterPassword": false, // TODO: Same as above
|
||||||
"scope": scope,
|
"scope": scope,
|
||||||
"unofficialServer": true,
|
"unofficialServer": true,
|
||||||
})))
|
});
|
||||||
|
|
||||||
|
if user.client_kdf_type == UserKdfType::Argon2id as i32 {
|
||||||
|
result["KdfMemory"] =
|
||||||
|
Value::Number(user.client_kdf_memory.expect("Argon2 memory parameter is required.").into());
|
||||||
|
result["KdfParallelism"] =
|
||||||
|
Value::Number(user.client_kdf_parallelism.expect("Argon2 parallelism parameter is required.").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves an existing device or creates a new device from ConnectData and the User
|
/// Retrieves an existing device or creates a new device from ConnectData and the User
|
||||||
|
|
|
@ -28,4 +28,4 @@ pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrgan
|
||||||
pub use self::send::{Send, SendType};
|
pub use self::send::{Send, SendType};
|
||||||
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||||
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
||||||
pub use self::user::{Invitation, User, UserStampException};
|
pub use self::user::{Invitation, User, UserKdfType, UserStampException};
|
||||||
|
|
|
@ -44,6 +44,8 @@ db_object! {
|
||||||
|
|
||||||
pub client_kdf_type: i32,
|
pub client_kdf_type: i32,
|
||||||
pub client_kdf_iter: i32,
|
pub client_kdf_iter: i32,
|
||||||
|
pub client_kdf_memory: Option<i32>,
|
||||||
|
pub client_kdf_parallelism: Option<i32>,
|
||||||
|
|
||||||
pub api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
|
|
||||||
|
@ -58,6 +60,11 @@ db_object! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum UserKdfType {
|
||||||
|
Pbkdf2 = 0,
|
||||||
|
Argon2id = 1,
|
||||||
|
}
|
||||||
|
|
||||||
enum UserStatus {
|
enum UserStatus {
|
||||||
Enabled = 0,
|
Enabled = 0,
|
||||||
Invited = 1,
|
Invited = 1,
|
||||||
|
@ -73,7 +80,7 @@ pub struct UserStampException {
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
impl User {
|
impl User {
|
||||||
pub const CLIENT_KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0
|
pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32;
|
||||||
pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
|
pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
|
||||||
|
|
||||||
pub fn new(email: String) -> Self {
|
pub fn new(email: String) -> Self {
|
||||||
|
@ -113,6 +120,8 @@ impl User {
|
||||||
|
|
||||||
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
|
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
|
||||||
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
|
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
|
||||||
|
client_kdf_memory: None,
|
||||||
|
client_kdf_parallelism: None,
|
||||||
|
|
||||||
api_key: None,
|
api_key: None,
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,8 @@ table! {
|
||||||
excluded_globals -> Text,
|
excluded_globals -> Text,
|
||||||
client_kdf_type -> Integer,
|
client_kdf_type -> Integer,
|
||||||
client_kdf_iter -> Integer,
|
client_kdf_iter -> Integer,
|
||||||
|
client_kdf_memory -> Nullable<Integer>,
|
||||||
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,6 +199,8 @@ table! {
|
||||||
excluded_globals -> Text,
|
excluded_globals -> Text,
|
||||||
client_kdf_type -> Integer,
|
client_kdf_type -> Integer,
|
||||||
client_kdf_iter -> Integer,
|
client_kdf_iter -> Integer,
|
||||||
|
client_kdf_memory -> Nullable<Integer>,
|
||||||
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,6 +199,8 @@ table! {
|
||||||
excluded_globals -> Text,
|
excluded_globals -> Text,
|
||||||
client_kdf_type -> Integer,
|
client_kdf_type -> Integer,
|
||||||
client_kdf_iter -> Integer,
|
client_kdf_iter -> Integer,
|
||||||
|
client_kdf_memory -> Nullable<Integer>,
|
||||||
|
client_kdf_parallelism -> Nullable<Integer>,
|
||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
// The more key/value pairs there are the more recursion occurs.
|
// The more key/value pairs there are the more recursion occurs.
|
||||||
// We want to keep this as low as possible, but not higher then 128.
|
// We want to keep this as low as possible, but not higher then 128.
|
||||||
// If you go above 128 it will cause rust-analyzer to fail,
|
// If you go above 128 it will cause rust-analyzer to fail,
|
||||||
#![recursion_limit = "97"]
|
#![recursion_limit = "103"]
|
||||||
|
|
||||||
// When enabled use MiMalloc as malloc instead of the default malloc
|
// When enabled use MiMalloc as malloc instead of the default malloc
|
||||||
#[cfg(feature = "enable_mimalloc")]
|
#[cfg(feature = "enable_mimalloc")]
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl Fairing for AppHeaders {
|
||||||
base-uri 'self'; \
|
base-uri 'self'; \
|
||||||
form-action 'self'; \
|
form-action 'self'; \
|
||||||
object-src 'self' blob:; \
|
object-src 'self' blob:; \
|
||||||
script-src 'self'; \
|
script-src 'self' 'wasm-unsafe-eval'; \
|
||||||
style-src 'self' 'unsafe-inline'; \
|
style-src 'self' 'unsafe-inline'; \
|
||||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||||
|
|
Loading…
Reference in a new issue