diff --git a/Cargo.lock b/Cargo.lock index bda3a9c4a..9694fb910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,6 +523,7 @@ name = "bitwarden-wasm" version = "0.1.0" dependencies = [ "argon2", + "bitwarden-crypto", "bitwarden-json", "console_error_panic_hook", "console_log", @@ -583,6 +584,7 @@ version = "0.0.2" dependencies = [ "bitwarden", "bitwarden-cli", + "bitwarden-crypto", "clap", "color-eyre", "env_logger", diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index b3a20fbc4..5a24125af 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -5,7 +5,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use super::utils::{derive_kdf_key, stretch_kdf_key}; -use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; +use crate::{ + util, CryptoError, EncString, KeyDecryptable, Result, SensitiveVec, SymmetricCryptoKey, UserKey, +}; /// Key Derivation Function for Bitwarden Account /// @@ -69,13 +71,17 @@ impl MasterKey { } /// Derives a users master key from their password, email and KDF. - pub fn derive(password: &[u8], email: &[u8], kdf: &Kdf) -> Result { - derive_kdf_key(password, email, kdf).map(Self) + pub fn derive(password: &SensitiveVec, email: &[u8], kdf: &Kdf) -> Result { + derive_kdf_key(password.expose(), email, kdf).map(Self) } /// 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.0.key, password, purpose as u32); + pub fn derive_master_key_hash( + &self, + password: &SensitiveVec, + purpose: HashPurpose, + ) -> Result { + let hash = util::pbkdf2(&self.0.key, password.expose(), purpose as u32); Ok(STANDARD.encode(hash)) } @@ -124,13 +130,15 @@ mod tests { use rand::SeedableRng; use super::{make_user_key, HashPurpose, Kdf, MasterKey}; - use crate::{keys::symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}; + use crate::{ + keys::symmetric_crypto_key::derive_symmetric_key, SensitiveVec, SymmetricCryptoKey, + }; #[test] fn test_master_key_derive_pbkdf2() { let master_key = MasterKey::derive( - &b"67t9b5g67$%Dh89n"[..], - "test_key".as_bytes(), + &SensitiveVec::test(b"67t9b5g67$%Dh89n"), + b"test_key", &Kdf::PBKDF2 { iterations: NonZeroU32::new(10000).unwrap(), }, @@ -150,8 +158,8 @@ mod tests { #[test] fn test_master_key_derive_argon2() { let master_key = MasterKey::derive( - &b"67t9b5g67$%Dh89n"[..], - "test_key".as_bytes(), + &SensitiveVec::test(b"67t9b5g67$%Dh89n"), + b"test_key", &Kdf::Argon2id { iterations: NonZeroU32::new(4).unwrap(), memory: NonZeroU32::new(32).unwrap(), @@ -172,38 +180,38 @@ mod tests { #[test] fn test_password_hash_pbkdf2() { - let password = "asdfasdf".as_bytes(); - let salt = "test_salt".as_bytes(); + let password = SensitiveVec::test(b"asdfasdf"); + let salt = b"test_salt"; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(100_000).unwrap(), }; - let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + let master_key = MasterKey::derive(&password, salt, &kdf).unwrap(); assert_eq!( "ZF6HjxUTSyBHsC+HXSOhZoXN+UuMnygV5YkWXCY4VmM=", master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) + .derive_master_key_hash(&password, HashPurpose::ServerAuthorization) .unwrap(), ); } #[test] fn test_password_hash_argon2id() { - let password = "asdfasdf".as_bytes(); - let salt = "test_salt".as_bytes(); + let password = SensitiveVec::test(b"asdfasdf"); + let salt = b"test_salt"; let kdf = Kdf::Argon2id { iterations: NonZeroU32::new(4).unwrap(), memory: NonZeroU32::new(32).unwrap(), parallelism: NonZeroU32::new(2).unwrap(), }; - let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + let master_key = MasterKey::derive(&password, salt, &kdf).unwrap(); assert_eq!( "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=", master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) + .derive_master_key_hash(&password, HashPurpose::ServerAuthorization) .unwrap(), ); } diff --git a/crates/bitwarden-crypto/src/sensitive/sensitive.rs b/crates/bitwarden-crypto/src/sensitive/sensitive.rs index 481058064..33e6fddc1 100644 --- a/crates/bitwarden-crypto/src/sensitive/sensitive.rs +++ b/crates/bitwarden-crypto/src/sensitive/sensitive.rs @@ -74,6 +74,13 @@ impl TryFrom for SensitiveString { } } +impl From for SensitiveVec { + fn from(mut s: SensitiveString) -> Self { + let value = std::mem::take(&mut s.value); + Sensitive::new(Box::new(value.into_bytes())) + } +} + impl SensitiveString { pub fn decode_base64(self, engine: T) -> Result { // Prevent accidental copies by allocating the full size @@ -146,15 +153,18 @@ impl JsonSchema for Sensitive { } } -impl Sensitive { - // We use a lot of `&str` in our tests, so we expose this helper - // to make it easier. - // IMPORTANT: This should not be used outside of test code - // Note that we can't just mark it with #[cfg(test)] because that only applies - // when testing this crate, not when testing other crates that depend on it. - // By at least limiting it to &'static str we should be able to avoid accidental usages - pub fn test(value: &'static str) -> Self { - Self::new(Box::new(value.to_string())) +// We use a lot of `&str` and `&[u8]` in our tests, so we expose this helper +// to make it easier. +// IMPORTANT: This should not be used outside of test code +// Note that we can't just mark it with #[cfg(test)] because that only applies +// when testing this crate, not when testing other crates that depend on it. +// By at least limiting it to &'static reference we should be able to avoid accidental usages +impl Sensitive { + pub fn test(value: &'static T) -> Self + where + &'static T: Into, + { + Self::new(Box::new(value.into())) } } @@ -166,7 +176,7 @@ mod tests { #[test] fn test_debug() { - let string = Sensitive::test("test"); + let string = SensitiveString::test("test"); assert_eq!( format!("{:?}", string), "Sensitive { type: \"alloc::string::String\", value: \"********\" }" diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs index ef9414f12..feb864b72 100644 --- a/crates/bitwarden-json/src/client.rs +++ b/crates/bitwarden-json/src/client.rs @@ -49,15 +49,15 @@ impl Client { match cmd { #[cfg(feature = "internal")] - Command::PasswordLogin(req) => client.auth().login_password(&req).await.into_string(), + Command::PasswordLogin(req) => client.auth().login_password(req).await.into_string(), #[cfg(feature = "secrets")] Command::AccessTokenLogin(req) => { client.auth().login_access_token(&req).await.into_string() } #[cfg(feature = "internal")] - Command::GetUserApiKey(req) => client.get_user_api_key(&req).await.into_string(), + Command::GetUserApiKey(req) => client.get_user_api_key(req).await.into_string(), #[cfg(feature = "internal")] - Command::ApiKeyLogin(req) => client.auth().login_api_key(&req).await.into_string(), + Command::ApiKeyLogin(req) => client.auth().login_api_key(req).await.into_string(), #[cfg(feature = "internal")] Command::Sync(req) => client.sync(&req).await.into_string(), #[cfg(feature = "internal")] diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index c6aed44eb..5c54a6cdb 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -4,7 +4,9 @@ use bitwarden::auth::{ password::MasterPasswordPolicyOptions, AuthRequestResponse, RegisterKeyResponse, RegisterTdeKeyResponse, }; -use bitwarden_crypto::{AsymmetricEncString, HashPurpose, Kdf, TrustDeviceResponse}; +use bitwarden_crypto::{ + AsymmetricEncString, HashPurpose, Kdf, SensitiveString, TrustDeviceResponse, +}; use crate::{error::Result, Client}; @@ -49,7 +51,7 @@ impl ClientAuth { pub async fn hash_password( &self, email: String, - password: String, + password: SensitiveString, kdf_params: Kdf, purpose: HashPurpose, ) -> Result { @@ -67,7 +69,7 @@ impl ClientAuth { pub async fn make_register_keys( &self, email: String, - password: String, + password: SensitiveString, kdf: Kdf, ) -> Result { Ok(self @@ -98,7 +100,11 @@ impl ClientAuth { /// To retrieve the user's password hash, use [`ClientAuth::hash_password`] with /// `HashPurpose::LocalAuthentication` during login and persist it. If the login method has no /// password, use the email OTP. - pub async fn validate_password(&self, password: String, password_hash: String) -> Result { + pub async fn validate_password( + &self, + password: SensitiveString, + password_hash: String, + ) -> Result { Ok(self .0 .0 @@ -116,7 +122,7 @@ impl ClientAuth { /// This works by comparing the provided password against the encrypted user key. pub async fn validate_password_user_key( &self, - password: String, + password: SensitiveString, encrypted_user_key: String, ) -> Result { Ok(self diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index f3abe694a..5907e9a8a 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -53,7 +53,10 @@ impl ClientCrypto { /// 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 async fn update_password(&self, new_password: String) -> Result { + pub async fn update_password( + &self, + new_password: SensitiveString, + ) -> Result { Ok(self .0 .0 @@ -67,7 +70,7 @@ impl ClientCrypto { /// Generates a PIN protected user key from the provided PIN. The result can be stored and later /// used to initialize another client instance by using the PIN and the PIN key with /// `initialize_user_crypto`. - pub async fn derive_pin_key(&self, pin: String) -> Result { + pub async fn derive_pin_key(&self, pin: SensitiveString) -> Result { Ok(self.0 .0.write().await.crypto().derive_pin_key(pin).await?) } diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index a4ba8b6ae..6103e55a6 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -19,6 +19,7 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", "zeroize", ], default-features = false } +bitwarden-crypto = { workspace = true } bitwarden-json = { path = "../bitwarden-json", features = [ "secrets", "internal", diff --git a/crates/bitwarden-wasm/src/client.rs b/crates/bitwarden-wasm/src/client.rs index bca8c2383..240e10c47 100644 --- a/crates/bitwarden-wasm/src/client.rs +++ b/crates/bitwarden-wasm/src/client.rs @@ -2,6 +2,7 @@ extern crate console_error_panic_hook; use std::rc::Rc; use argon2::{Algorithm, Argon2, Params, Version}; +use bitwarden_crypto::SensitiveVec; use bitwarden_json::client::Client as JsonClient; use js_sys::Promise; use log::Level; @@ -58,12 +59,15 @@ impl BitwardenClient { #[wasm_bindgen] pub fn argon2( - password: &[u8], - salt: &[u8], + password: Vec, + salt: Vec, iterations: u32, memory: u32, parallelism: u32, ) -> Result, JsError> { + let password = SensitiveVec::new(Box::new(password)); + let salt = SensitiveVec::new(Box::new(salt)); + let argon = Argon2::new( Algorithm::Argon2id, Version::V0x13, @@ -76,6 +80,6 @@ pub fn argon2( ); let mut hash = [0u8; 32]; - argon.hash_password_into(password, salt, &mut hash)?; + argon.hash_password_into(password.expose(), salt.expose(), &mut hash)?; Ok(hash.to_vec()) } diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs index 68e2cad90..8c3523d20 100644 --- a/crates/bitwarden/src/auth/auth_request.rs +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -122,7 +122,7 @@ fn test_auth_request() { mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{Kdf, SensitiveVec}; use super::*; use crate::mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}; @@ -132,7 +132,7 @@ mod tests { let mut client = Client::new(None); let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), + &SensitiveVec::test(b"asdfasdfasdf"), "test@bitwarden.com".as_bytes(), &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), @@ -206,9 +206,12 @@ mod tests { // Initialize an existing client which is unlocked let mut existing_device = Client::new(None); - let master_key = - bitwarden_crypto::MasterKey::derive("asdfasdfasdf".as_bytes(), email.as_bytes(), &kdf) - .unwrap(); + let master_key = bitwarden_crypto::MasterKey::derive( + &SensitiveVec::test(b"asdfasdfasdf"), + email.as_bytes(), + &kdf, + ) + .unwrap(); existing_device .initialize_user_crypto_master_key(master_key, user_key, private_key.parse().unwrap()) diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index b3afe133a..487877b33 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -1,5 +1,5 @@ #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, DeviceKey, TrustDeviceResponse}; +use bitwarden_crypto::{AsymmetricEncString, DeviceKey, SensitiveString, TrustDeviceResponse}; #[cfg(feature = "mobile")] use crate::auth::login::NewAuthRequestResponse; @@ -68,7 +68,7 @@ impl<'a> ClientAuth<'a> { pub fn make_register_keys( &self, email: String, - password: String, + password: SensitiveString, kdf: Kdf, ) -> Result { make_register_keys(email, password, kdf) @@ -83,7 +83,7 @@ impl<'a> ClientAuth<'a> { make_register_tde_keys(self.client, email, org_public_key, remember_device) } - pub async fn register(&mut self, input: &RegisterRequest) -> Result<()> { + pub async fn register(&mut self, input: RegisterRequest) -> Result<()> { register(self.client, input).await } @@ -96,29 +96,33 @@ impl<'a> ClientAuth<'a> { pub async fn login_password( &mut self, - input: &PasswordLoginRequest, + input: PasswordLoginRequest, ) -> Result { login_password(self.client, input).await } pub async fn login_api_key( &mut self, - input: &ApiKeyLoginRequest, + input: ApiKeyLoginRequest, ) -> Result { login_api_key(self.client, input).await } - pub async fn send_two_factor_email(&mut self, tf: &TwoFactorEmailRequest) -> Result<()> { + pub async fn send_two_factor_email(&mut self, tf: TwoFactorEmailRequest) -> Result<()> { send_two_factor_email(self.client, tf).await } - pub fn validate_password(&self, password: String, password_hash: String) -> Result { + pub fn validate_password( + &self, + password: SensitiveString, + password_hash: String, + ) -> Result { validate_password(self.client, password, password_hash) } pub fn validate_password_user_key( &self, - password: String, + password: SensitiveString, encrypted_user_key: String, ) -> Result { validate_password_user_key(self.client, password, encrypted_user_key) diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index 8b83e3a38..807457a53 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden/src/auth/login/api_key.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{EncString, MasterKey}; +use bitwarden_crypto::{EncString, MasterKey, SensitiveString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -15,12 +15,12 @@ use crate::{ pub(crate) async fn login_api_key( client: &mut Client, - input: &ApiKeyLoginRequest, + input: ApiKeyLoginRequest, ) -> Result { //info!("api key logging in"); //debug!("{:#?}, {:#?}", client, input); - let response = request_api_identity_tokens(client, input).await?; + let response = request_api_identity_tokens(client, &input).await?; if let IdentityTokenResponse::Authenticated(r) = &response { let access_token_obj: JWTToken = r.access_token.parse()?; @@ -38,7 +38,7 @@ pub(crate) async fn login_api_key( r.expires_in, ); - let master_key = MasterKey::derive(input.password.as_bytes(), email.as_bytes(), &kdf)?; + let master_key = MasterKey::derive(&input.password.into(), email.as_bytes(), &kdf)?; client.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey { client_id: input.client_id.to_owned(), @@ -76,7 +76,7 @@ pub struct ApiKeyLoginRequest { pub client_secret: String, /// Bitwarden account master password - pub password: String, + pub password: SensitiveString, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index e0cb67dbe..cae51d6eb 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -1,4 +1,6 @@ #[cfg(feature = "internal")] +use bitwarden_crypto::SensitiveString; +#[cfg(feature = "internal")] use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -20,7 +22,7 @@ use crate::{ #[cfg(feature = "internal")] pub(crate) async fn login_password( client: &mut Client, - input: &PasswordLoginRequest, + input: PasswordLoginRequest, ) -> Result { use bitwarden_crypto::{EncString, HashPurpose, MasterKey}; @@ -28,15 +30,14 @@ pub(crate) async fn login_password( info!("password logging in"); - let master_key = MasterKey::derive( - input.password.as_bytes(), - input.email.as_bytes(), - &input.kdf, - )?; - let password_hash = master_key - .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?; + let password_vec = input.password.into(); + + let master_key = MasterKey::derive(&password_vec, input.email.as_bytes(), &input.kdf)?; + let password_hash = + master_key.derive_master_key_hash(&password_vec, HashPurpose::ServerAuthorization)?; - let response = request_identity_tokens(client, input, &password_hash).await?; + let response = + request_identity_tokens(client, &input.email, &input.two_factor, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { client.set_tokens( @@ -62,18 +63,19 @@ pub(crate) async fn login_password( #[cfg(feature = "internal")] async fn request_identity_tokens( client: &mut Client, - input: &PasswordLoginRequest, + email: &str, + two_factor: &Option, password_hash: &str, ) -> Result { use crate::client::client_settings::DeviceType; let config = client.get_api_configurations().await; PasswordTokenRequest::new( - &input.email, + email, password_hash, DeviceType::ChromeBrowser, "b86dd6ab-4265-4ddf-a7f1-eb28d5677f33", - &input.two_factor, + two_factor, ) .send(config) .await @@ -87,7 +89,7 @@ pub struct PasswordLoginRequest { /// Bitwarden account email address pub email: String, /// Bitwarden account master password - pub password: String, + pub password: SensitiveString, // Two-factor authentication pub two_factor: Option, /// Kdf from prelogin diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden/src/auth/login/two_factor.rs index c8f0cc55b..9b432392f 100644 --- a/crates/bitwarden/src/auth/login/two_factor.rs +++ b/crates/bitwarden/src/auth/login/two_factor.rs @@ -1,5 +1,5 @@ use bitwarden_api_api::models::TwoFactorEmailRequestModel; -use bitwarden_crypto::HashPurpose; +use bitwarden_crypto::{HashPurpose, SensitiveString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -9,15 +9,15 @@ use crate::{auth::determine_password_hash, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct TwoFactorEmailRequest { - /// User Password - pub password: String, /// User email pub email: String, + /// User Password + pub password: SensitiveString, } pub(crate) async fn send_two_factor_email( client: &mut Client, - input: &TwoFactorEmailRequest, + input: TwoFactorEmailRequest, ) -> Result<()> { // TODO: This should be resolved from the client let kdf = client.auth().prelogin(input.email.clone()).await?; @@ -25,7 +25,7 @@ pub(crate) async fn send_two_factor_email( let password_hash = determine_password_hash( &input.email, &kdf, - &input.password, + &input.password.into(), HashPurpose::ServerAuthorization, )?; diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index b1fe2dbda..1c6395299 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -11,7 +11,7 @@ pub use jwt_token::JWTToken; #[cfg(feature = "internal")] mod register; #[cfg(feature = "internal")] -use bitwarden_crypto::{HashPurpose, MasterKey}; +use bitwarden_crypto::{HashPurpose, MasterKey, SensitiveVec}; #[cfg(feature = "internal")] pub use register::{RegisterKeyResponse, RegisterRequest}; #[cfg(feature = "internal")] @@ -32,11 +32,11 @@ use crate::{client::Kdf, error::Result}; fn determine_password_hash( email: &str, kdf: &Kdf, - password: &str, + password: &SensitiveVec, purpose: HashPurpose, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; - Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) + let master_key = MasterKey::derive(password, email.as_bytes(), kdf)?; + Ok(master_key.derive_master_key_hash(password, purpose)?) } #[cfg(test)] @@ -50,14 +50,14 @@ mod tests { fn test_determine_password_hash() { use super::determine_password_hash; - let password = "password123"; + let password = SensitiveVec::test(b"password123"); let email = "test@bitwarden.com"; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(100_000).unwrap(), }; let purpose = HashPurpose::LocalAuthorization; - let result = determine_password_hash(email, &kdf, password, purpose).unwrap(); + let result = determine_password_hash(email, &kdf, &password, purpose).unwrap(); assert_eq!(result, "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU="); } diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs index 7e30d858b..8758251d3 100644 --- a/crates/bitwarden/src/auth/password/validate.rs +++ b/crates/bitwarden/src/auth/password/validate.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{HashPurpose, MasterKey}; +use bitwarden_crypto::{HashPurpose, MasterKey, SensitiveString}; use crate::{ auth::determine_password_hash, @@ -10,7 +10,7 @@ use crate::{ /// Validate if the provided password matches the password hash stored in the client. pub(crate) fn validate_password( client: &Client, - password: String, + password: SensitiveString, password_hash: String, ) -> Result { let login_method = client @@ -25,7 +25,7 @@ pub(crate) fn validate_password( let hash = determine_password_hash( email, kdf, - &password, + &password.into(), HashPurpose::LocalAuthorization, )?; @@ -40,7 +40,7 @@ pub(crate) fn validate_password( #[cfg(feature = "internal")] pub(crate) fn validate_password_user_key( client: &Client, - password: String, + password: bitwarden_crypto::SensitiveString, encrypted_user_key: String, ) -> Result { let login_method = client @@ -52,7 +52,8 @@ pub(crate) fn validate_password_user_key( match login_method { UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. } => { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; + let password_vec = password.into(); + let master_key = MasterKey::derive(&password_vec, email.as_bytes(), kdf)?; let user_key = master_key .decrypt_user_key(encrypted_user_key.parse()?) .map_err(|_| "wrong password")?; @@ -68,7 +69,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_vec, HashPurpose::LocalAuthorization)?) } } } else { @@ -78,6 +79,8 @@ pub(crate) fn validate_password_user_key( #[cfg(test)] mod tests { + use bitwarden_crypto::SensitiveString; + use crate::auth::password::{validate::validate_password_user_key, validate_password}; #[test] @@ -95,7 +98,7 @@ mod tests { client_id: "1".to_string(), })); - let password = "password123".to_string(); + let password = SensitiveString::test("password123"); let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string(); let result = validate_password(&client, password, password_hash); @@ -108,11 +111,13 @@ mod tests { fn test_validate_password_user_key() { use std::num::NonZeroU32; + use bitwarden_crypto::SensitiveVec; + use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; let mut client = Client::new(None); - let password = "asdfasdfasdf"; + let password = SensitiveVec::test(b"asdfasdfasdf"); let email = "test@bitwarden.com"; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), @@ -125,8 +130,7 @@ mod tests { })); let master_key = - bitwarden_crypto::MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf) - .unwrap(); + bitwarden_crypto::MasterKey::derive(&password, email.as_bytes(), &kdf).unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(); @@ -135,12 +139,17 @@ mod tests { .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) .unwrap(); - let result = - validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string()) - .unwrap(); + let result = validate_password_user_key( + &client, + SensitiveString::test("asdfasdfasdf"), + user_key.to_string(), + ) + .unwrap(); assert_eq!(result, "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="); - assert!(validate_password(&client, "asdfasdfasdf".to_string(), result.to_string()).unwrap()) + assert!( + validate_password(&client, password.try_into().unwrap(), result.to_string()).unwrap() + ) } #[cfg(feature = "internal")] @@ -148,11 +157,13 @@ mod tests { fn test_validate_password_user_key_wrong_password() { use std::num::NonZeroU32; + use bitwarden_crypto::SensitiveVec; + use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; let mut client = Client::new(None); - let password = "asdfasdfasdf"; + let password = SensitiveVec::test(b"asdfasdfasdf"); let email = "test@bitwarden.com"; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), @@ -165,8 +176,7 @@ mod tests { })); let master_key = - bitwarden_crypto::MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf) - .unwrap(); + bitwarden_crypto::MasterKey::derive(&password, email.as_bytes(), &kdf).unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(); @@ -175,8 +185,9 @@ mod tests { .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) .unwrap(); - let result = validate_password_user_key(&client, "abc".to_string(), user_key.to_string()) - .unwrap_err(); + let result = + validate_password_user_key(&client, SensitiveString::test("abc"), user_key.to_string()) + .unwrap_err(); assert_eq!(result.to_string(), "Internal error: wrong password"); } diff --git a/crates/bitwarden/src/auth/register.rs b/crates/bitwarden/src/auth/register.rs index 5cedd9c53..09e60ac98 100644 --- a/crates/bitwarden/src/auth/register.rs +++ b/crates/bitwarden/src/auth/register.rs @@ -2,7 +2,9 @@ use bitwarden_api_identity::{ apis::accounts_api::accounts_register_post, models::{KeysRequestModel, RegisterRequestModel}, }; -use bitwarden_crypto::{default_pbkdf2_iterations, HashPurpose, MasterKey, RsaKeyPair}; +use bitwarden_crypto::{ + default_pbkdf2_iterations, HashPurpose, MasterKey, RsaKeyPair, SensitiveString, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -13,27 +15,27 @@ use crate::{client::Kdf, error::Result, Client}; pub struct RegisterRequest { pub email: String, pub name: Option, - pub password: String, + pub password: SensitiveString, pub password_hint: Option, } /// Half baked implementation of user registration -pub(super) async fn register(client: &mut Client, req: &RegisterRequest) -> Result<()> { +pub(super) async fn register(client: &mut Client, req: RegisterRequest) -> Result<()> { let config = client.get_api_configurations().await; let kdf = Kdf::default(); - let keys = make_register_keys(req.email.to_owned(), req.password.to_owned(), kdf)?; + let keys = make_register_keys(req.email.clone(), req.password, kdf)?; accounts_register_post( &config.identity, Some(RegisterRequestModel { - name: req.name.to_owned(), - email: req.email.to_owned(), + name: req.name, + email: req.email, master_password_hash: keys.master_password_hash, - master_password_hint: req.password_hint.to_owned(), + master_password_hint: req.password_hint, captcha_response: None, // TODO: Add - key: Some(keys.encrypted_user_key.to_string()), + key: Some(keys.encrypted_user_key), keys: Some(Box::new(KeysRequestModel { public_key: Some(keys.keys.public), encrypted_private_key: keys.keys.private.to_string(), @@ -54,12 +56,13 @@ pub(super) async fn register(client: &mut Client, req: &RegisterRequest) -> Resu pub(super) fn make_register_keys( email: String, - password: String, + password: SensitiveString, kdf: Kdf, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf)?; + let password_vec = password.into(); + let master_key = MasterKey::derive(&password_vec, email.as_bytes(), &kdf)?; let master_password_hash = - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)?; + master_key.derive_master_key_hash(&password_vec, 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/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 0251723d0..8e17d0c6d 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -197,7 +197,7 @@ impl Client { #[cfg(feature = "internal")] pub async fn get_user_api_key( &mut self, - input: &SecretVerificationRequest, + input: SecretVerificationRequest, ) -> Result { get_user_api_key(self, input).await } diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 26451ffaa..6259e084d 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -35,13 +35,13 @@ impl<'a> ClientCrypto<'a> { #[cfg(feature = "internal")] pub async fn update_password( &mut self, - new_password: String, + new_password: SensitiveString, ) -> Result { update_password(self.client, new_password) } #[cfg(feature = "internal")] - pub async fn derive_pin_key(&mut self, pin: String) -> Result { + pub async fn derive_pin_key(&mut self, pin: SensitiveString) -> Result { derive_pin_key(self.client, pin) } diff --git a/crates/bitwarden/src/mobile/client_kdf.rs b/crates/bitwarden/src/mobile/client_kdf.rs index 4e62e5d59..8597f6f8e 100644 --- a/crates/bitwarden/src/mobile/client_kdf.rs +++ b/crates/bitwarden/src/mobile/client_kdf.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::HashPurpose; +use bitwarden_crypto::{HashPurpose, SensitiveString}; use crate::{client::Kdf, error::Result, mobile::kdf::hash_password, Client}; @@ -10,7 +10,7 @@ impl<'a> ClientKdf<'a> { pub async fn hash_password( &self, email: String, - password: String, + password: SensitiveString, kdf_params: Kdf, purpose: HashPurpose, ) -> Result { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 35c38c910..4027302df 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -38,7 +38,7 @@ pub struct InitUserCryptoRequest { pub enum InitUserCryptoMethod { Password { /// The user's master password - password: String, + password: SensitiveString, /// The user's encrypted symmetric crypto key user_key: String, }, @@ -48,7 +48,7 @@ pub enum InitUserCryptoMethod { }, Pin { /// The user's PIN - pin: String, + pin: SensitiveString, /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain /// this. pin_protected_user_key: EncString, @@ -99,7 +99,7 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ let user_key: EncString = user_key.parse()?; let master_key = - MasterKey::derive(password.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; + MasterKey::derive(&password.into(), req.email.as_bytes(), &req.kdf_params)?; client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { @@ -111,7 +111,7 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ pin, pin_protected_user_key, } => { - let pin_key = MasterKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; + let pin_key = MasterKey::derive(&pin.into(), req.email.as_bytes(), &req.kdf_params)?; client.initialize_user_crypto_pin(pin_key, pin_protected_user_key, private_key)?; } InitUserCryptoMethod::AuthRequest { @@ -197,7 +197,7 @@ pub struct UpdatePasswordResponse { pub fn update_password( client: &mut Client, - new_password: String, + new_password: SensitiveString, ) -> Result { let user_key = client .get_encryption_settings()? @@ -209,19 +209,21 @@ pub fn update_password( .as_ref() .ok_or(Error::NotAuthenticated)?; + let password_vec = new_password.into(); + // Derive a new master key from password let new_master_key = match login_method { LoginMethod::User( UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. }, - ) => MasterKey::derive(new_password.as_bytes(), email.as_bytes(), kdf)?, + ) => MasterKey::derive(&password_vec, email.as_bytes(), kdf)?, _ => return Err(Error::NotAuthenticated), }; let new_key = new_master_key.encrypt_user_key(user_key)?; let password_hash = new_master_key.derive_master_key_hash( - new_password.as_bytes(), + &password_vec, bitwarden_crypto::HashPurpose::ServerAuthorization, )?; @@ -243,7 +245,7 @@ pub struct DerivePinKeyResponse { } #[cfg(feature = "internal")] -pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { +pub fn derive_pin_key(client: &mut Client, pin: SensitiveString) -> Result { let user_key = client .get_encryption_settings()? .get_key(&None) @@ -254,7 +256,8 @@ pub fn derive_pin_key(client: &mut Client, pin: String) -> Result Result Result { + use bitwarden_crypto::Sensitive; + let user_key = client .get_encryption_settings()? .get_key(&None) .ok_or(Error::VaultLocked)?; let pin: String = encrypted_pin.decrypt_with_key(user_key)?; + let pin = Sensitive::new(Box::new(pin)); + let login_method = client .login_method .as_ref() .ok_or(Error::NotAuthenticated)?; - derive_pin_protected_user_key(&pin, login_method, user_key) + derive_pin_protected_user_key(pin, login_method, user_key) } #[cfg(feature = "internal")] fn derive_pin_protected_user_key( - pin: &str, + pin: SensitiveString, login_method: &LoginMethod, user_key: &SymmetricCryptoKey, ) -> Result { @@ -288,7 +295,7 @@ fn derive_pin_protected_user_key( LoginMethod::User( UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. }, - ) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + ) => MasterKey::derive(&pin.into(), email.as_bytes(), kdf)?, _ => return Err(Error::NotAuthenticated), }; @@ -335,7 +342,7 @@ mod tests { email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), method: InitUserCryptoMethod::Password { - password: "asdfasdfasdf".into(), + password: SensitiveString::test("asdfasdfasdf"), user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), }, }, @@ -343,7 +350,8 @@ mod tests { .await .unwrap(); - let new_password_response = update_password(&mut client, "123412341234".into()).unwrap(); + let new_password_response = + update_password(&mut client, SensitiveString::test("123412341234")).unwrap(); let mut client2 = Client::new(None); @@ -354,7 +362,7 @@ mod tests { email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), method: InitUserCryptoMethod::Password { - password: "123412341234".into(), + password: SensitiveString::test("123412341234"), user_key: new_password_response.new_key.to_string(), }, }, @@ -366,7 +374,7 @@ mod tests { .kdf() .hash_password( "test@bitwarden.com".into(), - "123412341234".into(), + SensitiveString::test("123412341234"), kdf.clone(), bitwarden_crypto::HashPurpose::ServerAuthorization, ) @@ -406,7 +414,7 @@ mod tests { email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), method: InitUserCryptoMethod::Password { - password: "asdfasdfasdf".into(), + password: SensitiveString::test("asdfasdfasdf"), user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), }, }, @@ -414,7 +422,7 @@ mod tests { .await .unwrap(); - let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + let pin_key = derive_pin_key(&mut client, SensitiveString::test("1234")).unwrap(); // Verify we can unlock with the pin let mut client2 = Client::new(None); @@ -427,7 +435,7 @@ mod tests { email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), method: InitUserCryptoMethod::Pin { - pin: "1234".into(), + pin: SensitiveString::test("1234"), pin_protected_user_key: pin_key.pin_protected_user_key, }, }, @@ -465,7 +473,7 @@ mod tests { email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), method: InitUserCryptoMethod::Pin { - pin: "1234".into(), + pin: SensitiveString::test("1234"), pin_protected_user_key, }, }, @@ -495,12 +503,12 @@ mod tests { use std::num::NonZeroU32; use base64::{engine::general_purpose::STANDARD, Engine}; - use bitwarden_crypto::AsymmetricCryptoKey; + use bitwarden_crypto::{AsymmetricCryptoKey, SensitiveVec}; let mut client = Client::new(None); let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), + &SensitiveVec::test(b"asdfasdfasdf"), "test@bitwarden.com".as_bytes(), &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), diff --git a/crates/bitwarden/src/mobile/kdf.rs b/crates/bitwarden/src/mobile/kdf.rs index 1c1972086..6b7f3245e 100644 --- a/crates/bitwarden/src/mobile/kdf.rs +++ b/crates/bitwarden/src/mobile/kdf.rs @@ -1,15 +1,16 @@ -use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; +use bitwarden_crypto::{HashPurpose, Kdf, MasterKey, SensitiveString}; use crate::{error::Result, Client}; pub async fn hash_password( _client: &Client, email: String, - password: String, + password: SensitiveString, kdf_params: Kdf, purpose: HashPurpose, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf_params)?; + let password_vec = password.into(); + let master_key = MasterKey::derive(&password_vec, email.as_bytes(), &kdf_params)?; - Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) + Ok(master_key.derive_master_key_hash(&password_vec, purpose)?) } diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index b61454f06..f6ffe0f36 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -54,6 +54,8 @@ pub(crate) fn generate_user_fingerprint( mod tests { use std::num::NonZeroU32; + use bitwarden_crypto::SensitiveVec; + use super::*; use crate::{client::Kdf, Client}; @@ -66,7 +68,7 @@ mod tests { let mut client = Client::new(None); let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), + &SensitiveVec::test(b"asdfasdfasdf"), "robb@stark.com".as_bytes(), &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), diff --git a/crates/bitwarden/src/platform/get_user_api_key.rs b/crates/bitwarden/src/platform/get_user_api_key.rs index 3e408d926..0bb99dd17 100644 --- a/crates/bitwarden/src/platform/get_user_api_key.rs +++ b/crates/bitwarden/src/platform/get_user_api_key.rs @@ -16,7 +16,7 @@ use crate::{ pub(crate) async fn get_user_api_key( client: &mut Client, - input: &SecretVerificationRequest, + input: SecretVerificationRequest, ) -> Result { info!("Getting Api Key"); debug!("{:?}", input); @@ -43,16 +43,15 @@ fn get_login_method(client: &Client) -> Result<&LoginMethod> { fn get_secret_verification_request( login_method: &LoginMethod, - input: &SecretVerificationRequest, + input: SecretVerificationRequest, ) -> Result { if let LoginMethod::User(UserLoginMethod::Username { email, kdf, .. }) = login_method { let master_password_hash = input .master_password - .as_ref() .map(|p| { - let master_key = MasterKey::derive(p.as_bytes(), email.as_bytes(), kdf)?; - - master_key.derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization) + let password_vec = p.into(); + let master_key = MasterKey::derive(&password_vec, email.as_bytes(), kdf)?; + master_key.derive_master_key_hash(&password_vec, HashPurpose::ServerAuthorization) }) .transpose()?; Ok(SecretVerificationRequestModel { diff --git a/crates/bitwarden/src/platform/secret_verification_request.rs b/crates/bitwarden/src/platform/secret_verification_request.rs index e05926620..4fe23e286 100644 --- a/crates/bitwarden/src/platform/secret_verification_request.rs +++ b/crates/bitwarden/src/platform/secret_verification_request.rs @@ -1,3 +1,4 @@ +use bitwarden_crypto::SensitiveString; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -6,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub struct SecretVerificationRequest { /// The user's master password to use for user verification. If supplied, this will be used for /// verification purposes. - pub master_password: Option, + pub master_password: Option, /// Alternate user verification method through OTP. This is provided for users who have no /// master password due to use of Customer Managed Encryption. Must be present and valid if /// master_password is absent. diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 550da84aa..e681e5468 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -363,7 +363,7 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey}; + use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey, SensitiveVec}; use super::{Send, SendText, SendTextView, SendType}; use crate::{ @@ -375,7 +375,7 @@ mod tests { fn test_get_send_key() { // Initialize user encryption with some test data let master_key = MasterKey::derive( - "asdfasdfasdf".as_bytes(), + &SensitiveVec::test(b"asdfasdfasdf"), "test@bitwarden.com".as_bytes(), &Kdf::PBKDF2 { iterations: 345123.try_into().unwrap(), @@ -402,7 +402,7 @@ mod tests { fn build_encryption_settings() -> EncryptionSettings { let master_key = MasterKey::derive( - "asdfasdfasdf".as_bytes(), + &SensitiveVec::test(b"asdfasdfasdf"), "test@bitwarden.com".as_bytes(), &Kdf::PBKDF2 { iterations: 600_000.try_into().unwrap(), diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden/tests/register.rs index 8e523e26f..eb1b30e30 100644 --- a/crates/bitwarden/tests/register.rs +++ b/crates/bitwarden/tests/register.rs @@ -8,19 +8,19 @@ async fn test_register_initialize_crypto() { mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, Client, }; - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{Kdf, SensitiveString}; let mut client = Client::new(None); let email = "test@bitwarden.com"; - let password = "test123"; + let password = SensitiveString::test("test123"); let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }; let register_response = client .auth() - .make_register_keys(email.to_owned(), password.to_owned(), kdf.clone()) + .make_register_keys(email.to_owned(), password.clone(), kdf.clone()) .unwrap(); // Ensure we can initialize the crypto with the new keys @@ -32,7 +32,7 @@ async fn test_register_initialize_crypto() { private_key: register_response.keys.private.to_string(), method: InitUserCryptoMethod::Password { - password: password.to_owned(), + password, user_key: register_response.encrypted_user_key, }, }) diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 3fa5efd4a..65dd6fa30 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -16,6 +16,7 @@ license-file.workspace = true [dependencies] bitwarden = { workspace = true, features = ["internal", "mobile"] } bitwarden-cli = { workspace = true } +bitwarden-crypto = { workspace = true } clap = { version = "4.5.1", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.11.1" diff --git a/crates/bw/src/auth/login.rs b/crates/bw/src/auth/login.rs index 91e740a3a..ccc2024d9 100644 --- a/crates/bw/src/auth/login.rs +++ b/crates/bw/src/auth/login.rs @@ -7,6 +7,7 @@ use bitwarden::{ Client, }; use bitwarden_cli::text_prompt_when_none; +use bitwarden_crypto::SensitiveString; use color_eyre::eyre::{bail, Result}; use inquire::{Password, Text}; use log::{debug, error, info}; @@ -14,13 +15,15 @@ use log::{debug, error, info}; pub(crate) async fn login_password(mut client: Client, email: Option) -> Result<()> { let email = text_prompt_when_none("Email", email)?; - let password = Password::new("Password").without_confirmation().prompt()?; + let password = SensitiveString::new(Box::new( + Password::new("Password").without_confirmation().prompt()?, + )); let kdf = client.auth().prelogin(email.clone()).await?; let result = client .auth() - .login_password(&PasswordLoginRequest { + .login_password(PasswordLoginRequest { email: email.clone(), password: password.clone(), two_factor: None, @@ -48,7 +51,7 @@ pub(crate) async fn login_password(mut client: Client, email: Option) -> // Send token client .auth() - .send_two_factor_email(&TwoFactorEmailRequest { + .send_two_factor_email(TwoFactorEmailRequest { email: email.clone(), password: password.clone(), }) @@ -68,7 +71,7 @@ pub(crate) async fn login_password(mut client: Client, email: Option) -> let result = client .auth() - .login_password(&PasswordLoginRequest { + .login_password(PasswordLoginRequest { email, password, two_factor, @@ -99,11 +102,13 @@ pub(crate) async fn login_api_key( let client_id = text_prompt_when_none("Client ID", client_id)?; let client_secret = text_prompt_when_none("Client Secret", client_secret)?; - let password = Password::new("Password").without_confirmation().prompt()?; + let password = SensitiveString::new(Box::new( + Password::new("Password").without_confirmation().prompt()?, + )); let result = client .auth() - .login_api_key(&ApiKeyLoginRequest { + .login_api_key(ApiKeyLoginRequest { client_id, client_secret, password, diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index 6674bda1e..d973c4074 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -4,6 +4,7 @@ use bitwarden::{ generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, }; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; +use bitwarden_crypto::SensitiveString; use clap::{command, Args, CommandFactory, Parser, Subcommand}; use color_eyre::eyre::Result; use inquire::Password; @@ -191,11 +192,11 @@ async fn process_commands() -> Result<()> { let mut client = bitwarden::Client::new(settings); let email = text_prompt_when_none("Email", email)?; - let password = Password::new("Password").prompt()?; + let password = SensitiveString::new(Box::new(Password::new("Password").prompt()?)); client .auth() - .register(&RegisterRequest { + .register(RegisterRequest { email, name, password,