From dfe067e4ec677e52cb23e49a89133cce6b6bb063 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 19 Apr 2024 15:14:59 +0200 Subject: [PATCH 01/11] Add bacon config (#721) Adds a bacon config which is useful when writing rustdoc. `bacon doc-internal-open` will re-build the doc on changes. --- bacon.toml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 bacon.toml diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 000000000..099e47d75 --- /dev/null +++ b/bacon.toml @@ -0,0 +1,77 @@ +# This is a configuration file for the bacon tool +# +# Bacon repository: https://github.com/Canop/bacon +# Complete help on configuration: https://dystroy.org/bacon/config/ +# You can also check bacon's own bacon.toml file +# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml + +default_job = "check" + +[jobs.check] +command = ["cargo", "check", "--color", "always"] +need_stdout = false + +[jobs.check-all] +command = ["cargo", "check", "--all-targets", "--color", "always"] +need_stdout = false + +[jobs.clippy] +command = ["cargo", "clippy", "--all-targets", "--color", "always"] +need_stdout = false + +[jobs.test] +command = [ + "cargo", + "test", + "--all-features", + "--color", + "always", + "--", + "--color", + "always", # see https://github.com/Canop/bacon/issues/124 +] +need_stdout = true + +[jobs.doc] +command = ["cargo", "doc", "--color", "always", "--no-deps"] +need_stdout = false + +# If the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +[jobs.doc-internal] +command = [ + "cargo", + "doc", + "--color", + "always", + "--no-deps", + "--all-features", + "--document-private-items", +] +need_stdout = false + +[jobs.doc-internal-open] +command = [ + "cargo", + "doc", + "--color", + "always", + "--no-deps", + "--all-features", + "--document-private-items", + "--open", +] +need_stdout = false +on_success = "back" + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-job" From a5f6fd587c1211a4e52ede8a5fbf6f3d1d4b4148 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 22 Apr 2024 11:28:34 +0200 Subject: [PATCH 02/11] Add more docs to crypto crate (#722) Add some more documentation for crypto crate. --- bacon.toml | 3 +- .../bitwarden-crypto/src/keys/master_key.rs | 9 ++++ crates/bitwarden-crypto/src/lib.rs | 42 ++++++++++++++++--- crates/bitwarden-crypto/src/rsa.rs | 2 + crates/bitwarden-crypto/src/wordlist.rs | 2 +- crates/bitwarden-exporters/src/csv.rs | 2 +- crates/bitwarden-generators/src/username.rs | 4 +- crates/bitwarden/src/client/client.rs | 1 + crates/bitwarden/src/mobile/crypto.rs | 4 +- 9 files changed, 57 insertions(+), 12 deletions(-) diff --git a/bacon.toml b/bacon.toml index 099e47d75..6844980d5 100644 --- a/bacon.toml +++ b/bacon.toml @@ -66,8 +66,9 @@ command = [ "--document-private-items", "--open", ] +allow_warnings = true need_stdout = false -on_success = "back" +on_success = "job:doc-internal" # You may define here keybindings that would be specific to # a project, for example a shortcut to launch a specific job. diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 1fd678452..b3a20fbc4 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -7,6 +7,10 @@ use serde::{Deserialize, Serialize}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; +/// Key Derivation Function for Bitwarden Account +/// +/// In Bitwarden accounts can use multiple KDFs to derive their master key from their password. This +/// Enum represents all the possible KDFs. #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] @@ -22,6 +26,7 @@ pub enum Kdf { } impl Default for Kdf { + /// Default KDF for new accounts. fn default() -> Self { Kdf::PBKDF2 { iterations: default_pbkdf2_iterations(), @@ -29,15 +34,19 @@ impl Default for Kdf { } } +/// Default PBKDF2 iterations pub fn default_pbkdf2_iterations() -> NonZeroU32 { NonZeroU32::new(600_000).expect("Non-zero number") } +/// Default Argon2 iterations pub fn default_argon2_iterations() -> NonZeroU32 { NonZeroU32::new(3).expect("Non-zero number") } +/// Default Argon2 memory pub fn default_argon2_memory() -> NonZeroU32 { NonZeroU32::new(64).expect("Non-zero number") } +/// Default Argon2 parallelism pub fn default_argon2_parallelism() -> NonZeroU32 { NonZeroU32::new(4).expect("Non-zero number") } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 8c368580a..babcc921d 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -1,15 +1,47 @@ //! # Bitwarden Cryptographic primitives //! -//! This crate contains the cryptographic primitives used throughout the SDK. The crate makes a -//! best effort to abstract away cryptographic concepts into concepts such as [`EncString`], -//! [`AsymmetricEncString`] and [`SymmetricCryptoKey`]. +//! This crate contains the cryptographic primitives used throughout the SDK. The general +//! aspiration is for this crate to handle all the difficult cryptographic operations and expose +//! higher level concepts to the rest of the SDK. //! -//! ## Conventions: +//!
+//! Generally you should not find yourself needing to edit this crate! Everything written +//! here requires additional care and attention to ensure that the cryptographic primitives are +//! secure.
+//! +//! ## Example: +//! +//! ```rust +//! use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; +//! +//! async fn example() -> Result<(), CryptoError> { +//! let key = SymmetricCryptoKey::generate(rand::thread_rng()); +//! +//! let data = "Hello, World!".to_owned(); +//! let encrypted = data.clone().encrypt_with_key(&key)?; +//! let decrypted: String = encrypted.decrypt_with_key(&key)?; +//! +//! assert_eq!(data, decrypted); +//! Ok(()) +//! } +//! ``` +//! +//! ## Development considerations +//! +//! This crate is expected to provide long term support for cryptographic operations. To that end, +//! the following considerations should be taken into account when making changes to this crate: +//! +//! - Limit public interfaces to the bare minimum. +//! - Breaking changes should be rare and well communicated. +//! - Serializable representation of keys and encrypted data must be supported indefinitely as we +//! have no way to update all data. +//! +//! ### Conventions: //! //! - Pure Functions that deterministically "derive" keys from input are prefixed with `derive_`. //! - Functions that generate non deterministically keys are prefixed with `make_`. //! -//! ## Differences from `clients` +//! ### Differences from `clients` //! //! There are some noteworthy differences compared to the other Bitwarden //! [clients](https://github.com/bitwarden/clients). These changes are made in an effort to diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs index 98f1282cc..6e3658c04 100644 --- a/crates/bitwarden-crypto/src/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -21,6 +21,7 @@ pub struct RsaKeyPair { pub private: EncString, } +/// Generate a new RSA key pair of 2048 bits pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { let mut rng = rand::thread_rng(); let bits = 2048; @@ -48,6 +49,7 @@ pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { }) } +/// Encrypt data using RSA-OAEP-SHA1 with a 2048 bit key pub(super) fn encrypt_rsa2048_oaep_sha1(public_key: &RsaPublicKey, data: &[u8]) -> Result> { let mut rng = rand::thread_rng(); diff --git a/crates/bitwarden-crypto/src/wordlist.rs b/crates/bitwarden-crypto/src/wordlist.rs index 4cc30ff74..f1e7a59c0 100644 --- a/crates/bitwarden-crypto/src/wordlist.rs +++ b/crates/bitwarden-crypto/src/wordlist.rs @@ -1,4 +1,4 @@ -// EFF's Long Wordlist from https://www.eff.org/dice +/// EFF's Long Wordlist from pub const EFF_LONG_WORD_LIST: &[&str] = &[ "abacus", "abdomen", diff --git a/crates/bitwarden-exporters/src/csv.rs b/crates/bitwarden-exporters/src/csv.rs index eb60fca21..a0dcd1040 100644 --- a/crates/bitwarden-exporters/src/csv.rs +++ b/crates/bitwarden-exporters/src/csv.rs @@ -54,7 +54,7 @@ pub(crate) fn export_csv(folders: Vec, ciphers: Vec) -> Result /// /// Be careful when changing this struct to maintain compatibility with old exports. #[derive(serde::Serialize)] diff --git a/crates/bitwarden-generators/src/username.rs b/crates/bitwarden-generators/src/username.rs index ccb46604b..36ded98b2 100644 --- a/crates/bitwarden-generators/src/username.rs +++ b/crates/bitwarden-generators/src/username.rs @@ -173,7 +173,7 @@ fn random_number(mut rng: impl RngCore) -> String { } /// Generate a username using a plus addressed email address -/// The format is +@ +/// The format is `+@` fn username_subaddress(mut rng: impl RngCore, r#type: AppendType, email: String) -> String { if email.len() < 3 { return email; @@ -195,7 +195,7 @@ fn username_subaddress(mut rng: impl RngCore, r#type: AppendType, email: String) } /// Generate a username using a catchall email address -/// The format is @ +/// The format is `@` fn username_catchall(mut rng: impl RngCore, r#type: AppendType, domain: String) -> String { if domain.is_empty() { return domain; diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2374d6263..87a365a50 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -73,6 +73,7 @@ pub(crate) enum ServiceAccountLoginMethod { }, } +/// The main struct to interact with the Bitwarden SDK. #[derive(Debug)] pub struct Client { token: Option, diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index b8891aa09..2b985eaa8 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -231,9 +231,9 @@ pub fn update_password( #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct DerivePinKeyResponse { - /// [UserKey] protected by PIN + /// [UserKey](bitwarden_crypto::UserKey) protected by PIN pin_protected_user_key: EncString, - /// PIN protected by [UserKey] + /// PIN protected by [UserKey](bitwarden_crypto::UserKey) encrypted_pin: EncString, } From 10d04b439bebdd346860314c44a909b0d7e07cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 12:08:30 +0200 Subject: [PATCH 03/11] Document developer tools (#723) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [x] Other ``` ## Objective Documented the tools we use for development. Sadly couldn't integrate `nextest` into `bacon` as the output is too different and `bacon` relies on parsing it to work. I still included it in the list for the cases where devs run it manually. I've also removed the `lto = true` instruction from bitwarden-napi, as it was ignored in favor of the workspace setting and was producing a warning. --- README.md | 19 +++++++++++++++++++ crates/bitwarden-napi/Cargo.toml | 3 --- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1ef88b7d..6c639d052 100644 --- a/README.md +++ b/README.md @@ -140,5 +140,24 @@ VALUES ); ``` +## Developer tools + +This project recommends the use of certain developer tools, and also includes configurations for +them to make developers lives easier. The use of these tools is optional and they might require a +separate installation step. + +The list of developer tools is: + +- `Visual Studio Code`: We provide a recommended extension list which should show under the + `Extensions` tab when opening this project with the editor. We also offer a few launch settings + and tasks to build and run the SDK +- `bacon`: This is a CLI background code checker. We provide a configuration file with some of the + most common tasks to run (`check`, `clippy`, `test`, `doc` - run `bacon -l` to see them all). This + tool needs to be installed separately by running `cargo install bacon --locked`. +- `nexttest`: This is a new and faster test runner, capable of running tests in parallel and with a + much nicer output compared to `cargo test`. This tool needs to be installed separately by running + `cargo install cargo-nextest --locked`. It can be manually run using + `cargo nextest run --all-features` + [secrets-manager]: https://bitwarden.com/products/secrets-manager/ [bws-help]: https://bitwarden.com/help/secrets-manager-cli/ diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 16853dbbf..7bcd54aad 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -29,8 +29,5 @@ napi-derive = "2" [build-dependencies] napi-build = "2.1.0" -[profile.release] -lto = true - [lints] workspace = true From a863e8986cd61c640d3f4cb543563dbfd6f345f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 13:34:06 +0200 Subject: [PATCH 04/11] Make init_crypto take MasterKey (#724) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective By making the `init_crypto_*` functions take `MasterKey` directly, we remove the need for `login_method` to be set before the `init_crypto` call. We also centralize the use of the raw password to the `MasterKey::derive` function, which allows us to more easily secure it. --- crates/bitwarden/src/auth/auth_request.rs | 30 +++++++------- crates/bitwarden/src/auth/login/api_key.rs | 7 +++- crates/bitwarden/src/auth/login/password.rs | 19 ++++----- .../bitwarden/src/auth/password/validate.rs | 38 +++++++++++++----- crates/bitwarden/src/client/client.rs | 31 +++------------ .../src/client/encryption_settings.rs | 26 ++++--------- crates/bitwarden/src/mobile/crypto.rs | 37 +++++++++++------- .../src/platform/generate_fingerprint.rs | 22 +++++------ crates/bitwarden/src/vault/send.rs | 39 ++++++++++--------- 9 files changed, 124 insertions(+), 125 deletions(-) diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs index 04dc9fdce..68e2cad90 100644 --- a/crates/bitwarden/src/auth/auth_request.rs +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -125,26 +125,25 @@ mod tests { use bitwarden_crypto::Kdf; use super::*; - use crate::{ - client::{LoginMethod, UserLoginMethod}, - mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}, - }; + use crate::mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}; #[test] fn test_approve() { let mut client = Client::new(None); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "7b821276-e27c-400b-9853-606393c87f18".to_owned(), - email: "test@bitwarden.com".to_owned(), - kdf: Kdf::PBKDF2 { + + let master_key = bitwarden_crypto::MasterKey::derive( + "asdfasdfasdf".as_bytes(), + "test@bitwarden.com".as_bytes(), + &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, - })); + ) + .unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); 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(); client - .initialize_user_crypto("asdfasdfasdf", user_key, private_key) + .initialize_user_crypto_master_key(master_key, user_key, private_key) .unwrap(); let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyLRDUwXB4BfQ507D4meFPmwn5zwy3IqTPJO4plrrhnclWahXa240BzyFW9gHgYu+Jrgms5xBfRTBMcEsqqNm7+JpB6C1B6yvnik0DpJgWQw1rwvy4SUYidpR/AWbQi47n/hvnmzI/sQxGddVfvWu1iTKOlf5blbKYAXnUE5DZBGnrWfacNXwRRdtP06tFB0LwDgw+91CeLSJ9py6dm1qX5JIxoO8StJOQl65goLCdrTWlox+0Jh4xFUfCkb+s3px+OhSCzJbvG/hlrSRcUz5GnwlCEyF3v5lfUtV96MJD+78d8pmH6CfFAp2wxKRAbGdk+JccJYO6y6oIXd3Fm7twIDAQAB"; @@ -206,14 +205,13 @@ mod tests { // Initialize an existing client which is unlocked let mut existing_device = Client::new(None); - existing_device.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "123".to_owned(), - email: email.to_owned(), - kdf: kdf.clone(), - })); + + let master_key = + bitwarden_crypto::MasterKey::derive("asdfasdfasdf".as_bytes(), email.as_bytes(), &kdf) + .unwrap(); existing_device - .initialize_user_crypto("asdfasdfasdf", user_key, private_key.parse().unwrap()) + .initialize_user_crypto_master_key(master_key, user_key, private_key.parse().unwrap()) .unwrap(); // Initialize a new device which will request to be logged in diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index 5d7fdcd96..8b83e3a38 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; +use bitwarden_crypto::{EncString, MasterKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -37,6 +37,9 @@ pub(crate) async fn login_api_key( r.refresh_token.clone(), r.expires_in, ); + + let master_key = MasterKey::derive(input.password.as_bytes(), email.as_bytes(), &kdf)?; + client.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey { client_id: input.client_id.to_owned(), client_secret: input.client_secret.to_owned(), @@ -47,7 +50,7 @@ pub(crate) async fn login_api_key( let user_key: EncString = require!(r.key.as_deref()).parse()?; let private_key: EncString = require!(r.private_key.as_deref()).parse()?; - client.initialize_user_crypto(&input.password, user_key, private_key)?; + client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; } ApiKeyLoginResponse::process_response(response) diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index 8ae9daebc..e0cb67dbe 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -1,5 +1,5 @@ #[cfg(feature = "internal")] -use log::{debug, info}; +use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -22,19 +22,20 @@ pub(crate) async fn login_password( client: &mut Client, input: &PasswordLoginRequest, ) -> Result { - use bitwarden_crypto::{EncString, HashPurpose}; + use bitwarden_crypto::{EncString, HashPurpose, MasterKey}; - use crate::{auth::determine_password_hash, client::UserLoginMethod, error::require}; + use crate::{client::UserLoginMethod, error::require}; info!("password logging in"); - debug!("{:#?}, {:#?}", client, input); - let password_hash = determine_password_hash( - &input.email, + let master_key = MasterKey::derive( + input.password.as_bytes(), + input.email.as_bytes(), &input.kdf, - &input.password, - HashPurpose::ServerAuthorization, )?; + let password_hash = master_key + .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?; + let response = request_identity_tokens(client, input, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { @@ -52,7 +53,7 @@ pub(crate) async fn login_password( let user_key: EncString = require!(r.key.as_deref()).parse()?; let private_key: EncString = require!(r.private_key.as_deref()).parse()?; - client.initialize_user_crypto(&input.password, user_key, private_key)?; + client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; } PasswordLoginResponse::process_response(response) diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs index 9003347d9..7e30d858b 100644 --- a/crates/bitwarden/src/auth/password/validate.rs +++ b/crates/bitwarden/src/auth/password/validate.rs @@ -111,19 +111,28 @@ mod tests { use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; let mut client = Client::new(None); + + let password = "asdfasdfasdf"; + let email = "test@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - email: "test@bitwarden.com".to_string(), - kdf: Kdf::PBKDF2 { - iterations: NonZeroU32::new(600_000).unwrap(), - }, + email: email.to_string(), + kdf: kdf.clone(), client_id: "1".to_string(), })); + let master_key = + bitwarden_crypto::MasterKey::derive(password.as_bytes(), 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(); client - .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) .unwrap(); let result = @@ -142,19 +151,28 @@ mod tests { use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; let mut client = Client::new(None); + + let password = "asdfasdfasdf"; + let email = "test@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - email: "test@bitwarden.com".to_string(), - kdf: Kdf::PBKDF2 { - iterations: NonZeroU32::new(600_000).unwrap(), - }, + email: email.to_string(), + kdf: kdf.clone(), client_id: "1".to_string(), })); + let master_key = + bitwarden_crypto::MasterKey::derive(password.as_bytes(), 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(); client - .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key) + .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()) diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 87a365a50..0251723d0 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; pub use bitwarden_crypto::Kdf; use bitwarden_crypto::SymmetricCryptoKey; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{AsymmetricEncString, EncString, MasterKey}; use chrono::Utc; use reqwest::header::{self, HeaderValue}; use uuid::Uuid; @@ -247,23 +247,14 @@ impl Client { } #[cfg(feature = "internal")] - pub(crate) fn initialize_user_crypto( + pub(crate) fn initialize_user_crypto_master_key( &mut self, - password: &str, + master_key: MasterKey, user_key: EncString, private_key: EncString, ) -> Result<&EncryptionSettings> { - let login_method = match &self.login_method { - Some(LoginMethod::User(u)) => u, - _ => return Err(Error::NotAuthenticated), - }; - - self.encryption_settings = Some(EncryptionSettings::new( - login_method, - password, - user_key, - private_key, - )?); + self.encryption_settings = + Some(EncryptionSettings::new(master_key, user_key, private_key)?); Ok(self .encryption_settings .as_ref() @@ -289,20 +280,10 @@ impl Client { #[cfg(feature = "mobile")] pub(crate) fn initialize_user_crypto_pin( &mut self, - pin: &str, + pin_key: MasterKey, pin_protected_user_key: EncString, private_key: EncString, ) -> Result<&EncryptionSettings> { - use bitwarden_crypto::MasterKey; - - let pin_key = match &self.login_method { - Some(LoginMethod::User( - UserLoginMethod::Username { email, kdf, .. } - | UserLoginMethod::ApiKey { email, kdf, .. }, - )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, - _ => return Err(Error::NotAuthenticated), - }; - let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) } diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 6e4da9895..025b3cec7 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -2,11 +2,11 @@ use std::collections::HashMap; use bitwarden_crypto::{AsymmetricCryptoKey, KeyContainer, SymmetricCryptoKey}; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{AsymmetricEncString, EncString, MasterKey}; use uuid::Uuid; #[cfg(feature = "internal")] -use crate::{client::UserLoginMethod, error::Result}; +use crate::error::Result; pub struct EncryptionSettings { user_key: SymmetricCryptoKey, @@ -21,28 +21,16 @@ impl std::fmt::Debug for EncryptionSettings { } impl EncryptionSettings { - /// Initialize the encryption settings with the user password and their encrypted keys + /// Initialize the encryption settings with the master key and the encrypted user keys #[cfg(feature = "internal")] pub(crate) fn new( - login_method: &UserLoginMethod, - password: &str, + master_key: MasterKey, user_key: EncString, private_key: EncString, ) -> Result { - use bitwarden_crypto::MasterKey; - - match login_method { - UserLoginMethod::Username { email, kdf, .. } - | UserLoginMethod::ApiKey { email, kdf, .. } => { - // Derive master key from password - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; - - // Decrypt the user key - let user_key = master_key.decrypt_user_key(user_key)?; - - Self::new_decrypted_key(user_key, private_key) - } - } + // Decrypt the user key + let user_key = master_key.decrypt_user_key(user_key)?; + Self::new_decrypted_key(user_key, private_key) } /// Initialize the encryption settings with the decrypted user key and the encrypted user diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 2b985eaa8..35c38c910 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -92,19 +92,15 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key}; - let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { - client_id: "".to_string(), - email: req.email, - kdf: req.kdf_params, - }); - client.set_login_method(login_method); - let private_key: EncString = req.private_key.parse()?; match req.method { InitUserCryptoMethod::Password { password, user_key } => { let user_key: EncString = user_key.parse()?; - client.initialize_user_crypto(&password, user_key, private_key)?; + + let master_key = + MasterKey::derive(password.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; + client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { let decrypted_user_key = DecryptedString::new(Box::new(decrypted_user_key)); @@ -115,7 +111,8 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ pin, pin_protected_user_key, } => { - client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; + let pin_key = MasterKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; + client.initialize_user_crypto_pin(pin_key, pin_protected_user_key, private_key)?; } InitUserCryptoMethod::AuthRequest { request_private_key, @@ -150,6 +147,14 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ } } + client.set_login_method(crate::client::LoginMethod::User( + crate::client::UserLoginMethod::Username { + client_id: "".to_string(), + email: req.email, + kdf: req.kdf_params, + }, + )); + Ok(()) } @@ -493,18 +498,20 @@ mod tests { use bitwarden_crypto::AsymmetricCryptoKey; let mut client = Client::new(None); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "7b821276-e27c-400b-9853-606393c87f18".to_owned(), - email: "test@bitwarden.com".to_owned(), - kdf: Kdf::PBKDF2 { + + let master_key = bitwarden_crypto::MasterKey::derive( + "asdfasdfasdf".as_bytes(), + "test@bitwarden.com".as_bytes(), + &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, - })); + ) + .unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); 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(); client - .initialize_user_crypto("asdfasdfasdf", user_key, private_key) + .initialize_user_crypto_master_key(master_key, user_key, private_key) .unwrap(); let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB"; diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index 59d81d652..b61454f06 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -55,10 +55,7 @@ mod tests { use std::num::NonZeroU32; use super::*; - use crate::{ - client::{Kdf, LoginMethod, UserLoginMethod}, - Client, - }; + use crate::{client::Kdf, Client}; #[test] fn test_generate_user_fingerprint() { @@ -67,16 +64,19 @@ mod tests { let fingerprint_material = "a09726a0-9590-49d1-a5f5-afe300b6a515"; let mut client = Client::new(None); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "a09726a0-9590-49d1-a5f5-afe300b6a515".to_owned(), - email: "robb@stark.com".to_owned(), - kdf: Kdf::PBKDF2 { + + let master_key = bitwarden_crypto::MasterKey::derive( + "asdfasdfasdf".as_bytes(), + "robb@stark.com".as_bytes(), + &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, - })); + ) + .unwrap(); + client - .initialize_user_crypto( - "asdfasdfasdf", + .initialize_user_crypto_master_key( + master_key, user_key.parse().unwrap(), private_key.parse().unwrap(), ) diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 37e6efcc9..81341d0e3 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -361,26 +361,27 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey}; use super::{Send, SendText, SendTextView, SendType}; use crate::{ - client::{encryption_settings::EncryptionSettings, Kdf, UserLoginMethod}, + client::{encryption_settings::EncryptionSettings, Kdf}, vault::SendView, }; #[test] fn test_get_send_key() { // Initialize user encryption with some test data - let enc = EncryptionSettings::new( - &UserLoginMethod::Username { - client_id: "test".into(), - email: "test@bitwarden.com".into(), - kdf: Kdf::PBKDF2 { - iterations: 345123.try_into().unwrap(), - }, + let master_key = MasterKey::derive( + "asdfasdfasdf".as_bytes(), + "test@bitwarden.com".as_bytes(), + &Kdf::PBKDF2 { + iterations: 345123.try_into().unwrap(), }, - "asdfasdfasdf", + ) + .unwrap(); + let enc = EncryptionSettings::new( + master_key, "2.majkL1/hNz9yptLqNAUSnw==|RiOzMTTJMG948qu8O3Zm1EQUO2E8BuTwFKnO9LWQjMzxMWJM5GbyOq2/A+tumPbTERt4JWur/FKfgHb+gXuYiEYlXPMuVBvT7nv4LPytJuM=|IVqMxHJeR1ZXY0sGngTC0x+WqbG8p6V+BTrdgBbQXjM=".parse().unwrap(), "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(), ).unwrap(); @@ -398,15 +399,17 @@ mod tests { } fn build_encryption_settings() -> EncryptionSettings { - EncryptionSettings::new( - &UserLoginMethod::Username { - client_id: "test".into(), - email: "test@bitwarden.com".into(), - kdf: Kdf::PBKDF2 { - iterations: 600_000.try_into().unwrap(), - }, + let master_key = MasterKey::derive( + "asdfasdfasdf".as_bytes(), + "test@bitwarden.com".as_bytes(), + &Kdf::PBKDF2 { + iterations: 600_000.try_into().unwrap(), }, - "asdfasdfasdf", + ) + .unwrap(); + + EncryptionSettings::new( + master_key, "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(), "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(), ).unwrap() From 435cecd4deb12d546736c106d67d48d9d74273a0 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:54:31 -0500 Subject: [PATCH 05/11] [SM-1189] Fix renew for service account access token logins (#702) ## Type of change ``` - [X] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Fix `renew_token` to properly `client.set_tokens(r.access_token, r.refresh_token, r.expires_in)` when logged in via service account access token. When the client's OAuth token expired, a call would be made to the identity server, but the OAuth token was never replaced on the client. The response would map to nothing https://github.com/bitwarden/sdk/blob/a5692418b5836acd3662ce425258d660614bf7f3/crates/bitwarden/src/auth/renew.rs#L87-L90 Then silently error here https://github.com/bitwarden/sdk/blob/4a339a911e6db1583465f69b4c9ff981104ea0ef/crates/bitwarden/src/client/client.rs#L170-L175 This occurs when a client successfully `client.auth().login_access_token` via state, but then shortly after the OAuth token would expire. Subsequent calls would produce HTTP 401. An easier way to produce the error is to log in without state `thread::sleep(Duration::from_secs(4000));` then attempt to make any client call. ## Code changes - **crates/bitwarden/src/auth/renew.rs:** Match to the `IdentityTokenResponse:Payload` returned by the access token request to the identity server. ## Before you submit - Please add **unit tests** where it makes sense to do so --- crates/bitwarden/src/auth/renew.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index 6f93f7482..783494ae2 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -56,11 +56,8 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { .send(&client.__api_configurations) .await?; - if let ( - IdentityTokenResponse::Authenticated(r), - Some(state_file), - Ok(enc_settings), - ) = (&result, state_file, client.get_encryption_settings()) + if let (IdentityTokenResponse::Payload(r), Some(state_file), Ok(enc_settings)) = + (&result, state_file, client.get_encryption_settings()) { if let Some(enc_key) = enc_settings.get_key(&None) { let state = @@ -83,6 +80,10 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { client.set_tokens(r.access_token, r.refresh_token, r.expires_in); return Ok(()); } + IdentityTokenResponse::Payload(r) => { + client.set_tokens(r.access_token, r.refresh_token, r.expires_in); + return Ok(()); + } _ => { // We should never get here return Err(Error::InvalidResponse); From a08143d0d4420bb754f69b4ec4d663ac47698d72 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 22 Apr 2024 18:59:59 +0200 Subject: [PATCH 06/11] Mark generate_random_bytes to return sensitive (#726) Make `generate_random_bytes` return `Sensitive`. Also migrate `derive_shareable_key` to accept a sensitive input. --- .vscode/settings.json | 1 + .../src/keys/shareable_key.rs | 27 +++++++++++++------ .../src/keys/symmetric_crypto_key.rs | 4 +-- .../src/sensitive/sensitive.rs | 7 +++++ crates/bitwarden-crypto/src/util.rs | 8 +++--- .../bitwarden-exporters/src/encrypted_json.rs | 14 +++++----- crates/bitwarden/src/auth/access_token.rs | 6 ++--- crates/bitwarden/src/vault/send.rs | 10 ++++--- 8 files changed, 51 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e92fcfb76..a1b091d7f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,6 +21,7 @@ "totp", "uniffi", "wordlist", + "Zeroize", "zxcvbn" ] } diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs index e84e05ca3..490e6b56e 100644 --- a/crates/bitwarden-crypto/src/keys/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -3,10 +3,12 @@ use std::pin::Pin; use aes::cipher::typenum::U64; use generic_array::GenericArray; use hmac::Mac; +use zeroize::Zeroize; use crate::{ keys::SymmetricCryptoKey, util::{hkdf_expand, PbkdfSha256Hmac}, + Sensitive, }; /// Derive a shareable key using hkdf from secret and name. @@ -14,36 +16,45 @@ use crate::{ /// A specialized variant of this function was called `CryptoService.makeSendKey` in the Bitwarden /// `clients` repository. pub fn derive_shareable_key( - secret: [u8; 16], + secret: Sensitive<[u8; 16]>, name: &str, info: Option<&str>, ) -> SymmetricCryptoKey { // Because all inputs are fixed size, we can unwrap all errors here without issue - - // TODO: Are these the final `key` and `info` parameters or should we change them? I followed - // the pattern used for sends - let res = PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) + let mut res = PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) .expect("hmac new_from_slice should not fail") - .chain_update(secret) + .chain_update(secret.expose()) .finalize() .into_bytes(); let mut key: Pin>> = hkdf_expand(&res, info).expect("Input is a valid size"); + // Zeroize the temporary buffer + res.zeroize(); + SymmetricCryptoKey::try_from(key.as_mut_slice()).expect("Key is a valid size") } #[cfg(test)] mod tests { use super::derive_shareable_key; + use crate::Sensitive; #[test] fn test_derive_shareable_key() { - let key = derive_shareable_key(*b"&/$%F1a895g67HlX", "test_key", None); + let key = derive_shareable_key( + Sensitive::new(Box::new(*b"&/$%F1a895g67HlX")), + "test_key", + None, + ); assert_eq!(key.to_base64().expose(), "4PV6+PcmF2w7YHRatvyMcVQtI7zvCyssv/wFWmzjiH6Iv9altjmDkuBD1aagLVaLezbthbSe+ktR+U6qswxNnQ=="); - let key = derive_shareable_key(*b"67t9b5g67$%Dh89n", "test_key", Some("test")); + let key = derive_shareable_key( + Sensitive::new(Box::new(*b"67t9b5g67$%Dh89n")), + "test_key", + Some("test"), + ); assert_eq!(key.to_base64().expose(), "F9jVQmrACGx9VUPjuzfMYDjr726JtL300Y3Yg+VYUnVQtQ1s8oImJ5xtp1KALC9h2nav04++1LDW4iFD+infng=="); } } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 23d09cbca..b58468c7f 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -134,9 +134,9 @@ impl std::fmt::Debug for SymmetricCryptoKey { #[cfg(test)] pub fn derive_symmetric_key(name: &str) -> SymmetricCryptoKey { - use crate::{derive_shareable_key, generate_random_bytes}; + use crate::{derive_shareable_key, generate_random_bytes, Sensitive}; - let secret: [u8; 16] = generate_random_bytes(); + let secret: Sensitive<[u8; 16]> = generate_random_bytes(); derive_shareable_key(secret, name, None) } diff --git a/crates/bitwarden-crypto/src/sensitive/sensitive.rs b/crates/bitwarden-crypto/src/sensitive/sensitive.rs index 996b20330..481058064 100644 --- a/crates/bitwarden-crypto/src/sensitive/sensitive.rs +++ b/crates/bitwarden-crypto/src/sensitive/sensitive.rs @@ -54,6 +54,13 @@ impl Sensitive { } } +/// Helper to convert a `Sensitive<[u8, N]>` to a `SensitiveVec`. +impl From> for SensitiveVec { + fn from(sensitive: Sensitive<[u8; N]>) -> Self { + SensitiveVec::new(Box::new(sensitive.value.to_vec())) + } +} + /// Helper to convert a `Sensitive>` to a `Sensitive`, care is taken to ensure any /// intermediate copies are zeroed to avoid leaking sensitive data. impl TryFrom for SensitiveString { diff --git a/crates/bitwarden-crypto/src/util.rs b/crates/bitwarden-crypto/src/util.rs index ba60db366..b38cb0e00 100644 --- a/crates/bitwarden-crypto/src/util.rs +++ b/crates/bitwarden-crypto/src/util.rs @@ -7,8 +7,9 @@ use rand::{ distributions::{Distribution, Standard}, Rng, }; +use zeroize::Zeroize; -use crate::{CryptoError, Result}; +use crate::{CryptoError, Result, Sensitive}; pub(crate) type PbkdfSha256Hmac = hmac::Hmac; pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = @@ -30,11 +31,12 @@ pub(crate) fn hkdf_expand>( } /// Generate random bytes that are cryptographically secure -pub fn generate_random_bytes() -> T +pub fn generate_random_bytes() -> Sensitive where Standard: Distribution, + T: Zeroize, { - rand::thread_rng().gen() + Sensitive::new(Box::new(rand::thread_rng().gen::())) } pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] { diff --git a/crates/bitwarden-exporters/src/encrypted_json.rs b/crates/bitwarden-exporters/src/encrypted_json.rs index 1bbfd2660..6dabc90d5 100644 --- a/crates/bitwarden-exporters/src/encrypted_json.rs +++ b/crates/bitwarden-exporters/src/encrypted_json.rs @@ -1,5 +1,7 @@ -use base64::{engine::general_purpose::STANDARD, Engine}; -use bitwarden_crypto::{generate_random_bytes, Kdf, KeyEncryptable, PinKey}; +use base64::engine::general_purpose::STANDARD; +use bitwarden_crypto::{ + generate_random_bytes, Kdf, KeyEncryptable, PinKey, Sensitive, SensitiveVec, +}; use serde::Serialize; use thiserror::Error; use uuid::Uuid; @@ -43,16 +45,16 @@ pub(crate) fn export_encrypted_json( ), }; - let salt: [u8; 16] = generate_random_bytes(); - let salt = STANDARD.encode(salt); - let key = PinKey::derive(password.as_bytes(), salt.as_bytes(), &kdf)?; + let salt: Sensitive<[u8; 16]> = generate_random_bytes(); + let salt = SensitiveVec::from(salt).encode_base64(STANDARD); + let key = PinKey::derive(password.as_bytes(), salt.expose().as_bytes(), &kdf)?; let enc_key_validation = Uuid::new_v4().to_string(); let encrypted_export = EncryptedJsonExport { encrypted: true, password_protected: true, - salt, + salt: salt.expose().to_string(), kdf_type, kdf_iterations, kdf_memory, diff --git a/crates/bitwarden/src/auth/access_token.rs b/crates/bitwarden/src/auth/access_token.rs index 4b6f050c8..0bf43d29a 100644 --- a/crates/bitwarden/src/auth/access_token.rs +++ b/crates/bitwarden/src/auth/access_token.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, str::FromStr}; use base64::Engine; -use bitwarden_crypto::{derive_shareable_key, SymmetricCryptoKey}; +use bitwarden_crypto::{derive_shareable_key, Sensitive, SymmetricCryptoKey}; use uuid::Uuid; use crate::{error::AccessTokenInvalidError, util::STANDARD_INDIFFERENT}; @@ -45,12 +45,12 @@ impl FromStr for AccessToken { let encryption_key = STANDARD_INDIFFERENT .decode(encryption_key) .map_err(AccessTokenInvalidError::InvalidBase64)?; - let encryption_key: [u8; 16] = encryption_key.try_into().map_err(|e: Vec<_>| { + let encryption_key = Sensitive::new(encryption_key.try_into().map_err(|e: Vec<_>| { AccessTokenInvalidError::InvalidBase64Length { expected: 16, got: e.len(), } - })?; + })?); let encryption_key = derive_shareable_key(encryption_key, "accesstoken", Some("sm-access-token")); diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 81341d0e3..550da84aa 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -5,7 +5,7 @@ use base64::{ use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; use bitwarden_crypto::{ derive_shareable_key, generate_random_bytes, CryptoError, EncString, KeyDecryptable, - KeyEncryptable, LocateKey, SymmetricCryptoKey, + KeyEncryptable, LocateKey, Sensitive, SensitiveVec, SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -149,7 +149,9 @@ impl Send { } fn derive_shareable_key(key: &[u8]) -> Result { - let key = key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?; + let key = Sensitive::new(Box::new( + key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?, + )); Ok(derive_shareable_key(key, "send", Some("send"))) } } @@ -265,8 +267,8 @@ impl KeyEncryptable for SendView { .map_err(|_| CryptoError::InvalidKey)?, // New send, generate random key (None, None) => { - let key: [u8; 16] = generate_random_bytes(); - key.to_vec() + let key: Sensitive<[u8; 16]> = generate_random_bytes(); + SensitiveVec::from(key).expose().to_owned() } // Existing send without key _ => return Err(CryptoError::InvalidKey), From e0eb6598ed8eedde8313eb2912143df44ef7c508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 19:13:53 +0200 Subject: [PATCH 07/11] [PM-7066] Use Sensitive in MasterKey (#725) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Use Sensitive in MasterKey and bubble up the Sensitive change for all the requests that contain a password or pin. Note that this requires changing the request structs to be taken by value. --- Cargo.lock | 2 + .../bitwarden-crypto/src/keys/master_key.rs | 44 +++++++++------- .../src/sensitive/sensitive.rs | 30 +++++++---- crates/bitwarden-json/src/client.rs | 6 +-- crates/bitwarden-uniffi/src/auth/mod.rs | 16 ++++-- crates/bitwarden-uniffi/src/crypto.rs | 7 ++- crates/bitwarden-wasm/Cargo.toml | 1 + crates/bitwarden-wasm/src/client.rs | 10 ++-- crates/bitwarden/src/auth/auth_request.rs | 13 +++-- crates/bitwarden/src/auth/client_auth.rs | 20 ++++--- crates/bitwarden/src/auth/login/api_key.rs | 10 ++-- crates/bitwarden/src/auth/login/password.rs | 28 +++++----- crates/bitwarden/src/auth/login/two_factor.rs | 10 ++-- crates/bitwarden/src/auth/mod.rs | 12 ++--- .../bitwarden/src/auth/password/validate.rs | 49 ++++++++++------- crates/bitwarden/src/auth/register.rs | 25 +++++---- crates/bitwarden/src/client/client.rs | 2 +- crates/bitwarden/src/mobile/client_crypto.rs | 4 +- crates/bitwarden/src/mobile/client_kdf.rs | 4 +- crates/bitwarden/src/mobile/crypto.rs | 52 +++++++++++-------- crates/bitwarden/src/mobile/kdf.rs | 9 ++-- .../src/platform/generate_fingerprint.rs | 4 +- .../src/platform/get_user_api_key.rs | 11 ++-- .../platform/secret_verification_request.rs | 3 +- crates/bitwarden/src/vault/send.rs | 6 +-- crates/bitwarden/tests/register.rs | 8 +-- crates/bw/Cargo.toml | 1 + crates/bw/src/auth/login.rs | 17 +++--- crates/bw/src/main.rs | 5 +- 29 files changed, 242 insertions(+), 167 deletions(-) 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, From b4dd215d10f1106659451a54aceca5455ef82ab3 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:55:55 -0500 Subject: [PATCH 08/11] [SM-1189] Update change log with fix (#727) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [X] Other ``` ## Objective ## Code changes - **file.ext:** Description of what was changed and why ## Before you submit - Please add **unit tests** where it makes sense to do so --- crates/bitwarden/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index ee4c6033b..344bdd6ce 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -12,6 +12,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Switched TLS backend to `rustls`, removing the dependency on `OpenSSL`. (#374) - `client::AccessToken` is now `auth::AccessToken`. (#656) +### Fixed + +- Fix renew for service account access token logins (#702) + ## [0.4.0] - 2023-12-21 ### Added From dc4ad160c22a976aace9dc1c44f521979eb50478 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:18:42 +0200 Subject: [PATCH 09/11] Bump rustls from 0.22.2 to 0.22.4 (#728) Bumps [rustls](https://github.com/rustls/rustls) from 0.22.2 to 0.22.4.
Commits
  • ae277be Prepare 0.22.4
  • 5374108 complete_io: bail out if progress is impossible
  • 00e695d Regression test for complete_io infinite loop bug
  • 0c6cd7e Don't specially handle unauthenticated close_notify alerts
  • a1e28cd Prepare 0.22.3
  • 114c476 suites.rs: fix nonsensical duplicated feature gate
  • ffd933a Fix new clippy::use_self warnings
  • fb6e70a dangerous_extract_secrets(): test ConnectionTrafficSecrets variant
  • 2d5c80e Return correct ConnectionTrafficSecrets variant when AES-256-GCM is negotia...
  • 77ffe49 tests: fix implied_bounds_in_impls clippy warn
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustls&package-manager=cargo&previous-version=0.22.2&new-version=0.22.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/bitwarden/sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9694fb910..ca1dc0ea4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1861,7 +1861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.48.5", ] [[package]] @@ -2718,9 +2718,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", From ee463eca6f01df0f336e081c0860123d11f5cff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 23 Apr 2024 15:26:37 +0200 Subject: [PATCH 10/11] Use Option::insert() (#729) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Just discovered that `Option::insert` is a thing, which simplifies some code that first sets the value and then immediately returns a reference to it. --- crates/bitwarden/src/client/client.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 8e17d0c6d..95a1e8738 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -253,12 +253,11 @@ impl Client { user_key: EncString, private_key: EncString, ) -> Result<&EncryptionSettings> { - self.encryption_settings = - Some(EncryptionSettings::new(master_key, user_key, private_key)?); - Ok(self - .encryption_settings - .as_ref() - .expect("Value is initialized previously")) + Ok(self.encryption_settings.insert(EncryptionSettings::new( + master_key, + user_key, + private_key, + )?)) } #[cfg(feature = "internal")] @@ -267,14 +266,12 @@ impl Client { user_key: SymmetricCryptoKey, private_key: EncString, ) -> Result<&EncryptionSettings> { - self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( - user_key, - private_key, - )?); Ok(self .encryption_settings - .as_ref() - .expect("Value is initialized previously")) + .insert(EncryptionSettings::new_decrypted_key( + user_key, + private_key, + )?)) } #[cfg(feature = "mobile")] @@ -292,10 +289,8 @@ impl Client { &mut self, key: SymmetricCryptoKey, ) -> &EncryptionSettings { - self.encryption_settings = Some(EncryptionSettings::new_single_key(key)); self.encryption_settings - .as_ref() - .expect("Value is initialized previously") + .insert(EncryptionSettings::new_single_key(key)) } #[cfg(feature = "internal")] From 72a3b4947714ea847b93eae847edfeed3a5459fd Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:12:24 -0500 Subject: [PATCH 11/11] Sm 1186 fix docker image (#706) ## Type of change - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ## Objective Fix missing shared object libraries error: ```bash docker run --rm -it --platform linux/amd64 $ACR_URL/bws:main --help rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2 ``` ## Code changes - **crates/bws/Dockerfile:** x86_64 Linux stores some shared object libraries in a `/lib64` directory. We were missing `/lib64/ld-linux-x86-64.so.2`. This should handle dependencies for both arches. Also moved the binary to `/bin` to allow for `--entrypoint=bws` overriding, without having to specify the full path to the bin. ## Before you submit - Please add **unit tests** where it makes sense to do so --- crates/bws/Dockerfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/bws/Dockerfile b/crates/bws/Dockerfile index cc9e1c481..ccf9865c9 100644 --- a/crates/bws/Dockerfile +++ b/crates/bws/Dockerfile @@ -19,7 +19,10 @@ RUN cargo build --release --bin bws # Bundle bws dependencies RUN mkdir /lib-bws -RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/' | xargs -I % cp % /lib-bws +RUN mkdir /lib64-bws + +RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/lib' | xargs -I % cp % /lib-bws +RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/lib64' | xargs -I % cp % /lib64-bws # Make a HOME directory for the app stage RUN mkdir -p /home/app @@ -35,15 +38,16 @@ LABEL com.bitwarden.product="bitwarden" # Set a HOME directory COPY --from=build /home/app /home/app ENV HOME=/home/app +WORKDIR /home/app # Copy built project from the build stage -WORKDIR /usr/local/bin -COPY --from=build /app/target/release/bws . +COPY --from=build /app/target/release/bws /bin/bws # Copy certs COPY --from=build /etc/ssl/certs /etc/ssl/certs # Copy bws dependencies COPY --from=build /lib-bws /lib +COPY --from=build /lib64-bws /lib64 ENTRYPOINT ["bws"]