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 800ce8085..75ca3a9ee 100644 --- a/crates/bitwarden-core/src/key_management/crypto.rs +++ b/crates/bitwarden-core/src/key_management/crypto.rs @@ -23,6 +23,7 @@ use crate::{ client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod}, error::StatefulCryptoError, key_management::{ + master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData}, AsymmetricKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId, }, Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError, @@ -39,6 +40,8 @@ pub enum CryptoClientError { VaultLocked(#[from] VaultLockedError), #[error(transparent)] Crypto(#[from] bitwarden_crypto::CryptoError), + #[error("Invalid KDF settings")] + InvalidKdfSettings, } /// State used for initializing the user cryptographic state. @@ -265,6 +268,64 @@ 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, .. } | UserLoginMethod::ApiKey { email, .. }, + ) => email, + #[cfg(feature = "secrets")] + LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?, + }; + + let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email) + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; + let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key) + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; + let old_authentication_data = MasterPasswordAuthenticationData::derive( + password, + &client + .internal + .get_kdf() + .map_err(|_| NotAuthenticatedError)?, + email, + ) + .map_err(|_| CryptoClientError::InvalidKdfSettings)?; + + 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)] @@ -277,7 +338,7 @@ pub struct UpdatePasswordResponse { new_key: EncString, } -pub(super) fn update_password( +pub(super) fn make_update_password( client: &Client, new_password: String, ) -> Result { @@ -307,7 +368,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, @@ -737,6 +798,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 = make_update_kdf(&client, "123412341234", &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); @@ -765,7 +920,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 961b3d19b..3b46f05ac 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")] @@ -12,15 +12,15 @@ 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, CryptoClientError, - UserCryptoV2KeysResponse, + get_v2_rotated_account_keys, make_update_kdf, make_v2_keys_for_v1_user, CryptoClientError, + UpdateKdfResponse, UserCryptoV2KeysResponse, }, Client, }; @@ -80,6 +80,17 @@ impl CryptoClient { ) -> Result { 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. + pub fn make_update_kdf( + &self, + password: String, + kdf: Kdf, + ) -> Result { + make_update_kdf(&self.client, &password, &kdf) + } } impl CryptoClient { @@ -89,13 +100,26 @@ 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 is deprecated and `make_update_password` should be used instead pub fn update_password( &self, new_password: String, ) -> Result { - update_password(&self.client, new_password) + 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 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 { + 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-core/src/key_management/master_password.rs b/crates/bitwarden-core/src/key_management/master_password.rs index c68f92833..9dc70d967 100644 --- a/crates/bitwarden-core/src/key_management/master_password.rs +++ b/crates/bitwarden-core/src/key_management/master_password.rs @@ -3,7 +3,7 @@ use std::num::NonZeroU32; use bitwarden_api_api::models::{ master_password_unlock_response_model::MasterPasswordUnlockResponseModel, KdfType, }; -use bitwarden_crypto::{EncString, Kdf}; +use bitwarden_crypto::{EncString, Kdf, MasterKey, SymmetricCryptoKey}; use bitwarden_error::bitwarden_error; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] @@ -21,9 +21,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. @@ -44,6 +50,27 @@ pub struct MasterPasswordUnlockData { pub salt: String, } +impl MasterPasswordUnlockData { + pub(crate) fn derive( + password: &str, + kdf: &Kdf, + salt: &str, + user_key: &SymmetricCryptoKey, + ) -> Result { + 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)?; + + Ok(Self { + kdf: kdf.clone(), + salt: salt.to_owned(), + master_key_wrapped_user_key, + }) + } +} + impl TryFrom for MasterPasswordUnlockData { type Error = MasterPasswordError; @@ -90,6 +117,43 @@ fn kdf_parse_nonzero_u32(value: impl TryInto) -> Result Result { + 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(), + salt: salt.to_owned(), + master_password_authentication_hash: hash, + }) + } +} + #[cfg(test)] mod tests { use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel}; @@ -99,6 +163,45 @@ mod tests { const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; 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 + ); + } fn create_pbkdf2_response( master_key_encrypted_user_key: Option, 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/bitwarden-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml index fd37d52a5..8b1ba5a23 100644 --- a/crates/bitwarden-exporters/Cargo.toml +++ b/crates/bitwarden-exporters/Cargo.toml @@ -20,6 +20,7 @@ uniffi = ["dep:uniffi", "bitwarden-core/uniffi"] # Uniffi bindings wasm = [ "bitwarden-collections/wasm", "bitwarden-vault/wasm", + "bitwarden-collections/wasm", "dep:tsify", "dep:wasm-bindgen" ] # WebAssembly bindings diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 56ddb7a73..62a45665d 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}; @@ -42,12 +42,22 @@ 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) + } + + /// 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 { Ok(self .0 - .update_password(new_password) + .make_update_password(new_password) .map_err(Error::MobileCrypto)?) } @@ -81,4 +91,18 @@ impl CryptoClient { .derive_key_connector(request) .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. + pub fn make_update_kdf( + &self, + password: String, + kdf: Kdf, + ) -> Result { + Ok(self + .0 + .make_update_kdf(password, kdf) + .map_err(Error::MobileCrypto)?) + } } diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs index 67f38b0e8..96070b216 100644 --- a/crates/memory-testing/src/main.rs +++ b/crates/memory-testing/src/main.rs @@ -40,12 +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, - ) - .unwrap(); + let hash = key.derive_master_key_hash( + password.as_bytes(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ); master_keys.push((key, hash)); }