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/bacon.toml b/bacon.toml new file mode 100644 index 000000000..6844980d5 --- /dev/null +++ b/bacon.toml @@ -0,0 +1,78 @@ +# 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", +] +allow_warnings = true +need_stdout = false +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. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-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-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 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 2374d6263..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; @@ -73,6 +73,7 @@ pub(crate) enum ServiceAccountLoginMethod { }, } +/// The main struct to interact with the Bitwarden SDK. #[derive(Debug)] pub struct Client { token: Option, @@ -246,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() @@ -288,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 b8891aa09..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(()) } @@ -231,9 +236,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, } @@ -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()