Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add argon2 kdf fields #3210

Merged
merged 2 commits into from
Feb 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Empty file.
7 changes: 7 additions & 0 deletions migrations/mysql/2023-01-31-222222_add_argon2/up.sql
Original file line number Diff line number Diff line change
@@ -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;
Empty file.
7 changes: 7 additions & 0 deletions migrations/postgresql/2023-01-31-222222_add_argon2/up.sql
Original file line number Diff line number Diff line change
@@ -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;
Empty file.
7 changes: 7 additions & 0 deletions migrations/sqlite/2023-01-31-222222_add_argon2/up.sql
Original file line number Diff line number Diff line change
@@ -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;
57 changes: 46 additions & 11 deletions src/api/core/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub struct RegisterData {
Email: String,
Kdf: Option<i32>,
KdfIterations: Option<i32>,
KdfMemory: Option<i32>,
KdfParallelism: Option<i32>,
Key: String,
Keys: Option<KeysData>,
MasterPasswordHash: String,
Expand Down Expand Up @@ -153,13 +155,16 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
// Make sure we don't leave a lingering invitation.
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 {
user.client_kdf_iter = client_kdf_iter;
}

if let Some(client_kdf_type) = data.Kdf {
user.client_kdf_type = client_kdf_type;
}
user.client_kdf_parallelism = data.KdfMemory;
user.client_kdf_memory = data.KdfParallelism;

user.set_password(&data.MasterPasswordHash, Some(data.Key), true, None);
user.password_hint = password_hint;
Expand Down Expand Up @@ -337,6 +342,8 @@ async fn post_password(
struct ChangeKdfData {
Kdf: i32,
KdfIterations: i32,
KdfMemory: Option<i32>,
KdfParallelism: Option<i32>,

MasterPasswordHash: String,
NewMasterPasswordHash: String,
Expand All @@ -352,10 +359,31 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
err!("Invalid password")
}

if data.KdfIterations < 100_000 {
err!("KDF iterations lower then 100000 are not allowed.")
if data.Kdf == UserKdfType::Pbkdf2 as i32 && data.KdfIterations < 100_000 {
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_type = data.Kdf;
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
Expand Down Expand Up @@ -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> {
let data: PreloginData = data.into_inner().data;

let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &mut conn).await {
Some(user) => (user.client_kdf_type, user.client_kdf_iter),
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT),
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, user.client_kdf_memory, user.client_kdf_parallelism),
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
};

Json(json!({
let mut result = json!({
"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
Expand Down
22 changes: 16 additions & 6 deletions src/api/core/emergency_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,12 +620,22 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn:
None => err!("Grantor user not found."),
};

Ok(Json(json!({
"Kdf": grantor_user.client_kdf_type,
"KdfIterations": grantor_user.client_kdf_iter,
"KeyEncrypted": &emergency_access.key_encrypted,
"Object": "emergencyAccessTakeover",
})))
let mut result = json!({
"Kdf": grantor_user.client_kdf_type,
"KdfIterations": grantor_user.client_kdf_iter,
"KeyEncrypted": &emergency_access.key_encrypted,
"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)]
Expand Down
33 changes: 29 additions & 4 deletions src/api/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
device.save(conn).await?;

Ok(Json(json!({
let mut result = json!({
"access_token": access_token,
"expires_in": expires_in,
"token_type": "Bearer",
Expand All @@ -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
"scope": scope,
"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(
Expand Down Expand Up @@ -249,6 +258,13 @@ async fn _password_login(
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);
Ok(Json(result))
}
Expand Down Expand Up @@ -333,7 +349,7 @@ async fn _api_key_login(

// Note: No refresh_token is returned. The CLI just repeats the
// client_credentials login flow when the existing token expires.
Ok(Json(json!({
let mut result = json!({
"access_token": access_token,
"expires_in": expires_in,
"token_type": "Bearer",
Expand All @@ -345,7 +361,16 @@ async fn _api_key_login(
"ResetMasterPassword": false, // TODO: Same as above
"scope": scope,
"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
Expand Down
2 changes: 1 addition & 1 deletion src/db/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrgan
pub use self::send::{Send, SendType};
pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::two_factor_incomplete::TwoFactorIncomplete;
pub use self::user::{Invitation, User, UserStampException};
pub use self::user::{Invitation, User, UserKdfType, UserStampException};
11 changes: 10 additions & 1 deletion src/db/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ db_object! {

pub client_kdf_type: i32,
pub client_kdf_iter: i32,
pub client_kdf_memory: Option<i32>,
pub client_kdf_parallelism: Option<i32>,

pub api_key: Option<String>,

Expand All @@ -58,6 +60,11 @@ db_object! {
}
}

pub enum UserKdfType {
Pbkdf2 = 0,
Argon2id = 1,
}

enum UserStatus {
Enabled = 0,
Invited = 1,
Expand All @@ -73,7 +80,7 @@ pub struct UserStampException {

/// Local methods
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 fn new(email: String) -> Self {
Expand Down Expand Up @@ -113,6 +120,8 @@ impl User {

client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
client_kdf_memory: None,
client_kdf_parallelism: None,

api_key: None,

Expand Down
2 changes: 2 additions & 0 deletions src/db/schemas/mysql/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ table! {
excluded_globals -> Text,
client_kdf_type -> Integer,
client_kdf_iter -> Integer,
client_kdf_memory -> Nullable<Integer>,
client_kdf_parallelism -> Nullable<Integer>,
api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>,
}
Expand Down
2 changes: 2 additions & 0 deletions src/db/schemas/postgresql/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ table! {
excluded_globals -> Text,
client_kdf_type -> Integer,
client_kdf_iter -> Integer,
client_kdf_memory -> Nullable<Integer>,
client_kdf_parallelism -> Nullable<Integer>,
api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>,
}
Expand Down
2 changes: 2 additions & 0 deletions src/db/schemas/sqlite/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ table! {
excluded_globals -> Text,
client_kdf_type -> Integer,
client_kdf_iter -> Integer,
client_kdf_memory -> Nullable<Integer>,
client_kdf_parallelism -> Nullable<Integer>,
api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>,
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
// 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.
// 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
#[cfg(feature = "enable_mimalloc")]
Expand Down
2 changes: 1 addition & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Fairing for AppHeaders {
base-uri 'self'; \
form-action 'self'; \
object-src 'self' blob:; \
script-src 'self'; \
script-src 'self' 'wasm-unsafe-eval'; \
style-src 'self' 'unsafe-inline'; \
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
Expand Down