From fa1b16b34b185b9e82ae1c3f949f928e4e4c391f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 13 Aug 2025 13:08:41 +0200 Subject: [PATCH 01/23] Add update SDK functionality --- .../src/key_management/crypto.rs | 59 ++++++++++ .../src/key_management/crypto_client.rs | 16 ++- .../src/key_management/master_password.rs | 104 +++++++++++++++++- 3 files changed, 174 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 800ce8085..54666d3d9 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -23,6 +23,9 @@ use crate::{ client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod}, error::StatefulCryptoError, key_management::{ + master_password::{ + MasterPasswordAuthenticationData, MasterPasswordError, MasterPasswordUnlockData, + }, AsymmetricKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId, }, Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError, @@ -39,6 +42,8 @@ pub enum CryptoClientError { VaultLocked(#[from] VaultLockedError), #[error(transparent)] Crypto(#[from] bitwarden_crypto::CryptoError), + #[error(transparent)] + MasterPassword(#[from] MasterPasswordError), } /// State used for initializing the user cryptographic state. @@ -265,6 +270,60 @@ pub(super) async fn get_user_encryption_key(client: &Client) -> Result Result { + let key_store = client.internal.get_key_store(); + let ctx = key_store.context(); + // FIXME: [PM-18099] Once MasterKey deals with KeyIds, this should be updated + #[allow(deprecated)] + let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; + + let login_method = client + .internal + .get_login_method() + .ok_or(NotAuthenticatedError)?; + let email = match login_method.as_ref() { + LoginMethod::User(UserLoginMethod::Username { email, .. }) + | LoginMethod::User(UserLoginMethod::ApiKey { email, .. }) => email, + #[cfg(feature = "secrets")] + LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?, + }; + + let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email) + .map_err(CryptoClientError::MasterPassword)?; + let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key) + .map_err(CryptoClientError::MasterPassword)?; + let old_authentication_data = MasterPasswordAuthenticationData::derive( + password, + &client.internal.get_kdf().unwrap(), + email, + ) + .map_err(CryptoClientError::MasterPassword)?; + + Ok(UpdateKdfResponse { + master_password_authentication_data: authentication_data, + master_password_unlock_data: unlock_data, + old_master_password_authentication_data: old_authentication_data, + }) +} + /// Response from the `update_password` function #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase", deny_unknown_fields)] diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index 961b3d19b..07ac22376 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::CryptoError; +use bitwarden_crypto::{CryptoError, Kdf}; #[cfg(feature = "internal")] use bitwarden_crypto::{EncString, UnsignedSharedKey}; #[cfg(feature = "wasm")] @@ -19,8 +19,8 @@ use crate::{ client::encryption_settings::EncryptionSettingsError, error::StatefulCryptoError, key_management::crypto::{ - get_v2_rotated_account_keys, make_v2_keys_for_v1_user, CryptoClientError, - UserCryptoV2KeysResponse, + get_v2_rotated_account_keys, make_v2_keys_for_v1_user, update_kdf, CryptoClientError, + UpdateKdfResponse, UserCryptoV2KeysResponse, }, Client, }; @@ -80,6 +80,16 @@ impl CryptoClient { ) -> Result { get_v2_rotated_account_keys(&self.client) } + + /// Update the user's password, which will re-encrypt the user's encryption key with the new + /// password. This returns the new encrypted user key and the new password hash. + pub fn update_kdf( + &self, + password: String, + kdf: Kdf, + ) -> Result { + update_kdf(&self.client, &password, &kdf) + } } impl CryptoClient { diff --git a/crates/bitwarden-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index 19c570570..b1bf39ad5 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -1,4 +1,4 @@ -//! Mobile specific master password operations +//! Master password operations //! //! This module contains the data structures and error handling for master password unlock //! operations. @@ -8,7 +8,7 @@ use std::num::NonZeroU32; use bitwarden_api_api::models::{ master_password_unlock_response_model::MasterPasswordUnlockResponseModel, KdfType, }; -use bitwarden_crypto::{CryptoError, EncString, Kdf}; +use bitwarden_crypto::{CryptoError, EncString, Kdf, MasterKey, SymmetricCryptoKey}; use bitwarden_error::bitwarden_error; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] @@ -43,6 +43,27 @@ pub struct MasterPasswordUnlockData { pub salt: String, } +impl MasterPasswordUnlockData { + pub(crate) fn derive( + password: &str, + kdf: &Kdf, + salt: &String, + user_key: &SymmetricCryptoKey, + ) -> Result { + let master_key = + MasterKey::derive(&password, &salt, &kdf).map_err(MasterPasswordError::Crypto)?; + let master_key_wrapped_user_key = master_key + .encrypt_user_key(user_key) + .map_err(MasterPasswordError::Crypto)?; + + Ok(Self { + kdf: kdf.clone(), + salt: salt.clone(), + master_key_wrapped_user_key, + }) + } +} + impl TryFrom for MasterPasswordUnlockData { type Error = MasterPasswordError; @@ -88,6 +109,45 @@ fn parse_nonzero_u32( NonZeroU32::new(num).ok_or(MissingFieldError(field_name)) } +/// Represents the data required to unlock with the master password. +#[allow(missing_docs)] +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr( + feature = "wasm", + derive(tsify::Tsify), + tsify(into_wasm_abi, from_wasm_abi) +)] +pub struct MasterPasswordAuthenticationData { + pub kdf: Kdf, + pub salt: String, + pub master_password_authentication_hash: String, +} + +impl MasterPasswordAuthenticationData { + pub(crate) fn derive( + password: &str, + kdf: &Kdf, + salt: &String, + ) -> Result { + let master_key = + MasterKey::derive(password, &salt, &kdf).map_err(MasterPasswordError::Crypto)?; + let hash = master_key + .derive_master_key_hash( + password.as_bytes(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ) + .map_err(MasterPasswordError::Crypto)?; + + Ok(Self { + kdf: kdf.clone(), + salt: salt.clone(), + master_password_authentication_hash: hash, + }) + } +} + #[cfg(test)] mod tests { use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel}; @@ -98,6 +158,46 @@ mod tests { const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI="; const TEST_SALT: &str = "test@example.com"; + const TEST_PASSWORD: &str = "test_password"; + const TEST_MASTER_PASSWORD_AUTHENTICATION_HASH: &str = + "Lyry95vlXEJ5FE0EXjeR9zgcsFSU0qGhP9l5X2jwE38="; + + #[test] + fn test_master_password_unlock_data_derive() { + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + let salt = TEST_SALT.to_string(); + let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let data = MasterPasswordUnlockData::derive(TEST_PASSWORD, &kdf, &salt, &user_key) + .expect("Failed to derive master password unlock data"); + assert_eq!(data.salt, salt); + assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000)); + + let master_key = MasterKey::derive(TEST_PASSWORD, &salt, &data.kdf) + .expect("Failed to derive master key"); + let decrypted_user_key = master_key + .decrypt_user_key(data.master_key_wrapped_user_key) + .expect("Failed to decrypt user key"); + assert_eq!(decrypted_user_key, user_key); + } + + #[test] + fn test_master_password_authentication_data_derive() { + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + let salt = TEST_SALT.to_string(); + let data = MasterPasswordAuthenticationData::derive(TEST_PASSWORD, &kdf, &salt) + .expect("Failed to derive master password authentication data"); + assert_eq!(data.salt, salt); + assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000)); + assert_eq!( + data.master_password_authentication_hash, + TEST_MASTER_PASSWORD_AUTHENTICATION_HASH + ); + } + #[test] fn test_try_from_master_password_unlock_response_model_pbkdf2_success() { let response = MasterPasswordUnlockResponseModel { From af83c57edb12b065adad2408019f3bf885b4c1e9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 13 Aug 2025 13:13:14 +0200 Subject: [PATCH 02/23] Add test --- .../src/key_management/crypto.rs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 54666d3d9..3ba81a070 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -796,6 +796,100 @@ mod tests { "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR"; const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG"; + #[tokio::test] + async fn test_update_kdf() { + let client = Client::new(None); + + let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap(); + + let kdf = Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }; + + initialize_user_crypto( + & client, + InitUserCryptoRequest { + user_id: Some(uuid::Uuid::new_v4()), + kdf_params: kdf.clone(), + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + signing_key: None, + security_state: None, + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".into(), + user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(), + }, + }, + ) + .await + .unwrap(); + + let new_kdf = Kdf::PBKDF2 { + iterations: 600_000.try_into().unwrap(), + }; + let new_kdf_response = update_kdf(&client, &"123412341234".to_string(), &new_kdf).unwrap(); + + let client2 = Client::new(None); + + initialize_user_crypto( + &client2, + InitUserCryptoRequest { + user_id: Some(uuid::Uuid::new_v4()), + kdf_params: new_kdf.clone(), + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + signing_key: None, + security_state: None, + method: InitUserCryptoMethod::Password { + password: "123412341234".into(), + user_key: new_kdf_response + .master_password_unlock_data + .master_key_wrapped_user_key, + }, + }, + ) + .await + .unwrap(); + + let new_hash = client2 + .kdf() + .hash_password( + "test@bitwarden.com".into(), + "123412341234".into(), + new_kdf.clone(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ) + .await + .unwrap(); + + assert_eq!( + new_hash, + new_kdf_response + .master_password_authentication_data + .master_password_authentication_hash + ); + + let client_key = { + let key_store = client.internal.get_key_store(); + let ctx = key_store.context(); + #[allow(deprecated)] + ctx.dangerous_get_symmetric_key(SymmetricKeyId::User) + .unwrap() + .to_base64() + }; + + let client2_key = { + let key_store = client2.internal.get_key_store(); + let ctx = key_store.context(); + #[allow(deprecated)] + ctx.dangerous_get_symmetric_key(SymmetricKeyId::User) + .unwrap() + .to_base64() + }; + + assert_eq!(client_key, client2_key); + } + #[tokio::test] async fn test_update_password() { let client = Client::new(None); From e780d69e494eb4a2e00775c39aeb4e772a97e785 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 13 Aug 2025 13:29:17 +0200 Subject: [PATCH 03/23] Cleanup --- crates/bitwarden-core/src/key_management/master_password.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index b1bf39ad5..a20017618 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -51,7 +51,7 @@ impl MasterPasswordUnlockData { user_key: &SymmetricCryptoKey, ) -> Result { let master_key = - MasterKey::derive(&password, &salt, &kdf).map_err(MasterPasswordError::Crypto)?; + MasterKey::derive(password, salt, kdf).map_err(MasterPasswordError::Crypto)?; let master_key_wrapped_user_key = master_key .encrypt_user_key(user_key) .map_err(MasterPasswordError::Crypto)?; @@ -132,7 +132,7 @@ impl MasterPasswordAuthenticationData { salt: &String, ) -> Result { let master_key = - MasterKey::derive(password, &salt, &kdf).map_err(MasterPasswordError::Crypto)?; + MasterKey::derive(password, salt, kdf).map_err(MasterPasswordError::Crypto)?; let hash = master_key .derive_master_key_hash( password.as_bytes(), From bcc183abf6e0be6b63e321e63c2f0b1023f95259 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 14:41:00 +0200 Subject: [PATCH 04/23] Fix clippy issues --- crates/bitwarden-core/src/key_management/crypto.rs | 2 +- .../bitwarden-core/src/key_management/master_password.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 3ba81a070..3fd823ac0 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -312,7 +312,7 @@ pub(super) fn update_kdf( .map_err(CryptoClientError::MasterPassword)?; let old_authentication_data = MasterPasswordAuthenticationData::derive( password, - &client.internal.get_kdf().unwrap(), + &client.internal.get_kdf().ok_or(NotAuthenticatedError)?, email, ) .map_err(CryptoClientError::MasterPassword)?; diff --git a/crates/bitwarden-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index a20017618..1f6949b4b 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -47,7 +47,7 @@ impl MasterPasswordUnlockData { pub(crate) fn derive( password: &str, kdf: &Kdf, - salt: &String, + salt: &str, user_key: &SymmetricCryptoKey, ) -> Result { let master_key = @@ -58,7 +58,7 @@ impl MasterPasswordUnlockData { Ok(Self { kdf: kdf.clone(), - salt: salt.clone(), + salt: salt.to_owned(), master_key_wrapped_user_key, }) } @@ -129,7 +129,7 @@ impl MasterPasswordAuthenticationData { pub(crate) fn derive( password: &str, kdf: &Kdf, - salt: &String, + salt: &str, ) -> Result { let master_key = MasterKey::derive(password, salt, kdf).map_err(MasterPasswordError::Crypto)?; @@ -142,7 +142,7 @@ impl MasterPasswordAuthenticationData { Ok(Self { kdf: kdf.clone(), - salt: salt.clone(), + salt: salt.to_owned(), master_password_authentication_hash: hash, }) } From 6398e13df788ee9f6b34636593116fe99ce1bd4e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 14:45:32 +0200 Subject: [PATCH 05/23] Fix build --- crates/bitwarden-core/src/key_management/crypto.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 3fd823ac0..aa0d90eb7 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -312,7 +312,10 @@ pub(super) fn update_kdf( .map_err(CryptoClientError::MasterPassword)?; let old_authentication_data = MasterPasswordAuthenticationData::derive( password, - &client.internal.get_kdf().ok_or(NotAuthenticatedError)?, + &client + .internal + .get_kdf() + .map_err(|_| NotAuthenticatedError)?, email, ) .map_err(CryptoClientError::MasterPassword)?; From 21c72e00d6d80d3428296f5806385799200721c1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 14:51:43 +0200 Subject: [PATCH 06/23] Fix build --- crates/bitwarden-core/src/key_management/master_password.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index d131bf302..cba4ab0a4 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -6,7 +6,7 @@ use std::num::NonZeroU32; use bitwarden_api_api::models::{ master_password_unlock_response_model::MasterPasswordUnlockResponseModel, KdfType, }; -use bitwarden_crypto::{CryptoError, EncString, Kdf, MasterKey, SymmetricCryptoKey +use bitwarden_crypto::{EncString, Kdf, MasterKey, SymmetricCryptoKey}; use bitwarden_error::bitwarden_error; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] From 3dc35779a7dd8794adf45089ac39eb8855ccaff7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 17:01:11 +0200 Subject: [PATCH 07/23] Clean-up errors --- .../bitwarden-core/src/auth/login/password.rs | 2 +- .../bitwarden-core/src/auth/password/mod.rs | 2 +- .../src/auth/password/validate.rs | 2 +- crates/bitwarden-core/src/auth/register.rs | 2 +- .../src/key_management/crypto.rs | 2 +- .../src/key_management/master_password.rs | 24 +++++++++++-------- crates/bitwarden-core/src/mobile/kdf.rs | 2 +- .../src/platform/get_user_api_key.rs | 7 +++--- .../bitwarden-crypto/src/keys/master_key.rs | 12 +++++----- crates/memory-testing/src/main.rs | 3 +-- 10 files changed, 31 insertions(+), 27 deletions(-) diff --git a/crates/bitwarden-core/src/auth/login/password.rs b/crates/bitwarden-core/src/auth/login/password.rs index 28e33ff7e..a55d62654 100644 --- a/crates/bitwarden-core/src/auth/login/password.rs +++ b/crates/bitwarden-core/src/auth/login/password.rs @@ -31,7 +31,7 @@ pub(crate) async fn login_password( let master_key = MasterKey::derive(&input.password, &input.email, &input.kdf)?; let password_hash = master_key - .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?; + .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization); let response = request_identity_tokens(client, input, &password_hash).await?; diff --git a/crates/bitwarden-core/src/auth/password/mod.rs b/crates/bitwarden-core/src/auth/password/mod.rs index f08e9bd39..6b5c2710e 100644 --- a/crates/bitwarden-core/src/auth/password/mod.rs +++ b/crates/bitwarden-core/src/auth/password/mod.rs @@ -15,7 +15,7 @@ pub(crate) fn determine_password_hash( purpose: HashPurpose, ) -> Result { let master_key = MasterKey::derive(password, email, kdf)?; - master_key.derive_master_key_hash(password.as_bytes(), purpose) + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)) } #[cfg(test)] diff --git a/crates/bitwarden-core/src/auth/password/validate.rs b/crates/bitwarden-core/src/auth/password/validate.rs index d8c9a9ac9..fb8ff3316 100644 --- a/crates/bitwarden-core/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -70,7 +70,7 @@ pub(crate) fn validate_password_user_key( } Ok(master_key - .derive_master_key_hash(password.as_bytes(), HashPurpose::LocalAuthorization)?) + .derive_master_key_hash(password.as_bytes(), HashPurpose::LocalAuthorization)) } } } else { diff --git a/crates/bitwarden-core/src/auth/register.rs b/crates/bitwarden-core/src/auth/register.rs index 52297e7a2..54cb44e64 100644 --- a/crates/bitwarden-core/src/auth/register.rs +++ b/crates/bitwarden-core/src/auth/register.rs @@ -30,7 +30,7 @@ pub(super) fn make_register_keys( ) -> Result { let master_key = MasterKey::derive(&password, &email, &kdf)?; let master_password_hash = - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)?; + master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization); let (user_key, encrypted_user_key) = master_key.make_user_key()?; let keys = user_key.make_key_pair()?; diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index aa0d90eb7..7b6e5a16d 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -369,7 +369,7 @@ pub(super) fn update_password( let password_hash = new_master_key.derive_master_key_hash( new_password.as_bytes(), bitwarden_crypto::HashPurpose::ServerAuthorization, - )?; + ); Ok(UpdatePasswordResponse { password_hash, diff --git a/crates/bitwarden-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index cba4ab0a4..93247ddd8 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -24,9 +24,15 @@ pub enum MasterPasswordError { /// The KDF data could not be parsed, because it is missing values or has an invalid value #[error("KDF is malformed")] KdfMalformed, + /// The KDF had an invalid configuration + #[error("Invalid KDF configuration")] + InvalidKdfConfiguration, /// The wrapped encryption key or salt fields are missing or KDF data is malformed #[error(transparent)] MissingField(#[from] MissingFieldError), + /// Generic crypto error + #[error(transparent)] + Crypto(#[from] bitwarden_crypto::CryptoError), } /// Represents the data required to unlock with the master password. @@ -54,8 +60,8 @@ impl MasterPasswordUnlockData { salt: &str, user_key: &SymmetricCryptoKey, ) -> Result { - let master_key = - MasterKey::derive(password, salt, kdf).map_err(MasterPasswordError::Crypto)?; + let master_key = MasterKey::derive(password, salt, kdf) + .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?; let master_key_wrapped_user_key = master_key .encrypt_user_key(user_key) .map_err(MasterPasswordError::Crypto)?; @@ -136,14 +142,12 @@ impl MasterPasswordAuthenticationData { kdf: &Kdf, salt: &str, ) -> Result { - let master_key = - MasterKey::derive(password, salt, kdf).map_err(MasterPasswordError::Crypto)?; - let hash = master_key - .derive_master_key_hash( - password.as_bytes(), - bitwarden_crypto::HashPurpose::ServerAuthorization, - ) - .map_err(MasterPasswordError::Crypto)?; + let master_key = MasterKey::derive(password, salt, kdf) + .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?; + let hash = master_key.derive_master_key_hash( + password.as_bytes(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ); Ok(Self { kdf: kdf.clone(), diff --git a/crates/bitwarden-core/src/mobile/kdf.rs b/crates/bitwarden-core/src/mobile/kdf.rs index 4d7f9a709..7278e18b8 100644 --- a/crates/bitwarden-core/src/mobile/kdf.rs +++ b/crates/bitwarden-core/src/mobile/kdf.rs @@ -8,5 +8,5 @@ pub(super) async fn hash_password( ) -> Result { let master_key = MasterKey::derive(&password, &email, &kdf_params)?; - master_key.derive_master_key_hash(password.as_bytes(), purpose) + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)) } diff --git a/crates/bitwarden-core/src/platform/get_user_api_key.rs b/crates/bitwarden-core/src/platform/get_user_api_key.rs index fad5ae5ed..180f47904 100644 --- a/crates/bitwarden-core/src/platform/get_user_api_key.rs +++ b/crates/bitwarden-core/src/platform/get_user_api_key.rs @@ -18,7 +18,7 @@ use bitwarden_api_api::{ apis::accounts_api::accounts_api_key_post, models::{ApiKeyResponseModel, SecretVerificationRequestModel}, }; -use bitwarden_crypto::{HashPurpose, MasterKey}; +use bitwarden_crypto::{CryptoError, HashPurpose, MasterKey}; use log::{debug, info}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -78,10 +78,11 @@ fn build_secret_verification_request( let master_password_hash = input .master_password .as_ref() - .map(|p| { + .map(|p| -> Result { let master_key = MasterKey::derive(p, email, kdf)?; - master_key.derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization) + Ok(master_key + .derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization)) }) .transpose()?; Ok(SecretVerificationRequestModel { diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 653d34b7f..52c12892f 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -62,10 +62,12 @@ impl MasterKey { } /// Derive the master key hash, used for local and remote password validation. - pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> Result { - let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32); - - Ok(STANDARD.encode(hash)) + pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> String { + STANDARD.encode(util::pbkdf2( + self.inner_bytes().as_slice(), + password, + purpose as u32, + )) } /// Generate a new random user key and encrypt it with the master key. @@ -211,7 +213,6 @@ mod tests { "wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw=", master_key .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) - .unwrap(), ); } } @@ -234,7 +235,6 @@ mod tests { "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=", master_key .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) - .unwrap(), ); } diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs index 67f38b0e8..8eb7cf442 100644 --- a/crates/memory-testing/src/main.rs +++ b/crates/memory-testing/src/main.rs @@ -44,8 +44,7 @@ fn main() { .derive_master_key_hash( password.as_bytes(), bitwarden_crypto::HashPurpose::ServerAuthorization, - ) - .unwrap(); + ); master_keys.push((key, hash)); } From 367f9f08483492bcd1d02db26c765a966c382aad Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 17:07:10 +0200 Subject: [PATCH 08/23] Fix formatting --- crates/memory-testing/src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs index 8eb7cf442..96070b216 100644 --- a/crates/memory-testing/src/main.rs +++ b/crates/memory-testing/src/main.rs @@ -40,11 +40,10 @@ fn main() { kdf, } => { let key = MasterKey::derive(&password, &email, &kdf).unwrap(); - let hash = key - .derive_master_key_hash( - password.as_bytes(), - bitwarden_crypto::HashPurpose::ServerAuthorization, - ); + let hash = key.derive_master_key_hash( + password.as_bytes(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ); master_keys.push((key, hash)); } From 3d9b2576b69f73c2d921eef0c6a0d7cf99d7abef Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 17:28:23 +0200 Subject: [PATCH 09/23] Replace String with str --- crates/bitwarden-core/src/key_management/crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 7b6e5a16d..970673887 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -286,7 +286,7 @@ pub struct UpdateKdfResponse { pub(super) fn update_kdf( client: &Client, - password: &String, + password: &str, new_kdf: &Kdf, ) -> Result { let key_store = client.internal.get_key_store(); From 69ed59cc2cce80c4d9108c83ad0cf2c8249a20b5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 17:50:22 +0200 Subject: [PATCH 10/23] Fix wasm being imported on UNIFFI builds --- crates/bitwarden-vault/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-vault/Cargo.toml b/crates/bitwarden-vault/Cargo.toml index 5ceab6aaa..2b99c5407 100644 --- a/crates/bitwarden-vault/Cargo.toml +++ b/crates/bitwarden-vault/Cargo.toml @@ -22,6 +22,7 @@ uniffi = [ ] # Uniffi bindings wasm = [ "bitwarden-core/wasm", + "bitwarden-collections/wasm", "dep:tsify", "dep:wasm-bindgen", "dep:wasm-bindgen-futures" @@ -30,7 +31,7 @@ wasm = [ [dependencies] base64 = ">=0.22.1, <0.23" bitwarden-api-api = { workspace = true } -bitwarden-collections = { workspace = true, features = ["wasm"] } +bitwarden-collections = { workspace = true } bitwarden-core = { workspace = true, features = ["internal"] } bitwarden-crypto = { workspace = true } bitwarden-error = { workspace = true } From 866912bda69255f7759dd64751ad58f58ccc3553 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 18:24:53 +0200 Subject: [PATCH 11/23] Feature-flag wasm-only code and fix import --- crates/bitwarden-exporters/Cargo.toml | 3 ++- crates/bitwarden-vault/src/collection_client.rs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml index 51fc4d05e..1ff54a3db 100644 --- a/crates/bitwarden-exporters/Cargo.toml +++ b/crates/bitwarden-exporters/Cargo.toml @@ -19,13 +19,14 @@ keywords.workspace = true uniffi = ["dep:uniffi", "bitwarden-core/uniffi"] # Uniffi bindings wasm = [ "bitwarden-vault/wasm", + "bitwarden-collections/wasm", "dep:tsify", "dep:wasm-bindgen" ] # WebAssembly bindings [dependencies] base64 = ">=0.22.1, <0.23" -bitwarden-collections = { workspace = true, features = ["wasm"] } +bitwarden-collections = { workspace = true } bitwarden-core = { workspace = true } bitwarden-crypto = { workspace = true } bitwarden-error = { workspace = true } diff --git a/crates/bitwarden-vault/src/collection_client.rs b/crates/bitwarden-vault/src/collection_client.rs index 4856c84d0..80e092df3 100644 --- a/crates/bitwarden-vault/src/collection_client.rs +++ b/crates/bitwarden-vault/src/collection_client.rs @@ -3,6 +3,7 @@ use bitwarden_collections::{ tree::{NodeItem, Tree}, }; use bitwarden_core::Client; +#[cfg(feature = "wasm")] use bitwarden_error::js_sys::Map; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::wasm_bindgen; @@ -69,6 +70,7 @@ impl CollectionViewNodeItem { self.node_item.children.clone() } + #[cfg(feature = "wasm")] pub fn get_ancestors(&self) -> Map { self.node_item .ancestors From 0aba54b35cc2d1b1286bf9871d8347d01b520b88 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 19:19:44 +0200 Subject: [PATCH 12/23] Fix clippy issues --- crates/bitwarden-core/src/key_management/crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 970673887..3a2410f7d 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -830,7 +830,7 @@ mod tests { let new_kdf = Kdf::PBKDF2 { iterations: 600_000.try_into().unwrap(), }; - let new_kdf_response = update_kdf(&client, &"123412341234".to_string(), &new_kdf).unwrap(); + let new_kdf_response = update_kdf(&client, &"123412341234", &new_kdf).unwrap(); let client2 = Client::new(None); From 4e41a295b1602a6b1c529751ebe7030db4e22952 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 14 Aug 2025 19:28:14 +0200 Subject: [PATCH 13/23] Fix clippy issue --- crates/bitwarden-core/src/key_management/crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 3a2410f7d..6f22b59d9 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -830,7 +830,7 @@ mod tests { let new_kdf = Kdf::PBKDF2 { iterations: 600_000.try_into().unwrap(), }; - let new_kdf_response = update_kdf(&client, &"123412341234", &new_kdf).unwrap(); + let new_kdf_response = update_kdf(&client, "123412341234", &new_kdf).unwrap(); let client2 = Client::new(None); From 9f2c47365af12c13e214864aba686ad33479579c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 15 Aug 2025 18:55:16 +0200 Subject: [PATCH 14/23] Expose update kdf in uniffi --- .../src/key_management/crypto_client.rs | 4 ++-- crates/bitwarden-uniffi/src/crypto.rs | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index 07ac22376..d82a78763 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -81,8 +81,8 @@ impl CryptoClient { get_v2_rotated_account_keys(&self.client) } - /// Update the user's password, which will re-encrypt the user's encryption key with the new - /// password. This returns the new encrypted user key and the new password hash. + /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new + /// kdf settings. This returns the new encrypted user key and the new password hash. pub fn update_kdf( &self, password: String, diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 56ddb7a73..99e0c96e7 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -2,7 +2,7 @@ use bitwarden_core::key_management::crypto::{ DeriveKeyConnectorRequest, DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, }; -use bitwarden_crypto::{EncString, UnsignedSharedKey}; +use bitwarden_crypto::{EncString, Kdf, UnsignedSharedKey}; use crate::error::{Error, Result}; @@ -81,4 +81,17 @@ impl CryptoClient { .derive_key_connector(request) .map_err(Error::DeriveKeyConnector)?) } + + /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new + /// kdf settings. This returns the new encrypted user key and the new password hash. + pub fn update_kdf( + &self, + password: String, + kdf: Kdf, + ) -> Result { + Ok(self + .0 + .update_kdf(password, kdf) + .map_err(Error::MobileCrypto)?) + } } From 48bfbdabac9f6d95cdf084f5b1605cf975d58af9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 08:46:24 +0200 Subject: [PATCH 15/23] Update crates/bitwarden-core/src/key_management/crypto.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel García --- crates/bitwarden-core/src/key_management/crypto.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 6f22b59d9..4a86bb20b 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -300,8 +300,9 @@ pub(super) fn update_kdf( .get_login_method() .ok_or(NotAuthenticatedError)?; let email = match login_method.as_ref() { - LoginMethod::User(UserLoginMethod::Username { email, .. }) - | LoginMethod::User(UserLoginMethod::ApiKey { email, .. }) => email, + LoginMethod::User( + UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. }, + ) => email, #[cfg(feature = "secrets")] LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?, }; From b4d89e28d9693528f92b9df971b9bd93cf8e8e7d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 08:46:35 +0200 Subject: [PATCH 16/23] Update crates/bitwarden-core/src/key_management/crypto.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel García --- crates/bitwarden-core/src/key_management/crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 4a86bb20b..3ed66323b 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -280,7 +280,7 @@ pub struct UpdateKdfResponse { master_password_authentication_data: MasterPasswordAuthenticationData, /// The unlock data for the new KDF setting master_password_unlock_data: MasterPasswordUnlockData, - /// The authentication data for the prior to the KDF change + /// The authentication data for the KDF setting prior to the change old_master_password_authentication_data: MasterPasswordAuthenticationData, } From aeaeff52a3dcff7611b2980843d018dcfa9cffe1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 10:31:10 +0200 Subject: [PATCH 17/23] Rename to make_update --- .../src/key_management/crypto.rs | 8 +++---- .../src/key_management/crypto_client.rs | 22 ++++++++++++++----- crates/bitwarden-uniffi/src/crypto.rs | 13 ++++++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 6f22b59d9..01c7c5cc8 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -284,7 +284,7 @@ pub struct UpdateKdfResponse { old_master_password_authentication_data: MasterPasswordAuthenticationData, } -pub(super) fn update_kdf( +pub(super) fn make_update_kdf( client: &Client, password: &str, new_kdf: &Kdf, @@ -339,7 +339,7 @@ pub struct UpdatePasswordResponse { new_key: EncString, } -pub(super) fn update_password( +pub(super) fn make_update_password( client: &Client, new_password: String, ) -> Result { @@ -830,7 +830,7 @@ mod tests { let new_kdf = Kdf::PBKDF2 { iterations: 600_000.try_into().unwrap(), }; - let new_kdf_response = update_kdf(&client, "123412341234", &new_kdf).unwrap(); + let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf).unwrap(); let client2 = Client::new(None); @@ -921,7 +921,7 @@ mod tests { .await .unwrap(); - let new_password_response = update_password(&client, "123412341234".into()).unwrap(); + let new_password_response = make_update_password(&client, "123412341234".into()).unwrap(); let client2 = Client::new(None); diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index d82a78763..e2a5d3526 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -12,14 +12,14 @@ use super::crypto::{ #[cfg(feature = "internal")] use crate::key_management::crypto::{ derive_pin_key, derive_pin_user_key, enroll_admin_password_reset, get_user_encryption_key, - initialize_org_crypto, initialize_user_crypto, update_password, DerivePinKeyResponse, + initialize_org_crypto, initialize_user_crypto, make_update_password, DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, }; use crate::{ client::encryption_settings::EncryptionSettingsError, error::StatefulCryptoError, key_management::crypto::{ - get_v2_rotated_account_keys, make_v2_keys_for_v1_user, update_kdf, CryptoClientError, + get_v2_rotated_account_keys, make_update_kdf, make_v2_keys_for_v1_user, CryptoClientError, UpdateKdfResponse, UserCryptoV2KeysResponse, }, Client, @@ -83,12 +83,12 @@ impl CryptoClient { /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new /// kdf settings. This returns the new encrypted user key and the new password hash. - pub fn update_kdf( + pub fn make_update_kdf( &self, password: String, kdf: Kdf, ) -> Result { - update_kdf(&self.client, &password, &kdf) + make_update_kdf(&self.client, &password, &kdf) } } @@ -101,11 +101,23 @@ impl CryptoClient { /// Update the user's password, which will re-encrypt the user's encryption key with the new /// password. This returns the new encrypted user key and the new password hash. + /// + /// Note: This function is deprecated and will be replaced by `make_update_password` pub fn update_password( &self, new_password: String, ) -> Result { - update_password(&self.client, new_password) + self.make_update_password(new_password) + } + + /// Update the user's password, which will re-encrypt the user's encryption key with the new + /// password. This returns the new encrypted user key and the new password hash but does + /// not modify state. + pub fn make_update_password( + &self, + new_password: String, + ) -> Result { + make_update_password(&self.client, new_password) } /// Generates a PIN protected user key from the provided PIN. The result can be stored and later diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 99e0c96e7..c01dc1d44 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -45,9 +45,16 @@ impl CryptoClient { /// Update the user's password, which will re-encrypt the user's encryption key with the new /// password. This returns the new encrypted user key and the new password hash. pub fn update_password(&self, new_password: String) -> Result { + self.make_update_password(new_password) + } + + /// Update the user's password, which will re-encrypt the user's encryption key with the new + /// password. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. + pub fn make_update_password(&self, new_password: String) -> Result { Ok(self .0 - .update_password(new_password) + .make_update_password(new_password) .map_err(Error::MobileCrypto)?) } @@ -84,14 +91,14 @@ impl CryptoClient { /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new /// kdf settings. This returns the new encrypted user key and the new password hash. - pub fn update_kdf( + pub fn make_update_kdf( &self, password: String, kdf: Kdf, ) -> Result { Ok(self .0 - .update_kdf(password, kdf) + .make_update_kdf(password, kdf) .map_err(Error::MobileCrypto)?) } } From 1a3dd14531f3d3cb966d3797bfc0deff3a257f3d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 13:05:21 +0200 Subject: [PATCH 18/23] Update comments --- .../src/key_management/crypto_client.rs | 20 +++++++++++-------- crates/bitwarden-uniffi/src/crypto.rs | 16 ++++++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index e2a5d3526..c9f47b9ed 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -81,8 +81,9 @@ impl CryptoClient { get_v2_rotated_account_keys(&self.client) } - /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new - /// kdf settings. This returns the new encrypted user key and the new password hash. + /// Create the data necessary to update the user's kdf settings. The user's encryption key is re-encrypted + /// for the password under the new kdf settings. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. pub fn make_update_kdf( &self, password: String, @@ -99,10 +100,12 @@ impl CryptoClient { get_user_encryption_key(&self.client).await } - /// Update the user's password, which will re-encrypt the user's encryption key with the new - /// password. This returns the new encrypted user key and the new password hash. + /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted + /// with the new + /// password. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. /// - /// Note: This function is deprecated and will be replaced by `make_update_password` + /// Note: This is deprecated and `make_update_password` should be used instead pub fn update_password( &self, new_password: String, @@ -110,9 +113,10 @@ impl CryptoClient { self.make_update_password(new_password) } - /// Update the user's password, which will re-encrypt the user's encryption key with the new - /// password. This returns the new encrypted user key and the new password hash but does - /// not modify state. + /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted + /// with the new + /// password. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. pub fn make_update_password( &self, new_password: String, diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index c01dc1d44..7606ad2d4 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -42,13 +42,18 @@ impl CryptoClient { .map_err(Error::MobileCrypto)?) } - /// Update the user's password, which will re-encrypt the user's encryption key with the new - /// password. This returns the new encrypted user key and the new password hash. + /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted + /// with the new + /// password. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. + /// + /// Note: This is deprecated and `make_update_password` should be used instead pub fn update_password(&self, new_password: String) -> Result { self.make_update_password(new_password) } - /// Update the user's password, which will re-encrypt the user's encryption key with the new + /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted + /// with the new /// password. This returns the new encrypted user key and the new password hash but does not /// update sdk state. pub fn make_update_password(&self, new_password: String) -> Result { @@ -89,8 +94,9 @@ impl CryptoClient { .map_err(Error::DeriveKeyConnector)?) } - /// Update the user's kdf settings, which will re-encrypt the user's encryption key with the new - /// kdf settings. This returns the new encrypted user key and the new password hash. + /// Create the data necessary to update the user's kdf settings. The user's encryption key is re-encrypted + /// for the password under the new kdf settings. This returns the new encrypted user key and the new password hash but does not + /// update sdk state. pub fn make_update_kdf( &self, password: String, From 55cc2b7a840056873ad08fe6613fc5d1e97cbd63 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 13:08:49 +0200 Subject: [PATCH 19/23] Rename error variant --- crates/bitwarden-core/src/key_management/crypto.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index a6c1c5aa3..2d65f4f5c 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -43,7 +43,7 @@ pub enum CryptoClientError { #[error(transparent)] Crypto(#[from] bitwarden_crypto::CryptoError), #[error(transparent)] - MasterPassword(#[from] MasterPasswordError), + InvalidKdfSettings(#[from] MasterPasswordError), } /// State used for initializing the user cryptographic state. @@ -308,9 +308,9 @@ pub(super) fn make_update_kdf( }; let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email) - .map_err(CryptoClientError::MasterPassword)?; + .map_err(CryptoClientError::InvalidKdfSettings)?; let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key) - .map_err(CryptoClientError::MasterPassword)?; + .map_err(CryptoClientError::InvalidKdfSettings)?; let old_authentication_data = MasterPasswordAuthenticationData::derive( password, &client @@ -319,7 +319,7 @@ pub(super) fn make_update_kdf( .map_err(|_| NotAuthenticatedError)?, email, ) - .map_err(CryptoClientError::MasterPassword)?; + .map_err(CryptoClientError::InvalidKdfSettings)?; Ok(UpdateKdfResponse { master_password_authentication_data: authentication_data, From dd5dd3ee28f8701561d45b64ab5cdf60a38739be Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 13:10:45 +0200 Subject: [PATCH 20/23] Rename error variant --- crates/bitwarden-core/src/key_management/crypto.rs | 10 +++++----- .../src/key_management/crypto_client.rs | 14 +++++++------- crates/bitwarden-uniffi/src/crypto.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 2d65f4f5c..677699f78 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -42,8 +42,8 @@ pub enum CryptoClientError { VaultLocked(#[from] VaultLockedError), #[error(transparent)] Crypto(#[from] bitwarden_crypto::CryptoError), - #[error(transparent)] - InvalidKdfSettings(#[from] MasterPasswordError), + #[error("Invalid KDF settings")] + InvalidKdfSettings, } /// State used for initializing the user cryptographic state. @@ -308,9 +308,9 @@ pub(super) fn make_update_kdf( }; let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email) - .map_err(CryptoClientError::InvalidKdfSettings)?; + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key) - .map_err(CryptoClientError::InvalidKdfSettings)?; + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; let old_authentication_data = MasterPasswordAuthenticationData::derive( password, &client @@ -319,7 +319,7 @@ pub(super) fn make_update_kdf( .map_err(|_| NotAuthenticatedError)?, email, ) - .map_err(CryptoClientError::InvalidKdfSettings)?; + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; Ok(UpdateKdfResponse { master_password_authentication_data: authentication_data, diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index c9f47b9ed..acc269261 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -81,9 +81,9 @@ impl CryptoClient { get_v2_rotated_account_keys(&self.client) } - /// Create the data necessary to update the user's kdf settings. The user's encryption key is re-encrypted - /// for the password under the new kdf settings. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// Create the data necessary to update the user's kdf settings. The user's encryption key is + /// re-encrypted for the password under the new kdf settings. This returns the new encrypted + /// user key and the new password hash but does not update sdk state. pub fn make_update_kdf( &self, password: String, @@ -100,8 +100,8 @@ impl CryptoClient { get_user_encryption_key(&self.client).await } - /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted - /// with the new + /// Create the data necessary to update the user's password. The user's encryption key is + /// re-encrypted with the new /// password. This returns the new encrypted user key and the new password hash but does not /// update sdk state. /// @@ -113,8 +113,8 @@ impl CryptoClient { self.make_update_password(new_password) } - /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted - /// with the new + /// Create the data necessary to update the user's password. The user's encryption key is + /// re-encrypted with the new /// password. This returns the new encrypted user key and the new password hash but does not /// update sdk state. pub fn make_update_password( diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 7606ad2d4..78abc3b51 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -42,8 +42,8 @@ impl CryptoClient { .map_err(Error::MobileCrypto)?) } - /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted - /// with the new + /// Create the data necessary to update the user's password. The user's encryption key is + /// re-encrypted with the new /// password. This returns the new encrypted user key and the new password hash but does not /// update sdk state. /// @@ -52,8 +52,8 @@ impl CryptoClient { self.make_update_password(new_password) } - /// Create the data necessary to update the user's password. The user's encryption key is re-encrypted - /// with the new + /// Create the data necessary to update the user's password. The user's encryption key is + /// re-encrypted with the new /// password. This returns the new encrypted user key and the new password hash but does not /// update sdk state. pub fn make_update_password(&self, new_password: String) -> Result { @@ -94,9 +94,9 @@ impl CryptoClient { .map_err(Error::DeriveKeyConnector)?) } - /// Create the data necessary to update the user's kdf settings. The user's encryption key is re-encrypted - /// for the password under the new kdf settings. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// Create the data necessary to update the user's kdf settings. The user's encryption key is + /// re-encrypted for the password under the new kdf settings. This returns the new encrypted + /// user key and the new password hash but does not update sdk state. pub fn make_update_kdf( &self, password: String, From 20b5a0666febf5dd73be1312552d16ba54d6f5e4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 13:16:35 +0200 Subject: [PATCH 21/23] Fix build --- crates/bitwarden-core/src/key_management/crypto.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto.rs b/crates/bitwarden-core/src/key_management/crypto.rs index 677699f78..75ca3a9ee 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -23,9 +23,7 @@ use crate::{ client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod}, error::StatefulCryptoError, key_management::{ - master_password::{ - MasterPasswordAuthenticationData, MasterPasswordError, MasterPasswordUnlockData, - }, + master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData}, AsymmetricKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId, }, Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError, From 5cf50d29583deff402c542b0359dfb4d817050cb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 13:25:39 +0200 Subject: [PATCH 22/23] Undo duplicate upstream change --- crates/bitwarden-vault/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bitwarden-vault/Cargo.toml b/crates/bitwarden-vault/Cargo.toml index ae7918d97..6f1cf2828 100644 --- a/crates/bitwarden-vault/Cargo.toml +++ b/crates/bitwarden-vault/Cargo.toml @@ -23,7 +23,6 @@ uniffi = [ wasm = [ "bitwarden-collections/wasm", "bitwarden-core/wasm", - "bitwarden-collections/wasm", "dep:tsify", "dep:wasm-bindgen", "dep:wasm-bindgen-futures" From 6cdb29bd38478357b179a725c951753c91b859d7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 19 Aug 2025 14:09:47 +0200 Subject: [PATCH 23/23] Fix formatting --- .../bitwarden-core/src/key_management/crypto_client.rs | 10 ++++------ crates/bitwarden-uniffi/src/crypto.rs | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/bitwarden-core/src/key_management/crypto_client.rs b/crates/bitwarden-core/src/key_management/crypto_client.rs index acc269261..3b46f05ac 100644 --- a/crates/bitwarden-core/src/key_management/crypto_client.rs +++ b/crates/bitwarden-core/src/key_management/crypto_client.rs @@ -101,9 +101,8 @@ impl CryptoClient { } /// Create the data necessary to update the user's password. The user's encryption key is - /// re-encrypted with the new - /// password. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// re-encrypted with the new password. This returns the new encrypted user key and the new + /// password hash but does not update sdk state. /// /// Note: This is deprecated and `make_update_password` should be used instead pub fn update_password( @@ -114,9 +113,8 @@ impl CryptoClient { } /// Create the data necessary to update the user's password. The user's encryption key is - /// re-encrypted with the new - /// password. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// re-encrypted with the new password. This returns the new encrypted user key and the new + /// password hash but does not update sdk state. pub fn make_update_password( &self, new_password: String, diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 78abc3b51..62a45665d 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -43,9 +43,8 @@ impl CryptoClient { } /// Create the data necessary to update the user's password. The user's encryption key is - /// re-encrypted with the new - /// password. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// re-encrypted with the new password. This returns the new encrypted user key and the new + /// password hash but does not update sdk state. /// /// Note: This is deprecated and `make_update_password` should be used instead pub fn update_password(&self, new_password: String) -> Result { @@ -53,9 +52,8 @@ impl CryptoClient { } /// Create the data necessary to update the user's password. The user's encryption key is - /// re-encrypted with the new - /// password. This returns the new encrypted user key and the new password hash but does not - /// update sdk state. + /// re-encrypted with the new password. This returns the new encrypted user key and the new + /// password hash but does not update sdk state. pub fn make_update_password(&self, new_password: String) -> Result { Ok(self .0