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/Cargo.lock b/Cargo.lock index bda3a9c4a..ca1dc0ea4 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", @@ -1859,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]] @@ -2716,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", 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..5a24125af 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -5,8 +5,14 @@ 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 +/// +/// 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 +28,7 @@ pub enum Kdf { } impl Default for Kdf { + /// Default KDF for new accounts. fn default() -> Self { Kdf::PBKDF2 { iterations: default_pbkdf2_iterations(), @@ -29,15 +36,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") } @@ -60,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)) } @@ -115,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(), }, @@ -141,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(), @@ -163,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/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/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/sensitive/sensitive.rs b/crates/bitwarden-crypto/src/sensitive/sensitive.rs index 996b20330..33e6fddc1 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 { @@ -67,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 @@ -139,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())) } } @@ -159,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-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-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-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-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-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-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-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/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 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/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs index 04dc9fdce..8c3523d20 100644 --- a/crates/bitwarden/src/auth/auth_request.rs +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -122,29 +122,28 @@ fn test_auth_request() { mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{Kdf, SensitiveVec}; 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( + &SensitiveVec::test(b"asdfasdfasdf"), + "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,16 @@ 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( + &SensitiveVec::test(b"asdfasdfasdf"), + 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/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 5d7fdcd96..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; +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()?; @@ -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.into(), 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) @@ -73,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 8ae9daebc..cae51d6eb 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -1,5 +1,7 @@ #[cfg(feature = "internal")] -use log::{debug, info}; +use bitwarden_crypto::SensitiveString; +#[cfg(feature = "internal")] +use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -20,22 +22,22 @@ use crate::{ #[cfg(feature = "internal")] pub(crate) async fn login_password( client: &mut Client, - input: &PasswordLoginRequest, + 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, - &input.kdf, - &input.password, - HashPurpose::ServerAuthorization, - )?; - let response = request_identity_tokens(client, input, &password_hash).await?; + 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.email, &input.two_factor, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { client.set_tokens( @@ -52,7 +54,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) @@ -61,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 @@ -86,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 9003347d9..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,30 +111,45 @@ 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 = SensitiveVec::test(b"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, 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, "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")] @@ -139,26 +157,37 @@ 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 = SensitiveVec::test(b"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, 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()) - .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/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); diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2374d6263..95a1e8738 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, @@ -196,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 } @@ -246,27 +247,17 @@ 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, + Ok(self.encryption_settings.insert(EncryptionSettings::new( + master_key, user_key, private_key, - )?); - Ok(self - .encryption_settings - .as_ref() - .expect("Value is initialized previously")) + )?)) } #[cfg(feature = "internal")] @@ -275,33 +266,21 @@ 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")] 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) } @@ -310,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")] 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/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 b8891aa09..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, @@ -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.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 } => { 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.into(), 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(()) } @@ -192,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()? @@ -204,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, )?; @@ -231,14 +238,14 @@ 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, } #[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) @@ -249,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 { @@ -283,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), }; @@ -330,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(), }, }, @@ -338,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); @@ -349,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(), }, }, @@ -361,7 +374,7 @@ mod tests { .kdf() .hash_password( "test@bitwarden.com".into(), - "123412341234".into(), + SensitiveString::test("123412341234"), kdf.clone(), bitwarden_crypto::HashPurpose::ServerAuthorization, ) @@ -401,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(), }, }, @@ -409,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); @@ -422,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, }, }, @@ -460,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, }, }, @@ -490,21 +503,23 @@ 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); - 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( + &SensitiveVec::test(b"asdfasdfasdf"), + "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/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 59d81d652..f6ffe0f36 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -54,11 +54,10 @@ pub(crate) fn generate_user_fingerprint( mod tests { use std::num::NonZeroU32; + use bitwarden_crypto::SensitiveVec; + use super::*; - use crate::{ - client::{Kdf, LoginMethod, UserLoginMethod}, - Client, - }; + use crate::{client::Kdf, Client}; #[test] fn test_generate_user_fingerprint() { @@ -67,16 +66,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( + &SensitiveVec::test(b"asdfasdfasdf"), + "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/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 37e6efcc9..e681e5468 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), @@ -361,26 +363,27 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey, SensitiveVec}; 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( + &SensitiveVec::test(b"asdfasdfasdf"), + "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 +401,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( + &SensitiveVec::test(b"asdfasdfasdf"), + "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() 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, 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"]