From 72bcf39e3472d7de2513f17648d20e8726700953 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 11:44:47 +0100 Subject: [PATCH 001/152] Add xchacha20poly1305 crypto primitives --- Cargo.lock | 57 +++++++++-- crates/bitwarden-crypto/Cargo.toml | 3 + crates/bitwarden-crypto/src/lib.rs | 1 + crates/bitwarden-crypto/src/xchacha20.rs | 122 +++++++++++++++++++++++ crates/bitwarden-wasm-internal/build.sh | 6 +- 5 files changed, 177 insertions(+), 12 deletions(-) create mode 100644 crates/bitwarden-crypto/src/xchacha20.rs diff --git a/Cargo.lock b/Cargo.lock index 7dd4b4195..8cd5706d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", "zeroize", ] @@ -47,7 +47,7 @@ checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", - "cipher", + "cipher 0.4.4", "ctr", "ghash", "subtle", @@ -423,6 +423,8 @@ dependencies = [ "base64", "bitwarden-error", "cbc", + "chacha20 0.8.2", + "chacha20poly1305", "criterion", "generic-array", "hkdf", @@ -430,6 +432,7 @@ dependencies = [ "num-bigint", "num-traits", "pbkdf2", + "poly1305", "rand", "rand_chacha", "rayon", @@ -709,7 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", - "cipher", + "cipher 0.4.4", ] [[package]] @@ -791,7 +794,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -821,6 +824,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "zeroize", +] + [[package]] name = "chacha20" version = "0.9.1" @@ -828,10 +843,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.40" @@ -874,6 +902,15 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1177,6 +1214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -1207,7 +1245,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -2822,6 +2860,7 @@ dependencies = [ "cpufeatures", "opaque-debug", "universal-hash", + "zeroize", ] [[package]] @@ -3268,7 +3307,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -3676,8 +3715,8 @@ dependencies = [ "aes", "aes-gcm", "cbc", - "chacha20", - "cipher", + "chacha20 0.9.1", + "cipher 0.4.4", "ctr", "poly1305", "ssh-encoding", diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 7a9fb8b38..a970ad0b9 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -29,12 +29,15 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ base64 = ">=0.22.1, <0.23" bitwarden-error = { workspace = true } cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } +chacha20 = { version = ">=0.8.2, <0.9", features = ["zeroize"] } +chacha20poly1305 = { version = "0.10.1" } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } +poly1305 = { version = "0.8.0", features = ["zeroize"] } rand = ">=0.8.5, <0.9" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 25e563bd1..1e32087c4 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -91,6 +91,7 @@ pub use wordlist::EFF_LONG_WORD_LIST; mod store; pub use store::{KeyStore, KeyStoreContext}; mod traits; +mod xchacha20; pub use traits::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs new file mode 100644 index 000000000..ad03da05d --- /dev/null +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -0,0 +1,122 @@ +//! # XChaCha20Poly1305 operations +//! +//! Contains low level XChaCha20Poly1305 operations used by the rest of the crate. +//! +//! In most cases you should use the [EncString][crate::EncString] with +//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead. + +use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305}; +use generic_array::{typenum::U24, GenericArray}; + +/** + * Note: + * XChaCha20Poly1305 encrypts data, and authenticates both the cipher text and associated + * data. This does not provide key-commitment, and assumes there can only be one key. + * + * If multiple keys are possible, a key-committing cipher such as + * XChaCha20Poly1305Blake3CTX should be used: `https://github.com/bitwarden/sdk-internal/pull/41` to prevent invisible-salamander style attacks. + * `https://eprint.iacr.org/2019/016.pdf` + * `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/` + */ +use crate::CryptoError; + +#[allow(unused)] +pub(crate) fn generate_nonce() -> GenericArray { + let rng = rand::thread_rng(); + let nonce = XChaCha20Poly1305::generate_nonce(rng); + nonce +} + +#[allow(unused)] +pub(crate) fn encrypt_xchacha20_poly1305( + nonce: &[u8; 24], + key: &[u8; 32], + plaintext_secret_data: &[u8], + associated_data: &[u8], +) -> Result, CryptoError> { + let nonce = GenericArray::from_slice(nonce); + // This buffer contains the plaintext, that will be encrypted in-place + let mut buffer = Vec::from(plaintext_secret_data); + XChaCha20Poly1305::new(GenericArray::from_slice(key)) + .encrypt_in_place(&nonce, associated_data, &mut buffer) + .map_err(|_| CryptoError::InvalidKey)?; + Ok(buffer) +} + +#[allow(unused)] +pub(crate) fn decrypt_xchacha20_poly1305( + nonce: &[u8; 24], + key: &[u8; 32], + ciphertext: &[u8], + associated_data: &[u8], +) -> Result, CryptoError> { + let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(key)); + let mut buffer = ciphertext.to_vec(); + cipher + .decrypt_in_place( + GenericArray::from_slice(nonce), + associated_data, + &mut buffer, + ) + .map_err(|_| CryptoError::InvalidKey)?; + Ok(buffer) +} + +mod tests { + #[cfg(test)] + use crate::xchacha20::*; + + #[test] + fn test_encrypt_decrypt_xchacha20() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let nonce = generate_nonce().into(); + + let encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); + let decrypted = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data).unwrap(); + assert_eq!(plaintext_secret_data, decrypted.as_slice()); + } + + #[test] + fn test_fails_when_ciphertext_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let nonce = generate_nonce().into(); + + let mut encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); + encrypted[0] = encrypted[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + assert!(result.is_err()); + } + + #[test] + fn test_fails_when_associated_data_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let mut authenticated_data = b"My authenticated data".to_vec(); + let nonce = generate_nonce().into(); + + let encrypted = encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data.as_slice()).unwrap(); + authenticated_data[0] = authenticated_data[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305(&nonce, &key, encrypted.as_slice(), authenticated_data.as_slice()); + assert!(result.is_err()); + } + + #[test] + fn test_fails_when_nonce_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let mut nonce = generate_nonce().into(); + + let encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); + nonce[0] = nonce[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + assert!(result.is_err()); + } +} \ No newline at end of file diff --git a/crates/bitwarden-wasm-internal/build.sh b/crates/bitwarden-wasm-internal/build.sh index b925e058e..3dd9f0187 100755 --- a/crates/bitwarden-wasm-internal/build.sh +++ b/crates/bitwarden-wasm-internal/build.sh @@ -20,15 +20,15 @@ else fi # Build normally -cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown ${RELEASE_FLAG} +cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown ${RELEASE_FLAG} --config 'patch.crates-io.pkcs5.git="https://github.com/bitwarden/rustcrypto-formats.git"' --config 'patch.crates-io.pkcs5.rev="2b27c63034217dd126bbf5ed874da51b84f8c705"' wasm-bindgen --target bundler --out-dir crates/bitwarden-wasm-internal/npm ./target/wasm32-unknown-unknown/${BUILD_FOLDER}/bitwarden_wasm_internal.wasm wasm-bindgen --target nodejs --out-dir crates/bitwarden-wasm-internal/npm/node ./target/wasm32-unknown-unknown/${BUILD_FOLDER}/bitwarden_wasm_internal.wasm # Build with MVP CPU target, for wasm2js support # Note that this requirest build-std which is an unstable feature, -# this normally requires a nightly build, but we can also use the +# this normally requires a nightly build, but we can also use the # RUSTC_BOOTSTRAP hack to use the same stable version as the normal build -RUSTFLAGS=-Ctarget-cpu=mvp RUSTC_BOOTSTRAP=1 cargo build -p bitwarden-wasm-internal -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ${RELEASE_FLAG} +RUSTFLAGS=-Ctarget-cpu=mvp RUSTC_BOOTSTRAP=1 cargo build -p bitwarden-wasm-internal -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ${RELEASE_FLAG} --config 'patch.crates-io.pkcs5.git="https://github.com/bitwarden/rustcrypto-formats.git"' --config 'patch.crates-io.pkcs5.rev="2b27c63034217dd126bbf5ed874da51b84f8c705"' wasm-bindgen --target bundler --out-dir crates/bitwarden-wasm-internal/npm/mvp ./target/wasm32-unknown-unknown/${BUILD_FOLDER}/bitwarden_wasm_internal.wasm # Format From 0e682014480b1b2a4fc9cd499c90ee60ef89f815 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 11:50:32 +0100 Subject: [PATCH 002/152] Cargo fmt --- crates/bitwarden-crypto/src/xchacha20.rs | 29 ++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index ad03da05d..0a4369cbc 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -74,8 +74,10 @@ mod tests { let nonce = generate_nonce().into(); let encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); - let decrypted = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data).unwrap(); + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); + let decrypted = + decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data).unwrap(); assert_eq!(plaintext_secret_data, decrypted.as_slice()); } @@ -87,7 +89,8 @@ mod tests { let nonce = generate_nonce().into(); let mut encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); encrypted[0] = encrypted[0].wrapping_add(1); let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); assert!(result.is_err()); @@ -100,9 +103,20 @@ mod tests { let mut authenticated_data = b"My authenticated data".to_vec(); let nonce = generate_nonce().into(); - let encrypted = encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data.as_slice()).unwrap(); + let encrypted = encrypt_xchacha20_poly1305( + &nonce, + &key, + plaintext_secret_data, + authenticated_data.as_slice(), + ) + .unwrap(); authenticated_data[0] = authenticated_data[0].wrapping_add(1); - let result = decrypt_xchacha20_poly1305(&nonce, &key, encrypted.as_slice(), authenticated_data.as_slice()); + let result = decrypt_xchacha20_poly1305( + &nonce, + &key, + encrypted.as_slice(), + authenticated_data.as_slice(), + ); assert!(result.is_err()); } @@ -114,9 +128,10 @@ mod tests { let mut nonce = generate_nonce().into(); let encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data).unwrap(); + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); nonce[0] = nonce[0].wrapping_add(1); let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); assert!(result.is_err()); } -} \ No newline at end of file +} From a58c53f105284eac28acc9399941ba18fcf50336 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 11:59:44 +0100 Subject: [PATCH 003/152] Cleanup --- crates/bitwarden-crypto/src/xchacha20.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 0a4369cbc..a9ba8e6d3 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -22,9 +22,7 @@ use crate::CryptoError; #[allow(unused)] pub(crate) fn generate_nonce() -> GenericArray { - let rng = rand::thread_rng(); - let nonce = XChaCha20Poly1305::generate_nonce(rng); - nonce + XChaCha20Poly1305::generate_nonce(rand::thread_rng()) } #[allow(unused)] @@ -34,11 +32,14 @@ pub(crate) fn encrypt_xchacha20_poly1305( plaintext_secret_data: &[u8], associated_data: &[u8], ) -> Result, CryptoError> { - let nonce = GenericArray::from_slice(nonce); // This buffer contains the plaintext, that will be encrypted in-place let mut buffer = Vec::from(plaintext_secret_data); XChaCha20Poly1305::new(GenericArray::from_slice(key)) - .encrypt_in_place(&nonce, associated_data, &mut buffer) + .encrypt_in_place( + GenericArray::from_slice(nonce), + associated_data, + &mut buffer, + ) .map_err(|_| CryptoError::InvalidKey)?; Ok(buffer) } @@ -50,9 +51,8 @@ pub(crate) fn decrypt_xchacha20_poly1305( ciphertext: &[u8], associated_data: &[u8], ) -> Result, CryptoError> { - let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(key)); let mut buffer = ciphertext.to_vec(); - cipher + XChaCha20Poly1305::new(GenericArray::from_slice(key)) .decrypt_in_place( GenericArray::from_slice(nonce), associated_data, From ba2d199434a99d629c91e04b048614a3519bf40b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 12:08:08 +0100 Subject: [PATCH 004/152] Cleanup --- Cargo.lock | 47 ++++++++---------------------- crates/bitwarden-crypto/Cargo.toml | 1 - 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cd5706d5..32ea4c172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", "zeroize", ] @@ -47,7 +47,7 @@ checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", - "cipher 0.4.4", + "cipher", "ctr", "ghash", "subtle", @@ -423,7 +423,6 @@ dependencies = [ "base64", "bitwarden-error", "cbc", - "chacha20 0.8.2", "chacha20poly1305", "criterion", "generic-array", @@ -712,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", - "cipher 0.4.4", + "cipher", ] [[package]] @@ -794,7 +793,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -824,18 +823,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "zeroize", -] - [[package]] name = "chacha20" version = "0.9.1" @@ -843,7 +830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] @@ -854,8 +841,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20 0.9.1", - "cipher 0.4.4", + "chacha20", + "cipher", "poly1305", "zeroize", ] @@ -902,15 +889,6 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.4.4" @@ -1245,7 +1223,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -2793,8 +2771,7 @@ dependencies = [ [[package]] name = "pkcs5" version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +source = "git+https://github.com/bitwarden/rustcrypto-formats.git?rev=2b27c63034217dd126bbf5ed874da51b84f8c705#2b27c63034217dd126bbf5ed874da51b84f8c705" dependencies = [ "aes", "cbc", @@ -3307,7 +3284,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -3715,8 +3692,8 @@ dependencies = [ "aes", "aes-gcm", "cbc", - "chacha20 0.9.1", - "cipher 0.4.4", + "chacha20", + "cipher", "ctr", "poly1305", "ssh-encoding", diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index a970ad0b9..e99907116 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -29,7 +29,6 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ base64 = ">=0.22.1, <0.23" bitwarden-error = { workspace = true } cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } -chacha20 = { version = ">=0.8.2, <0.9", features = ["zeroize"] } chacha20poly1305 = { version = "0.10.1" } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" From 764725658735051614753a2ceacaf7c2ebd077cd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 16:32:54 +0100 Subject: [PATCH 005/152] Remove poly1305 dependency --- Cargo.lock | 5 ++--- crates/bitwarden-crypto/Cargo.toml | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32ea4c172..066eca1cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,7 +431,6 @@ dependencies = [ "num-bigint", "num-traits", "pbkdf2", - "poly1305", "rand", "rand_chacha", "rayon", @@ -2771,7 +2770,8 @@ dependencies = [ [[package]] name = "pkcs5" version = "0.7.1" -source = "git+https://github.com/bitwarden/rustcrypto-formats.git?rev=2b27c63034217dd126bbf5ed874da51b84f8c705#2b27c63034217dd126bbf5ed874da51b84f8c705" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" dependencies = [ "aes", "cbc", @@ -2837,7 +2837,6 @@ dependencies = [ "cpufeatures", "opaque-debug", "universal-hash", - "zeroize", ] [[package]] diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index e99907116..70baf06bb 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -36,7 +36,6 @@ hmac = ">=0.12.1, <0.13" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } -poly1305 = { version = "0.8.0", features = ["zeroize"] } rand = ">=0.8.5, <0.9" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" From 6bab0494f9f8522d13b31d0b20ead64b92031835 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 16:35:30 +0100 Subject: [PATCH 006/152] Move comment --- crates/bitwarden-crypto/src/xchacha20.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index a9ba8e6d3..27ea469a4 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -4,20 +4,20 @@ //! //! In most cases you should use the [EncString][crate::EncString] with //! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead. +//! +//! Note: +//! XChaCha20Poly1305 encrypts data, and authenticates both the cipher text and associated +//! data. This does not provide key-commitment, and assumes there can only be one key. +//! +//! If multiple keys are possible, a key-committing cipher such as +//! XChaCha20Poly1305Blake3CTX should be used: `https://github.com/bitwarden/sdk-internal/pull/41` to prevent invisible-salamander style attacks. +//! `https://eprint.iacr.org/2019/016.pdf` +//! `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/` +//! use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305}; use generic_array::{typenum::U24, GenericArray}; -/** - * Note: - * XChaCha20Poly1305 encrypts data, and authenticates both the cipher text and associated - * data. This does not provide key-commitment, and assumes there can only be one key. - * - * If multiple keys are possible, a key-committing cipher such as - * XChaCha20Poly1305Blake3CTX should be used: `https://github.com/bitwarden/sdk-internal/pull/41` to prevent invisible-salamander style attacks. - * `https://eprint.iacr.org/2019/016.pdf` - * `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/` - */ use crate::CryptoError; #[allow(unused)] From 6b241ece78bb39fb13623a72109ca1712372a82f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 17:17:39 +0100 Subject: [PATCH 007/152] Adjust interface according to feedback --- crates/bitwarden-crypto/src/xchacha20.rs | 99 ++++++++++++++---------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 27ea469a4..6deb7e60d 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -16,32 +16,44 @@ //! use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305}; -use generic_array::{typenum::U24, GenericArray}; +use generic_array::GenericArray; +use rand::{CryptoRng, RngCore}; use crate::CryptoError; #[allow(unused)] -pub(crate) fn generate_nonce() -> GenericArray { - XChaCha20Poly1305::generate_nonce(rand::thread_rng()) +pub(crate) struct XChaCha20Poly1305Ciphertext { + nonce: GenericArray::NonceSize>, + ciphertext: Vec, } #[allow(unused)] -pub(crate) fn encrypt_xchacha20_poly1305( - nonce: &[u8; 24], +fn encrypt_xchacha20_poly1305( key: &[u8; 32], plaintext_secret_data: &[u8], associated_data: &[u8], -) -> Result, CryptoError> { +) -> XChaCha20Poly1305Ciphertext { + let mut rng = rand::thread_rng(); + encrypt_xchacha20_poly1305_internal(rng, key, plaintext_secret_data, associated_data) +} + +fn encrypt_xchacha20_poly1305_internal( + rng: impl RngCore + CryptoRng, + key: &[u8; 32], + plaintext_secret_data: &[u8], + associated_data: &[u8], +) -> XChaCha20Poly1305Ciphertext { + let nonce = &XChaCha20Poly1305::generate_nonce(rng); // This buffer contains the plaintext, that will be encrypted in-place - let mut buffer = Vec::from(plaintext_secret_data); + let mut buffer = plaintext_secret_data.to_vec(); XChaCha20Poly1305::new(GenericArray::from_slice(key)) - .encrypt_in_place( - GenericArray::from_slice(nonce), - associated_data, - &mut buffer, - ) - .map_err(|_| CryptoError::InvalidKey)?; - Ok(buffer) + .encrypt_in_place(&nonce, associated_data, &mut buffer) + .expect("encryption failed"); + + XChaCha20Poly1305Ciphertext { + nonce: *nonce, + ciphertext: buffer, + } } #[allow(unused)] @@ -58,7 +70,7 @@ pub(crate) fn decrypt_xchacha20_poly1305( associated_data, &mut buffer, ) - .map_err(|_| CryptoError::InvalidKey)?; + .map_err(|_| CryptoError::KeyDecrypt)?; Ok(buffer) } @@ -71,13 +83,14 @@ mod tests { let key = [0u8; 32]; let plaintext_secret_data = b"My secret data"; let authenticated_data = b"My authenticated data"; - let nonce = generate_nonce().into(); - - let encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) - .unwrap(); - let decrypted = - decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data).unwrap(); + let encrypted = encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data); + let decrypted = decrypt_xchacha20_poly1305( + &encrypted.nonce.into(), + &key, + &encrypted.ciphertext, + authenticated_data, + ) + .unwrap(); assert_eq!(plaintext_secret_data, decrypted.as_slice()); } @@ -86,13 +99,16 @@ mod tests { let key = [0u8; 32]; let plaintext_secret_data = b"My secret data"; let authenticated_data = b"My authenticated data"; - let nonce = generate_nonce().into(); let mut encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) - .unwrap(); - encrypted[0] = encrypted[0].wrapping_add(1); - let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data); + encrypted.ciphertext[0] = encrypted.ciphertext[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305( + &encrypted.nonce.into(), + &key, + &encrypted.ciphertext, + authenticated_data, + ); assert!(result.is_err()); } @@ -101,20 +117,14 @@ mod tests { let key = [0u8; 32]; let plaintext_secret_data = b"My secret data"; let mut authenticated_data = b"My authenticated data".to_vec(); - let nonce = generate_nonce().into(); - let encrypted = encrypt_xchacha20_poly1305( - &nonce, - &key, - plaintext_secret_data, - authenticated_data.as_slice(), - ) - .unwrap(); + let encrypted = + encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data.as_slice()); authenticated_data[0] = authenticated_data[0].wrapping_add(1); let result = decrypt_xchacha20_poly1305( - &nonce, + &encrypted.nonce.into(), &key, - encrypted.as_slice(), + &encrypted.ciphertext, authenticated_data.as_slice(), ); assert!(result.is_err()); @@ -125,13 +135,16 @@ mod tests { let key = [0u8; 32]; let plaintext_secret_data = b"My secret data"; let authenticated_data = b"My authenticated data"; - let mut nonce = generate_nonce().into(); - let encrypted = - encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) - .unwrap(); - nonce[0] = nonce[0].wrapping_add(1); - let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + let mut encrypted = + encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data); + encrypted.nonce[0] = encrypted.nonce[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305( + &encrypted.nonce.into(), + &key, + &encrypted.ciphertext, + authenticated_data, + ); assert!(result.is_err()); } } From ced7213aa0a3b4cc70bd4ffc58f46796e67ba5aa Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 17:19:24 +0100 Subject: [PATCH 008/152] Remove comment --- crates/bitwarden-crypto/src/xchacha20.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 6deb7e60d..b1ab619b2 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -13,7 +13,6 @@ //! XChaCha20Poly1305Blake3CTX should be used: `https://github.com/bitwarden/sdk-internal/pull/41` to prevent invisible-salamander style attacks. //! `https://eprint.iacr.org/2019/016.pdf` //! `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/` -//! use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305}; use generic_array::GenericArray; From 44e5ffde85421c32fc71ede37210162dcf7f917e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 17:22:06 +0100 Subject: [PATCH 009/152] Fix clippy warning --- crates/bitwarden-crypto/src/xchacha20.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index b1ab619b2..5e832ff65 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -46,7 +46,7 @@ fn encrypt_xchacha20_poly1305_internal( // This buffer contains the plaintext, that will be encrypted in-place let mut buffer = plaintext_secret_data.to_vec(); XChaCha20Poly1305::new(GenericArray::from_slice(key)) - .encrypt_in_place(&nonce, associated_data, &mut buffer) + .encrypt_in_place(nonce, associated_data, &mut buffer) .expect("encryption failed"); XChaCha20Poly1305Ciphertext { From 77e0252afaa7754f3fa61f5679e75be06f87f5fc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 24 Mar 2025 15:49:15 +0100 Subject: [PATCH 010/152] tmp --- Cargo.lock | 65 ++++ .../bitwarden-core/src/auth/auth_request.rs | 8 +- .../src/auth/password/validate.rs | 2 +- crates/bitwarden-core/src/auth/pin.rs | 2 +- crates/bitwarden-core/src/auth/tde.rs | 10 +- crates/bitwarden-core/src/mobile/crypto.rs | 4 +- crates/bitwarden-crypto/Cargo.toml | 6 + crates/bitwarden-crypto/src/cose.rs | 8 + .../src/enc_string/symmetric.rs | 148 ++++++++- crates/bitwarden-crypto/src/error.rs | 13 + .../bitwarden-crypto/src/keys/device_key.rs | 4 +- crates/bitwarden-crypto/src/keys/key_hash.rs | 33 ++ .../bitwarden-crypto/src/keys/master_key.rs | 20 +- crates/bitwarden-crypto/src/keys/mod.rs | 5 +- crates/bitwarden-crypto/src/keys/pin_key.rs | 12 +- .../src/keys/symmetric_crypto_key.rs | 295 ++++++++++++++++-- crates/bitwarden-crypto/src/lib.rs | 4 +- crates/bitwarden-crypto/src/rsa.rs | 3 + .../src/store/backend/implementation/mod.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 49 ++- crates/bitwarden-crypto/src/store/mod.rs | 5 +- .../src/traits/encryptable.rs | 2 +- crates/bitwarden-crypto/src/xchacha20.rs | 134 ++++++++ crates/bitwarden-exporters/src/models.rs | 4 +- crates/bitwarden-vault/src/cipher/cipher.rs | 36 ++- crates/bitwarden-wasm-internal/build.sh | 2 +- .../src/pure_crypto.rs | 42 ++- crates/memory-testing/src/main.rs | 2 +- 28 files changed, 805 insertions(+), 115 deletions(-) create mode 100644 crates/bitwarden-crypto/src/cose.rs create mode 100644 crates/bitwarden-crypto/src/keys/key_hash.rs create mode 100644 crates/bitwarden-crypto/src/xchacha20.rs diff --git a/Cargo.lock b/Cargo.lock index 7dd4b4195..deaf455ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "zeroize", +] + [[package]] name = "askama" version = "0.12.1" @@ -422,7 +437,11 @@ dependencies = [ "argon2", "base64", "bitwarden-error", + "blake3", "cbc", + "chacha20poly1305", + "ciborium", + "coset", "criterion", "generic-array", "hkdf", @@ -430,12 +449,14 @@ dependencies = [ "num-bigint", "num-traits", "pbkdf2", + "poly1305", "rand", "rand_chacha", "rayon", "rsa", "schemars", "serde", + "serde_bytes", "serde_json", "sha1", "sha2", @@ -684,6 +705,20 @@ dependencies = [ "digest", ] +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "zeroize", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -832,6 +867,19 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.40" @@ -1006,6 +1054,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.10.0" @@ -1177,6 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -2822,6 +2877,7 @@ dependencies = [ "cpufeatures", "opaque-debug", "universal-hash", + "zeroize", ] [[package]] @@ -3419,6 +3475,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.218" diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 3f1d30e10..2f4711661 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -102,7 +102,7 @@ pub(crate) fn approve_auth_request( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( - &key.to_vec(), + &key.to_encoded(), &public_key, )?) } @@ -125,7 +125,7 @@ fn test_auth_request() { let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); - assert_eq!(&decrypted.to_vec(), secret); + assert_eq!(&decrypted.to_encoded(), secret); } #[cfg(test)] @@ -178,7 +178,7 @@ mod tests { let dec = auth_request_decrypt_user_key(private_key.to_owned(), enc_user_key).unwrap(); assert_eq!( - &dec.to_vec(), + &dec.to_encoded(), &[ 201, 37, 234, 213, 21, 75, 40, 70, 149, 213, 234, 16, 19, 251, 162, 245, 161, 74, 34, 245, 211, 151, 211, 192, 95, 10, 117, 50, 88, 223, 23, 157 @@ -197,7 +197,7 @@ mod tests { .unwrap(); assert_eq!( - &dec.to_vec(), + &dec.to_encoded(), &[ 109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212, 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, diff --git a/crates/bitwarden-core/src/auth/password/validate.rs b/crates/bitwarden-core/src/auth/password/validate.rs index 8d7b7f520..39abee276 100644 --- a/crates/bitwarden-core/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -65,7 +65,7 @@ pub(crate) fn validate_password_user_key( #[allow(deprecated)] let existing_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; - if user_key.to_vec() != existing_key.to_vec() { + if user_key != *existing_key { return Err(AuthValidateError::WrongUserKey); } diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs index e163e50d7..93e172f25 100644 --- a/crates/bitwarden-core/src/auth/pin.rs +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -37,7 +37,7 @@ pub(crate) fn validate_pin( return Ok(false); }; - Ok(user_key.to_vec() == decrypted_key.to_vec()) + Ok(*user_key == decrypted_key) } } } diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index 94b916457..3f641f531 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -17,13 +17,13 @@ pub(super) fn make_register_tde_keys( ) -> Result { let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(org_public_key)?)?; - let mut rng = rand::thread_rng(); - - let user_key = UserKey::new(SymmetricCryptoKey::generate(&mut rng)); + let user_key = UserKey::new(SymmetricCryptoKey::generate()); let key_pair = user_key.make_key_pair()?; - let admin_reset = - AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&user_key.0.to_vec(), &public_key)?; + let admin_reset = AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + &user_key.0.to_encoded(), + &public_key, + )?; let device_key = if remember_device { Some(DeviceKey::trust_device(&user_key.0)?) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 7992ce95d..786349340 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -375,7 +375,7 @@ pub(super) fn enroll_admin_password_reset( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( - &key.to_vec(), + &key.to_encoded(), &public_key, )?) } @@ -746,7 +746,7 @@ mod tests { .dangerous_get_symmetric_key(SymmetricKeyId::User) .unwrap(); - assert_eq!(&decrypted, &expected.to_vec()); + assert_eq!(&decrypted, &expected.to_encoded()) } #[test] diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 7a9fb8b38..21589f379 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -28,18 +28,24 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ ], default-features = false } base64 = ">=0.22.1, <0.23" bitwarden-error = { workspace = true } +blake3 = { version = "1.5.5", features = ["zeroize"] } cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } +chacha20poly1305 = { version = "0.10.1" } +ciborium = "0.2.2" +coset = "0.3.8" generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } +poly1305 = { version = "0.8.0", features = ["zeroize"] } rand = ">=0.8.5, <0.9" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" schemars = { workspace = true } serde = { workspace = true } +serde_bytes = "0.11.15" sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" subtle = ">=2.5.0, <3.0" diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs new file mode 100644 index 000000000..f6255fb89 --- /dev/null +++ b/crates/bitwarden-crypto/src/cose.rs @@ -0,0 +1,8 @@ +/** + * This file contains private-use constants for COSE encoded key types and algorithms. + */ +use coset::iana; + +pub(crate) const XCHACHA20_POLY1305: i64 = -70000; + +pub(crate) const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; \ No newline at end of file diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index b7d79757a..8f7e9990d 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -1,12 +1,12 @@ use std::{fmt::Display, str::FromStr}; use base64::{engine::general_purpose::STANDARD, Engine}; +use coset::{iana::CoapContentFormat, CborSerializable}; use serde::Deserialize; use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ - error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, - Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + cose, error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key }; #[cfg(feature = "wasm")] @@ -59,6 +59,10 @@ pub enum EncString { mac: [u8; 32], data: Vec, }, + // 7 The actual enc type is contained in the cose struct + COSE_B64 { + data: Vec, + }, } /// To avoid printing sensitive information, [EncString] debug prints to `EncString`. @@ -88,7 +92,11 @@ impl FromStr for EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } + ("7", 1) => { + let buffer = from_b64_vec(parts[0])?; + Ok(EncString::COSE_B64 { data: buffer }) + } (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), parts, @@ -126,6 +134,9 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } + 7 => { + Ok(EncString::COSE_B64 { data: buf[1..].to_vec() }) + } _ => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), parts: 1, @@ -151,6 +162,13 @@ impl EncString { buf.extend_from_slice(mac); buf.extend_from_slice(data); } + EncString::COSE_B64 { + data, + } => { + buf = Vec::with_capacity(1 + data.len()); + buf.push(self.enc_type()); + buf.extend_from_slice(&data); + } } Ok(buf) @@ -159,16 +177,29 @@ impl EncString { impl Display for EncString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let parts: Vec<&[u8]> = match self { - EncString::AesCbc256_B64 { iv, data } => vec![iv, data], - EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], - }; + match self { + EncString::AesCbc256_B64 { .. } | EncString::AesCbc256_HmacSha256_B64 { .. } => { + let parts: Vec<&[u8]> = match self { + EncString::AesCbc256_B64 { iv, data } => vec![iv, data], + EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], + _ => unreachable!(), + }; + + let encoded_parts: Vec = + parts.iter().map(|part| STANDARD.encode(part)).collect(); - let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); + write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; - write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; + Ok(()) + } + EncString::COSE_B64 { + data, + } => { + write!(f, "{}.{}", self.enc_type(), STANDARD.encode(&data))?; - Ok(()) + Ok(()) + } + } } } @@ -191,6 +222,8 @@ impl serde::Serialize for EncString { } impl EncString { + const XCHACHA20_PAD_BLOCK_SIZE: usize = 24; + pub(crate) fn encrypt_aes256_hmac( data_dec: &[u8], key: &Aes256CbcHmacKey, @@ -200,11 +233,48 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } + pub(crate) fn encrypt_xchacha20_poly1305( + data_dec: &[u8], + key: &XChaCha20Poly1305Key, + content_format: CoapContentFormat, + ) -> Result { + + let mut protected_header = coset::HeaderBuilder::new() + .content_format(content_format) + .build(); + protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305)); + + let mut unprotected_header = coset::HeaderBuilder::new() + .build(); + let nonce = crate::xchacha20::generate_nonce(); + unprotected_header.iv = nonce.to_vec(); + let cose = coset::CoseEncrypt0Builder::new() + .try_create_ciphertext(data_dec, &[], |data, aad| { + let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305( + nonce.as_slice().try_into().map_err(|_| CryptoError::InvalidKey)?, + key.enc_key + .as_slice() + .try_into() + .expect("XChaChaPoly1305 key is 32 bytes long"), + data, + aad, + )?; + Ok(ciphertext) + }).map_err(|_a: CryptoError| CryptoError::EncodingError)?; + let cose = cose.unprotected(unprotected_header) + .build(); + + Ok(EncString::COSE_B64 { + data: cose.to_vec().map_err(|_| CryptoError::EncodingError)?, + }) + } + /// The numerical representation of the encryption type of the [EncString]. const fn enc_type(&self) -> u8 { match self { EncString::AesCbc256_B64 { .. } => 0, EncString::AesCbc256_HmacSha256_B64 { .. } => 2, + EncString::COSE_B64 { .. } => 7, } } } @@ -213,6 +283,10 @@ impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { match key { SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key), + SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => { + let padded_data = pad_bytes(self, EncString::XCHACHA20_PAD_BLOCK_SIZE); + EncString::encrypt_xchacha20_poly1305(&padded_data, inner_key, CoapContentFormat::OctetStream) + } SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, )), @@ -230,6 +304,31 @@ impl KeyDecryptable> for EncString { EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }, SymmetricCryptoKey::Aes256CbcHmacKey(key), ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), + ( + EncString::COSE_B64 { + data, + }, + SymmetricCryptoKey::XChaCha20Poly1305Key(key), + ) => { + // parse cose + let msg = coset::CoseEncrypt0::from_slice(data.as_slice()).map_err(|_| { + CryptoError::EncString(EncStringParseError::InvalidEncoding) + })?; + let decrypted_message = msg.decrypt(&[], |data, aad| { + let nonce = msg.protected.header.iv.as_slice(); + crate::xchacha20::decrypt_xchacha20_poly1305( + nonce.try_into().map_err(|_| CryptoError::EncodingError)?, + key.enc_key + .as_slice() + .try_into() + .expect("XChaChaPoly1305 key is 32 bytes long"), + data, + aad + ).map_err(|_| CryptoError::EncodingError) + } + ).map_err(|_| CryptoError::EncodingError)?; + Ok(unpad_bytes(&decrypted_message)?.to_vec()) + } _ => Err(CryptoError::WrongKeyType), } } @@ -266,6 +365,33 @@ impl schemars::JsonSchema for EncString { } } +// Pads the bytes to the next block size +// The format is as follows: +// N|0x00..0x00|data +// ^ N null bytes +fn pad_bytes(bytes: &[u8], block_size: usize) -> Vec { + let padding_len = block_size - (bytes.len() % block_size); + let mut padded = vec![0; 1 + padding_len + bytes.len()]; + padded[0] = padding_len as u8; + padded[1..=padding_len].fill(0); + padded[1 + padding_len..].copy_from_slice(bytes); + padded +} + +// Unpads the bytes +fn unpad_bytes(bytes: &[u8]) -> Result<&[u8]> { + if bytes.is_empty() { + return Err(CryptoError::EncodingError); + } + + let padding_len = bytes[0] as usize; + if (padding_len + 1) >= bytes.len() { + return Err(CryptoError::EncodingError); + } + + Ok(&bytes[1 + padding_len..]) +} + #[cfg(test)] mod tests { use schemars::schema_for; @@ -388,13 +514,13 @@ mod tests { #[test] fn test_from_str_invalid() { - let enc_str = "7.ABC"; + let enc_str = "8.ABC"; let enc_string: Result = enc_str.parse(); let err = enc_string.unwrap_err(); assert_eq!( err.to_string(), - "EncString error, Invalid symmetric type, got type 7 with 1 parts" + "EncString error, Invalid symmetric type, got type 8 with 1 parts" ); } diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 4602ad9bc..11a565f1e 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -48,11 +48,20 @@ pub enum CryptoError { #[error("Number is zero")] ZeroNumber, + #[error("Invalid key hash algorithm")] + InvalidHashAlgorithm, + + #[error("Error parsing key hash")] + HashParseError, + #[error("Unsupported operation, {0}")] OperationNotSupported(UnsupportedOperation), #[error("Key algorithm does not match encrypted data type")] WrongKeyType, + + #[error("Encoding error")] + EncodingError, } #[derive(Debug, Error)] @@ -73,6 +82,10 @@ pub enum EncStringParseError { InvalidBase64(#[from] base64::DecodeError), #[error("Invalid length: expected {expected}, got {got}")] InvalidLength { expected: usize, got: usize }, + #[error("Invalid additional data")] + InvalidAdditionalData, + #[error("Invalid encoding")] + InvalidEncoding, } #[derive(Debug, Error)] diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index 11ae078a4..ed39a839f 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -30,12 +30,12 @@ impl DeviceKey { /// from EncSettings. pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result { let mut rng = rand::thread_rng(); - let device_key = DeviceKey(SymmetricCryptoKey::generate(&mut rng)); + let device_key = DeviceKey(SymmetricCryptoKey::generate()); let device_private_key = AsymmetricCryptoKey::generate(&mut rng); // Encrypt both the key and mac_key of the user key - let data = user_key.to_vec(); + let data = user_key.to_encoded(); let protected_user_key = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&data, &device_private_key)?; diff --git a/crates/bitwarden-crypto/src/keys/key_hash.rs b/crates/bitwarden-crypto/src/keys/key_hash.rs new file mode 100644 index 000000000..e47acb4fe --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/key_hash.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub(crate) enum KeyHashAlgorithm { + #[serde(rename = "b3")] + Blake3, +} + +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub(crate) struct KeyHash { + #[serde(with = "serde_bytes", rename = "h")] + pub(crate) hash: Vec, + #[serde(rename = "alg")] + pub(crate) algorithm: KeyHashAlgorithm, +} + +pub(crate) trait KeyHashable { + fn hash(&self) -> KeyHash; +} + +impl KeyHashable for T { + fn hash(&self) -> KeyHash { + let hash: [u8; 32] = blake3::hash(&self.hash_data()).into(); + KeyHash { + hash: hash.to_vec(), + algorithm: KeyHashAlgorithm::Blake3, + } + } +} + +pub(crate) trait KeyHashData { + fn hash_data(&self) -> Vec; +} diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 3f287e9dc..cbcc25f67 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -60,7 +60,7 @@ impl MasterKey { /// Derive the master key hash, used for local and remote password validation. pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> Result { - let hash = util::pbkdf2(self.inner_bytes(), password, purpose as u32); + let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32); Ok(STANDARD.encode(hash)) } @@ -113,7 +113,7 @@ pub(super) fn encrypt_user_key( user_key: &SymmetricCryptoKey, ) -> Result { let stretched_master_key = stretch_key(master_key)?; - let user_key_bytes = Zeroizing::new(user_key.to_vec()); + let user_key_bytes = Zeroizing::new(user_key.to_encoded()); EncString::encrypt_aes256_hmac(&user_key_bytes, &stretched_master_key) } @@ -132,10 +132,17 @@ pub(super) fn decrypt_user_key( }); user_key.decrypt_with_key(&legacy_key)? } - _ => { + EncString::AesCbc256_HmacSha256_B64 { .. } => { let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.decrypt_with_key(&stretched_key)? } + EncString::COSE_B64 { .. } => { + let key = SymmetricCryptoKey::XChaCha20Poly1305Key(super::XChaCha20Poly1305Key { + enc_key: Box::pin(GenericArray::clone_from_slice(key)), + }); + + user_key.decrypt_with_key(&key)? + } }; SymmetricCryptoKey::try_from(dec.as_mut_slice()) @@ -146,7 +153,7 @@ fn make_user_key( mut rng: impl rand::RngCore, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { - let user_key = SymmetricCryptoKey::generate(&mut rng); + let user_key = SymmetricCryptoKey::generate(); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) } @@ -221,7 +228,8 @@ mod tests { 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, ] .into(), - )) + ) + ) .into(); let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap(); @@ -255,7 +263,7 @@ mod tests { #[test] fn test_make_user_key2() { - let kdf_material = KdfDerivedKeyMaterial((derive_symmetric_key("test1")).enc_key.clone()); + let kdf_material = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone()); let master_key = MasterKey::KdfKey(kdf_material); let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2")); diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index e7d3ff047..658aa9daa 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -7,7 +7,9 @@ pub use shareable_key::derive_shareable_key; mod symmetric_crypto_key; #[cfg(test)] pub use symmetric_crypto_key::derive_symmetric_key; -pub use symmetric_crypto_key::{Aes256CbcHmacKey, Aes256CbcKey, SymmetricCryptoKey}; +pub use symmetric_crypto_key::{ + Aes256CbcHmacKey, Aes256CbcKey, SymmetricCryptoKey, XChaCha20Poly1305Key, +}; mod asymmetric_crypto_key; pub use asymmetric_crypto_key::{ AsymmetricCryptoKey, AsymmetricEncryptable, AsymmetricPublicCryptoKey, @@ -19,6 +21,7 @@ pub use device_key::{DeviceKey, TrustDeviceResponse}; mod pin_key; pub use pin_key::PinKey; mod kdf; +pub(crate) mod key_hash; pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 0cced165a..0a0e1bbdd 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -1,10 +1,10 @@ use super::{ kdf::{Kdf, KdfDerivedKeyMaterial}, master_key::{decrypt_user_key, encrypt_user_key}, + utils::stretch_key, }; use crate::{ - keys::{key_encryptable::CryptoKey, utils::stretch_key}, - EncString, KeyEncryptable, Result, SymmetricCryptoKey, + keys::key_encryptable::CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey, }; /// Pin Key. @@ -19,13 +19,15 @@ impl PinKey { } /// Encrypt the users user key + /// + /// @param user_key: The user key to encrypt pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { - encrypt_user_key(&self.0 .0, user_key) + encrypt_user_key(&self.0.0, user_key) } /// Decrypt the users user key pub fn decrypt_user_key(&self, user_key: EncString) -> Result { - decrypt_user_key(&self.0 .0, user_key) + decrypt_user_key(&self.0.0, user_key) } } @@ -33,7 +35,7 @@ impl CryptoKey for PinKey {} impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &PinKey) -> Result { - let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&key.0 .0)?); + let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&key.0.0)?); self.encrypt_with_key(&stretched_key) } } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 8908a2d75..71f32150f 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -2,27 +2,39 @@ use std::pin::Pin; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; +use coset::{iana, CborSerializable, Label, RegisteredLabelWithPrivate}; use generic_array::GenericArray; use rand::Rng; +use subtle::{Choice, ConstantTimeEq}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::key_encryptable::CryptoKey; -use crate::CryptoError; +use super::{key_encryptable::CryptoKey, key_hash::KeyHashData}; +use crate::{cose, CryptoError}; /// Aes256CbcKey is a symmetric encryption key, consisting of one 256-bit key, /// used to decrypt legacy type 0 encstrings. The data is not autenticated /// so this should be used with caution, and removed where possible. #[derive(ZeroizeOnDrop, Clone)] -#[cfg_attr(test, derive(Debug, PartialEq))] pub struct Aes256CbcKey { /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data] pub(crate) enc_key: Pin>>, } +impl ConstantTimeEq for Aes256CbcKey { + fn ct_eq(&self, other: &Self) -> Choice { + self.enc_key.ct_eq(&other.enc_key) + } +} + +impl PartialEq for Aes256CbcKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + /// Aes256CbcHmacKey is a symmetric encryption key consisting /// of two 256-bit keys, one for encryption and one for MAC #[derive(ZeroizeOnDrop, Clone)] -#[cfg_attr(test, derive(Debug, PartialEq))] pub struct Aes256CbcHmacKey { /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data] pub(crate) enc_key: Pin>>, @@ -30,54 +42,161 @@ pub struct Aes256CbcHmacKey { pub(crate) mac_key: Pin>>, } +impl ConstantTimeEq for Aes256CbcHmacKey { + fn ct_eq(&self, other: &Self) -> Choice { + self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key) + } +} + +impl PartialEq for Aes256CbcHmacKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +#[derive(Zeroize, Clone)] +#[cfg_attr(test, derive(Debug))] +pub struct XChaCha20Poly1305Key { + pub(crate) enc_key: Pin>>, +} + +impl ConstantTimeEq for XChaCha20Poly1305Key { + fn ct_eq(&self, other: &Self) -> Choice { + self.enc_key.ct_eq(&other.enc_key) + } +} + +impl PartialEq for XChaCha20Poly1305Key { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + /// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) #[derive(ZeroizeOnDrop, Clone)] -#[cfg_attr(test, derive(PartialEq))] pub enum SymmetricCryptoKey { Aes256CbcKey(Aes256CbcKey), Aes256CbcHmacKey(Aes256CbcHmacKey), + // always encode with cose + XChaCha20Poly1305Key(XChaCha20Poly1305Key), +} + +impl KeyHashData for SymmetricCryptoKey { + fn hash_data(&self) -> Vec { + match &self { + SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), + SymmetricCryptoKey::Aes256CbcHmacKey(key) => { + let mut buf = Vec::with_capacity(64); + buf.extend_from_slice(&key.enc_key); + buf.extend_from_slice(&key.mac_key); + buf + } + SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.enc_key.to_vec(), + } + } } impl SymmetricCryptoKey { - const KEY_LEN: usize = 32; - const MAC_LEN: usize = 32; + // enc type 0 old static format + const AES256_CBC_KEY_LEN: usize = 32; + // enc type 2 old static format + const AES256_CBC_HMAC_KEY_LEN: usize = 64; + + pub fn generate() -> Self { + let mut rng = rand::thread_rng(); + Self::generate_internal(&mut rng, false) + } + + pub fn generate_cose() -> Self { + let mut rng = rand::thread_rng(); + Self::generate_internal(&mut rng, true) + } /// Generate a new random [SymmetricCryptoKey] - pub fn generate(mut rng: impl rand::RngCore) -> Self { - let mut enc_key = Box::pin(GenericArray::::default()); - let mut mac_key = Box::pin(GenericArray::::default()); + /// @param rng: A random number generator + fn generate_internal(mut rng: impl rand::RngCore, cose: bool) -> Self { + if !cose { + let mut enc_key = Box::pin(GenericArray::::default()); + let mut mac_key = Box::pin(GenericArray::::default()); - rng.fill(enc_key.as_mut_slice()); - rng.fill(mac_key.as_mut_slice()); + rng.fill(enc_key.as_mut_slice()); + rng.fill(mac_key.as_mut_slice()); - SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) + SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) + } else { + let mut enc_key = Box::pin(GenericArray::::default()); + rng.fill(enc_key.as_mut_slice()); + SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key }) + } } - fn total_len(&self) -> usize { + /** + * Encodes the key to a byte array representation. This can be used for storage and transmission + * in the old byte array format. When the wrapping key is a COSE key, then COSE MUST be used to encode + * the key. + */ + pub fn to_encoded(&self) -> Vec { + let encoded_key = self.to_encoded_raw(); match self { - SymmetricCryptoKey::Aes256CbcKey(_) => 32, - SymmetricCryptoKey::Aes256CbcHmacKey(_) => 64, + SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { + encoded_key + } + SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { + let padded_key = pad_key(&encoded_key, Self::AES256_CBC_HMAC_KEY_LEN + 1); + padded_key + } } } - pub fn to_base64(&self) -> String { - STANDARD.encode(self.to_vec()) - } - - pub fn to_vec(&self) -> Vec { - let mut buf = Vec::with_capacity(self.total_len()); - + /** + * + */ + pub(crate) fn to_encoded_raw(&self) -> Vec { match self { - SymmetricCryptoKey::Aes256CbcKey(key) => { - buf.extend_from_slice(&key.enc_key); - } + SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), SymmetricCryptoKey::Aes256CbcHmacKey(key) => { + let mut buf = Vec::with_capacity(64); buf.extend_from_slice(&key.enc_key); buf.extend_from_slice(&key.mac_key); + buf + } + SymmetricCryptoKey::XChaCha20Poly1305Key(key) => { + let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec()); + let mut cose_key = builder + .add_key_op(iana::KeyOperation::Decrypt) + .add_key_op(iana::KeyOperation::Encrypt) + .add_key_op(iana::KeyOperation::WrapKey) + .add_key_op(iana::KeyOperation::UnwrapKey) + .build(); + cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305)); + let encoded_key = cose_key.to_vec().map_err(|_| CryptoError::InvalidKey).unwrap(); + encoded_key } } + } + + pub fn to_base64(&self) -> String { + STANDARD.encode(self.to_encoded()) + } +} + +impl ConstantTimeEq for SymmetricCryptoKey { + fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice { + match (self, other) { + (SymmetricCryptoKey::Aes256CbcKey(a), SymmetricCryptoKey::Aes256CbcKey(b)) => { + a.ct_eq(b) + } + (SymmetricCryptoKey::Aes256CbcHmacKey(a), SymmetricCryptoKey::Aes256CbcHmacKey(b)) => { + a.ct_eq(b) + } + _ => Choice::from(0), + } + } +} - buf +impl PartialEq for SymmetricCryptoKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() } } @@ -106,23 +225,28 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { /// Note: This function takes the byte slice by mutable reference and will zero out all /// the data in it. This is to prevent the key from being left in memory. fn try_from(value: &mut [u8]) -> Result { - let result = if value.len() == Self::KEY_LEN + Self::MAC_LEN { + let result = if value.len() == Self::AES256_CBC_HMAC_KEY_LEN { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); - enc_key.copy_from_slice(&value[..Self::KEY_LEN]); - mac_key.copy_from_slice(&value[Self::KEY_LEN..]); + enc_key.copy_from_slice(&value[..32]); + mac_key.copy_from_slice(&value[32..]); Ok(SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key, })) - } else if value.len() == Self::KEY_LEN { + } else if value.len() == Self::AES256_CBC_KEY_LEN { let mut enc_key = Box::pin(GenericArray::::default()); - enc_key.copy_from_slice(&value[..Self::KEY_LEN]); + enc_key.copy_from_slice(&value[..Self::AES256_CBC_KEY_LEN]); Ok(SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey { enc_key })) + } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN { + let unpadded_value = zeroize::Zeroizing::new(unpad_key(value)); + let cose_key = coset::CoseKey::from_slice(&unpadded_value.as_slice()) + .map_err(|_| CryptoError::InvalidKey)?; + parse_cose_key(&cose_key) } else { Err(CryptoError::InvalidKeyLen) }; @@ -132,6 +256,29 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { } } +fn parse_cose_key(cose_key: &coset::CoseKey) -> Result { + let key_bytes = cose_key.params.iter().find_map(|(label, value)| { + if let (Label::Int(cose::SYMMETRIC_KEY), ciborium::Value::Bytes(bytes)) = (label, value) { + Some(bytes) + } else { + None + } + }).ok_or(CryptoError::InvalidKey)?; + + match cose_key.alg.clone().ok_or(CryptoError::InvalidKey)? { + coset::RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305) => { + if key_bytes.len() == 32 { + let mut enc_key = Box::pin(GenericArray::::default()); + enc_key.copy_from_slice(key_bytes); + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key })) + } else { + Err(CryptoError::InvalidKey) + } + } + _ => Err(CryptoError::InvalidKey), + } +} + impl CryptoKey for SymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data @@ -141,6 +288,25 @@ impl std::fmt::Debug for SymmetricCryptoKey { } } +/// Pad a key to a minimum length; +/// The first byte describes the number (N) of subsequently following null bytes +/// Next, there are N null bytes +/// Finally, the key bytes are appended +fn pad_key(key_bytes: &[u8], min_length: usize) -> Vec { + let null_bytes = min_length.saturating_sub(1).saturating_sub(key_bytes.len()); + let mut padded_key = Vec::with_capacity(min_length); + padded_key.extend_from_slice(&[null_bytes as u8]); + padded_key.extend_from_slice(vec![0u8; null_bytes].as_slice()); + padded_key.extend_from_slice(key_bytes); + padded_key +} + +// Unpad a key that was padded with pad_key +fn unpad_key(key_bytes: &[u8]) -> Vec { + let null_bytes = key_bytes[0] as usize; + key_bytes[1 + null_bytes..].to_vec() +} + #[cfg(test)] pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey { use zeroize::Zeroizing; @@ -153,12 +319,15 @@ pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey { #[cfg(test)] mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + use super::{derive_symmetric_key, SymmetricCryptoKey}; + use crate::keys::symmetric_crypto_key::{pad_key, unpad_key}; #[test] fn test_symmetric_crypto_key() { let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test")); - let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap(); + let key2 = SymmetricCryptoKey::try_from(key.to_base64().unwrap()).unwrap(); assert_eq!(key, key2); @@ -166,4 +335,64 @@ mod tests { let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap(); assert_eq!(key, key2.to_base64()); } + + #[test] + fn test_encode_decode_new_symmetric_crypto_key() { + let key = SymmetricCryptoKey::generate_internal(rand::thread_rng(), false); + let encoded = key.to_encoded(); + let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn test_decode_new_symmetric_crypto_key() { + let key_b64 = STANDARD.decode("AKNjYWxnZ0EyNTZDLUhiZWtYIAtsdVJIYcRrWMrV7M9RNH9pL0SWF8T9XwwJethAjVMJYmFrWCAnEUA5iKocRCHoq7rU3Tm7TbLWC+JXp1ywMCLjtLJvcw==").unwrap(); + let key = SymmetricCryptoKey::try_from(key_b64).unwrap(); + match key { + SymmetricCryptoKey::Aes256CbcHmacKey(_) => (), + _ => panic!("Invalid key type"), + } + } + + #[test] + fn test_pad_unpad_key_63() { + let key_bytes = vec![1u8; 63]; + let mut encoded_bytes = vec![1u8; 65]; + encoded_bytes[0] = 1; + encoded_bytes[1] = 0; + let padded_key = pad_key(key_bytes.as_slice(), 65); + assert_eq!(encoded_bytes, padded_key); + let unpadded_key = unpad_key(&padded_key); + assert_eq!(key_bytes, unpadded_key); + } + + #[test] + fn test_pad_unpad_key_64() { + let key_bytes = vec![1u8; 64]; + let mut encoded_bytes = vec![1u8; 65]; + encoded_bytes[0] = 0; + let padded_key = pad_key(key_bytes.as_slice(), 65); + assert_eq!(encoded_bytes, padded_key); + let unpadded_key = unpad_key(&padded_key); + assert_eq!(key_bytes, unpadded_key); + } + + #[test] + fn test_pad_unpad_key_65() { + let key_bytes = vec![1u8; 65]; + let mut encoded_bytes = vec![1u8; 66]; + encoded_bytes[0] = 0; + let padded_key = pad_key(key_bytes.as_slice(), 65); + assert_eq!(encoded_bytes, padded_key); + let unpadded_key = unpad_key(&padded_key); + assert_eq!(key_bytes, unpadded_key); + } + + #[test] + fn test_encode_xchacha20_poly1305_key() { + let key = SymmetricCryptoKey::generate_internal(rand::thread_rng(), true); + let key_vec = key.to_encoded().unwrap(); + let key_vec_utf8_lossy = String::from_utf8_lossy(&key_vec); + println!("key_vec: {:?}", key_vec_utf8_lossy); + } } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 25e563bd1..72dde48d9 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -15,7 +15,7 @@ //! use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; //! //! async fn example() -> Result<(), CryptoError> { -//! let key = SymmetricCryptoKey::generate(rand::thread_rng()); +//! let key = SymmetricCryptoKey::generate(); //! //! let data = "Hello, World!".to_owned(); //! let encrypted = data.clone().encrypt_with_key(&key)?; @@ -73,6 +73,7 @@ static ALLOC: ZeroizingAllocator = ZeroizingAllocator(std::alloc::System); mod aes; +mod xchacha20; mod enc_string; pub use enc_string::{AsymmetricEncString, EncString}; mod error; @@ -91,6 +92,7 @@ pub use wordlist::EFF_LONG_WORD_LIST; mod store; pub use store::{KeyStore, KeyStoreContext}; mod traits; +mod cose; pub use traits::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs index e1bb2ebe8..c4c349b1a 100644 --- a/crates/bitwarden-crypto/src/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -41,6 +41,9 @@ pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { SymmetricCryptoKey::Aes256CbcHmacKey(key) => { EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key) } + SymmetricCryptoKey::XChaCha20Poly1305Key(_) => Err(CryptoError::OperationNotSupported( + UnsupportedOperation::EncryptionNotImplementedForKey, + )), SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, )), diff --git a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs index 5ae4f8ef6..69d4c69f5 100644 --- a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs +++ b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs @@ -17,7 +17,7 @@ mod tests { fn test_creates_a_valid_store() { let mut store = create_store::(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); store.upsert(TestSymmKey::A(0), key.clone()); assert_eq!( diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 22eaf7088..2f1a18ed8 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -8,9 +8,9 @@ use zeroize::Zeroizing; use super::KeyStoreInner; use crate::{ - derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, - AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, - SymmetricCryptoKey, + derive_shareable_key, error::UnsupportedOperation, + store::backend::StoreBackend, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, + KeyId, KeyIds, Result, SymmetricCryptoKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -169,8 +169,30 @@ impl KeyStoreContext<'_, Ids> { encryption_key: Ids::Symmetric, key_to_encrypt: Ids::Symmetric, ) -> Result { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_vec()) + let wrapping_key = self.get_symmetric_key(encryption_key)?; + match wrapping_key { + // These keys wrap directly by encrypting the key bytes of the inner key, with padding applied in case it is needed + SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { + let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; + self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_encoded()) + } + // These keys wrap using CBOR. The content type needs to indicate what the format of the inner key is + SymmetricCryptoKey::XChaCha20Poly1305Key(k) => { + let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; + match key_to_encrypt { + SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { + let encoded_key = key_to_encrypt.to_encoded_raw(); + let encrypted = EncString::encrypt_xchacha20_poly1305(encoded_key.as_slice(), k, coset::iana::CoapContentFormat::OctetStream); + encrypted + } + SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { + let cose_encoded_key = key_to_encrypt.to_encoded_raw(); + let encrypted = EncString::encrypt_xchacha20_poly1305(cose_encoded_key.as_slice(), k, coset::iana::CoapContentFormat::CoseKey); + encrypted + } + } + } + } } /// Decrypt a symmetric key into the context by using an already existing asymmetric key @@ -215,7 +237,7 @@ impl KeyStoreContext<'_, Ids> { key_to_encrypt: Ids::Symmetric, ) -> Result { let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - self.encrypt_data_with_asymmetric_key(encryption_key, &key_to_encrypt.to_vec()) + self.encrypt_data_with_asymmetric_key(encryption_key, &key_to_encrypt.to_encoded()) } /// Returns `true` if the context has a symmetric key with the given identifier @@ -230,7 +252,7 @@ impl KeyStoreContext<'_, Ids> { /// Generate a new random symmetric key and store it in the context pub fn generate_symmetric_key(&mut self, key_id: Ids::Symmetric) -> Result { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); #[allow(deprecated)] self.set_symmetric_key(key_id, key)?; Ok(key_id) @@ -353,6 +375,11 @@ impl KeyStoreContext<'_, Ids> { UnsupportedOperation::EncryptionNotImplementedForKey, )), SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key), + SymmetricCryptoKey::XChaCha20Poly1305Key(key) => EncString::encrypt_xchacha20_poly1305( + data, + key, + coset::iana::CoapContentFormat::OctetStream, + ), } } @@ -400,12 +427,11 @@ mod tests { #[test] fn test_set_keys_for_encryption() { - let mut rng = rand::thread_rng(); let store: KeyStore = KeyStore::default(); // Generate and insert a key let key_a0_id = TestSymmKey::A(0); - let key_a0 = SymmetricCryptoKey::generate(&mut rng); + let key_a0 = SymmetricCryptoKey::generate(); store .context_mut() @@ -421,14 +447,13 @@ mod tests { #[test] fn test_key_encryption() { - let mut rng = rand::thread_rng(); let store: KeyStore = KeyStore::default(); let mut ctx = store.context(); // Generate and insert a key let key_1_id = TestSymmKey::C(1); - let key_1 = SymmetricCryptoKey::generate(&mut rng); + let key_1 = SymmetricCryptoKey::generate(); ctx.set_symmetric_key(key_1_id, key_1.clone()).unwrap(); @@ -436,7 +461,7 @@ mod tests { // Generate and insert a new key let key_2_id = TestSymmKey::C(2); - let key_2 = SymmetricCryptoKey::generate(&mut rng); + let key_2 = SymmetricCryptoKey::generate(); ctx.set_symmetric_key(key_2_id, key_2.clone()).unwrap(); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 55520d206..cd2cc4dd4 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -65,7 +65,7 @@ pub use context::KeyStoreContext; /// let store: KeyStore = KeyStore::default(); /// /// #[allow(deprecated)] -/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate(rand::thread_rng())); +/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate()); /// /// // Define some data that needs to be encrypted /// struct Data(String); @@ -346,7 +346,6 @@ pub(crate) mod tests { #[test] fn test_multithread_decrypt_keeps_order() { - let mut rng = rand::thread_rng(); let store: KeyStore = KeyStore::default(); // Create a bunch of random keys @@ -354,7 +353,7 @@ pub(crate) mod tests { #[allow(deprecated)] store .context_mut() - .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate(&mut rng)) + .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate()) .unwrap(); } diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/traits/encryptable.rs index afc46ec08..44b086736 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/traits/encryptable.rs @@ -123,7 +123,7 @@ mod tests { fn test_store() -> KeyStore { let store = KeyStore::::default(); - let symm_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let symm_key = SymmetricCryptoKey::generate(); let asymm_key = AsymmetricCryptoKey::generate(&mut rand::thread_rng()); #[allow(deprecated)] diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs new file mode 100644 index 000000000..ab7d64ead --- /dev/null +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -0,0 +1,134 @@ +//! # XChaCha20Poly1305 operations +//! +//! Contains low level XChaCha20Poly1305 operations used by the rest of the crate. +//! +//! In most cases you should use the [EncString][crate::EncString] with +//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead. + +use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, XChaCha20Poly1305}; +use generic_array::{typenum::U24, GenericArray}; + +/** + * Note: + * XChaCha20Poly1305 encrypts data, and authenticates both the cipher text and associated + * data. This does not provide key-commitment, and assumes there can only be one key. + * + * If multiple keys are possible, a key-committing cipher such as + * XChaCha20Poly1305Blake3CTX should be used: `https://github.com/bitwarden/sdk-internal/pull/41` to prevent invisible-salamander style attacks. + * `https://eprint.iacr.org/2019/016.pdf` + * `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/` + */ +use crate::CryptoError; + +pub(crate) fn generate_nonce() -> GenericArray { + XChaCha20Poly1305::generate_nonce(rand::thread_rng()) +} + +pub(crate) fn encrypt_xchacha20_poly1305( + nonce: &[u8; 24], + key: &[u8; 32], + plaintext_secret_data: &[u8], + associated_data: &[u8], +) -> Result, CryptoError> { + // This buffer contains the plaintext, that will be encrypted in-place + let mut buffer = Vec::from(plaintext_secret_data); + XChaCha20Poly1305::new(GenericArray::from_slice(key)) + .encrypt_in_place( + GenericArray::from_slice(nonce), + associated_data, + &mut buffer, + ) + .map_err(|_| CryptoError::InvalidKey)?; + Ok(buffer) +} + +pub(crate) fn decrypt_xchacha20_poly1305( + nonce: &[u8; 24], + key: &[u8; 32], + ciphertext: &[u8], + associated_data: &[u8], +) -> Result, CryptoError> { + let mut buffer = ciphertext.to_vec(); + XChaCha20Poly1305::new(GenericArray::from_slice(key)) + .decrypt_in_place( + GenericArray::from_slice(nonce), + associated_data, + &mut buffer, + ) + .map_err(|_| CryptoError::InvalidKey)?; + Ok(buffer) +} + +mod tests { + #[cfg(test)] + use crate::xchacha20::*; + + #[test] + fn test_encrypt_decrypt_xchacha20() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let nonce = generate_nonce().into(); + + let encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); + let decrypted = + decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data).unwrap(); + assert_eq!(plaintext_secret_data, decrypted.as_slice()); + } + + #[test] + fn test_fails_when_ciphertext_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let nonce = generate_nonce().into(); + + let mut encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); + encrypted[0] = encrypted[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + assert!(result.is_err()); + } + + #[test] + fn test_fails_when_associated_data_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let mut authenticated_data = b"My authenticated data".to_vec(); + let nonce = generate_nonce().into(); + + let encrypted = encrypt_xchacha20_poly1305( + &nonce, + &key, + plaintext_secret_data, + authenticated_data.as_slice(), + ) + .unwrap(); + authenticated_data[0] = authenticated_data[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305( + &nonce, + &key, + encrypted.as_slice(), + authenticated_data.as_slice(), + ); + assert!(result.is_err()); + } + + #[test] + fn test_fails_when_nonce_changed() { + let key = [0u8; 32]; + let plaintext_secret_data = b"My secret data"; + let authenticated_data = b"My authenticated data"; + let mut nonce = generate_nonce().into(); + + let encrypted = + encrypt_xchacha20_poly1305(&nonce, &key, plaintext_secret_data, authenticated_data) + .unwrap(); + nonce[0] = nonce[0].wrapping_add(1); + let result = decrypt_xchacha20_poly1305(&nonce, &key, &encrypted, authenticated_data); + assert!(result.is_err()); + } +} diff --git a/crates/bitwarden-exporters/src/models.rs b/crates/bitwarden-exporters/src/models.rs index 1cd00c721..ebd6d6e1e 100644 --- a/crates/bitwarden-exporters/src/models.rs +++ b/crates/bitwarden-exporters/src/models.rs @@ -222,7 +222,7 @@ mod tests { #[test] fn test_from_login() { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); @@ -273,7 +273,7 @@ mod tests { #[test] fn test_from_cipher_login() { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 5df63ed75..7c2ff5581 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -880,7 +880,7 @@ mod tests { #[test] fn test_generate_cipher_key() { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_key(key); let original_cipher = generate_cipher(); @@ -906,7 +906,7 @@ mod tests { #[test] fn test_generate_cipher_key_when_a_cipher_key_already_exists() { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_key(key); let mut original_cipher = generate_cipher(); @@ -935,7 +935,7 @@ mod tests { #[test] fn test_generate_cipher_key_ignores_attachments_without_key() { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_key(key); let mut cipher = generate_cipher(); @@ -958,8 +958,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); - let org_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); + let org_key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -983,8 +983,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org_manually() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); - let org_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); + let org_key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -1003,8 +1003,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_without_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); - let org_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); + let org_key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let mut cipher = generate_cipher(); @@ -1027,8 +1027,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); - let org_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); + let org_key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1077,7 +1077,10 @@ mod tests { .unwrap(); let new_attachment_key_dec: SymmetricCryptoKey = new_attachment_key_dec.try_into().unwrap(); - assert_eq!(new_attachment_key_dec.to_vec(), attachment_key_val.to_vec()); + assert_eq!( + new_attachment_key_dec.to_encoded().unwrap(), + attachment_key_val.to_encoded().unwrap() + ); let cred2: Fido2CredentialFullView = cipher .login @@ -1095,8 +1098,8 @@ mod tests { #[test] fn test_move_user_cipher_with_key_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(rand::thread_rng()); - let org_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); + let org_key = SymmetricCryptoKey::generate(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1148,7 +1151,10 @@ mod tests { #[allow(deprecated)] let cipher_key_val = ctx.dangerous_get_symmetric_key(cipher_key).unwrap(); - assert_eq!(new_cipher_key_dec.to_vec(), cipher_key_val.to_vec()); + assert_eq!( + new_cipher_key_dec.to_encoded().unwrap(), + cipher_key_val.to_encoded().unwrap() + ); // Check that the attachment key hasn't changed assert_eq!( diff --git a/crates/bitwarden-wasm-internal/build.sh b/crates/bitwarden-wasm-internal/build.sh index 211d579d3..25fa91745 100755 --- a/crates/bitwarden-wasm-internal/build.sh +++ b/crates/bitwarden-wasm-internal/build.sh @@ -25,7 +25,7 @@ fi # Note that this requirest build-std which is an unstable feature, # this normally requires a nightly build, but we can also use the # RUSTC_BOOTSTRAP hack to use the same stable version as the normal build -RUSTFLAGS=-Ctarget-cpu=mvp RUSTC_BOOTSTRAP=1 cargo build -p bitwarden-wasm-internal -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ${RELEASE_FLAG} +RUSTFLAGS=-Ctarget-cpu=mvp RUSTC_BOOTSTRAP=1 cargo build -p bitwarden-wasm-internal -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown ${RELEASE_FLAG} --config 'patch.crates-io.pkcs5.git="https://github.com/bitwarden/rustcrypto-formats.git"' --config 'patch.crates-io.pkcs5.rev="2b27c63034217dd126bbf5ed874da51b84f8c705"' wasm-bindgen --target bundler --out-dir crates/bitwarden-wasm-internal/npm ./target/wasm32-unknown-unknown/${BUILD_FOLDER}/bitwarden_wasm_internal.wasm wasm-bindgen --target nodejs --out-dir crates/bitwarden-wasm-internal/npm/node ./target/wasm32-unknown-unknown/${BUILD_FOLDER}/bitwarden_wasm_internal.wasm diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 1718588ee..a87141da7 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey }; use wasm_bindgen::prelude::*; @@ -14,7 +14,7 @@ pub struct PureCrypto {} #[wasm_bindgen] impl PureCrypto { - pub fn symmetric_decrypt(enc_string: String, key_b64: String) -> Result { + pub fn symmetric_decrypt(enc_string: String, key_b64: Vec) -> Result { let enc_string = EncString::from_str(&enc_string)?; let key = SymmetricCryptoKey::try_from(key_b64)?; enc_string.decrypt_with_key(&key) @@ -22,7 +22,7 @@ impl PureCrypto { pub fn symmetric_decrypt_to_bytes( enc_string: String, - key_b64: String, + key_b64: Vec, ) -> Result, CryptoError> { let enc_string = EncString::from_str(&enc_string)?; let key = SymmetricCryptoKey::try_from(key_b64)?; @@ -31,26 +31,54 @@ impl PureCrypto { pub fn symmetric_decrypt_array_buffer( enc_bytes: Vec, - key_b64: String, + key_b64: Vec, ) -> Result, CryptoError> { let enc_string = EncString::from_buffer(&enc_bytes)?; let key = SymmetricCryptoKey::try_from(key_b64)?; enc_string.decrypt_with_key(&key) } - pub fn symmetric_encrypt(plain: String, key_b64: String) -> Result { + pub fn symmetric_encrypt(plain: Vec, key_b64: Vec) -> Result { let key = SymmetricCryptoKey::try_from(key_b64)?; - Ok(plain.encrypt_with_key(&key)?.to_string()) } pub fn symmetric_encrypt_to_array_buffer( plain: Vec, - key_b64: String, + key_b64: Vec, ) -> Result, CryptoError> { let key = SymmetricCryptoKey::try_from(key_b64)?; plain.encrypt_with_key(&key)?.to_buffer() } + + pub fn decrypt_userkey_with_masterpassword( + encrypted_userkey: String, + master_password: String, + email: String, + kdf: Kdf, + ) -> Result, CryptoError> { + let masterkey = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; + let encrypted_userkey = EncString::from_str(&encrypted_userkey)?; + let result = masterkey.decrypt_user_key(encrypted_userkey).map_err(|_| CryptoError::InvalidKey)?; + Ok(result.to_encoded()) + } + + pub fn encrypt_userkey_with_masterpassword( + userkey: Vec, + master_password: String, + email: String, + kdf: Kdf, + ) -> Result { + let masterkey = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; + let userkey = SymmetricCryptoKey::try_from(userkey)?; + let result = masterkey.encrypt_user_key(&userkey)?; + Ok(result.to_string()) + } + + pub fn generate_userkey(cose: bool) -> Result, CryptoError> { + let key = if !cose { SymmetricCryptoKey::generate() } else { SymmetricCryptoKey::generate_cose() }; + Ok(key.to_encoded()) + } } #[cfg(test)] diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs index 133a786ee..67f38b0e8 100644 --- a/crates/memory-testing/src/main.rs +++ b/crates/memory-testing/src/main.rs @@ -28,7 +28,7 @@ fn main() { match case.command { memory_testing::CaseCommand::SymmetricKey { key } => { let key = SymmetricCryptoKey::try_from(key).unwrap(); - symmetric_keys.push((key.to_vec(), key)); + symmetric_keys.push((key.to_encoded(), key)); } memory_testing::CaseCommand::AsymmetricKey { private_key } => { let key = bitwarden_crypto::AsymmetricCryptoKey::from_pem(&private_key).unwrap(); From 27fb7cf036825d7e0388a30086d6be3b23d00c82 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 24 Mar 2025 17:11:21 +0100 Subject: [PATCH 011/152] Cleanup --- crates/bitwarden-crypto/src/keys/key_hash.rs | 33 ------------------- .../bitwarden-crypto/src/keys/master_key.rs | 3 +- crates/bitwarden-crypto/src/keys/mod.rs | 1 - .../src/keys/symmetric_crypto_key.rs | 22 ++----------- 4 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 crates/bitwarden-crypto/src/keys/key_hash.rs diff --git a/crates/bitwarden-crypto/src/keys/key_hash.rs b/crates/bitwarden-crypto/src/keys/key_hash.rs deleted file mode 100644 index e47acb4fe..000000000 --- a/crates/bitwarden-crypto/src/keys/key_hash.rs +++ /dev/null @@ -1,33 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] -pub(crate) enum KeyHashAlgorithm { - #[serde(rename = "b3")] - Blake3, -} - -#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] -pub(crate) struct KeyHash { - #[serde(with = "serde_bytes", rename = "h")] - pub(crate) hash: Vec, - #[serde(rename = "alg")] - pub(crate) algorithm: KeyHashAlgorithm, -} - -pub(crate) trait KeyHashable { - fn hash(&self) -> KeyHash; -} - -impl KeyHashable for T { - fn hash(&self) -> KeyHash { - let hash: [u8; 32] = blake3::hash(&self.hash_data()).into(); - KeyHash { - hash: hash.to_vec(), - algorithm: KeyHashAlgorithm::Blake3, - } - } -} - -pub(crate) trait KeyHashData { - fn hash_data(&self) -> Vec; -} diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index cbcc25f67..22efcd777 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -67,7 +67,7 @@ impl MasterKey { /// Generate a new random user key and encrypt it with the master key. pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { - make_user_key(rand::thread_rng(), self) + make_user_key(self) } /// Encrypt the users user key @@ -150,7 +150,6 @@ pub(super) fn decrypt_user_key( /// Generate a new random user key and encrypt it with the master key. fn make_user_key( - mut rng: impl rand::RngCore, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { let user_key = SymmetricCryptoKey::generate(); diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 658aa9daa..4dd55658d 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -21,7 +21,6 @@ pub use device_key::{DeviceKey, TrustDeviceResponse}; mod pin_key; pub use pin_key::PinKey; mod kdf; -pub(crate) mod key_hash; pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 71f32150f..d6f8609d6 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -8,7 +8,7 @@ use rand::Rng; use subtle::{Choice, ConstantTimeEq}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::{key_encryptable::CryptoKey, key_hash::KeyHashData}; +use super::key_encryptable::CryptoKey; use crate::{cose, CryptoError}; /// Aes256CbcKey is a symmetric encryption key, consisting of one 256-bit key, @@ -81,21 +81,6 @@ pub enum SymmetricCryptoKey { XChaCha20Poly1305Key(XChaCha20Poly1305Key), } -impl KeyHashData for SymmetricCryptoKey { - fn hash_data(&self) -> Vec { - match &self { - SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), - SymmetricCryptoKey::Aes256CbcHmacKey(key) => { - let mut buf = Vec::with_capacity(64); - buf.extend_from_slice(&key.enc_key); - buf.extend_from_slice(&key.mac_key); - buf - } - SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.enc_key.to_vec(), - } - } -} - impl SymmetricCryptoKey { // enc type 0 old static format const AES256_CBC_KEY_LEN: usize = 32; @@ -148,9 +133,6 @@ impl SymmetricCryptoKey { } } - /** - * - */ pub(crate) fn to_encoded_raw(&self) -> Vec { match self { SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), @@ -327,7 +309,7 @@ mod tests { #[test] fn test_symmetric_crypto_key() { let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test")); - let key2 = SymmetricCryptoKey::try_from(key.to_base64().unwrap()).unwrap(); + let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap(); assert_eq!(key, key2); From eab453fc762609fb005883da5349f8a7542c7c7a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 13:45:52 +0100 Subject: [PATCH 012/152] Fix build --- .../src/enc_string/symmetric.rs | 27 +++-- .../bitwarden-crypto/src/keys/master_key.rs | 6 +- .../src/keys/symmetric_crypto_key.rs | 99 ++++++++++--------- crates/bitwarden-crypto/src/lib.rs | 1 - crates/bitwarden-crypto/src/xchacha20.rs | 6 +- .../src/pure_crypto.rs | 20 ++-- 6 files changed, 82 insertions(+), 77 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 8f7e9990d..ee10de5b8 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -167,7 +167,7 @@ impl EncString { } => { buf = Vec::with_capacity(1 + data.len()); buf.push(self.enc_type()); - buf.extend_from_slice(&data); + buf.extend_from_slice(data); } } @@ -195,7 +195,7 @@ impl Display for EncString { EncString::COSE_B64 { data, } => { - write!(f, "{}.{}", self.enc_type(), STANDARD.encode(&data))?; + write!(f, "{}.{}", self.enc_type(), STANDARD.encode(data))?; Ok(()) } @@ -238,34 +238,33 @@ impl EncString { key: &XChaCha20Poly1305Key, content_format: CoapContentFormat, ) -> Result { - let mut protected_header = coset::HeaderBuilder::new() .content_format(content_format) .build(); protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305)); - let mut unprotected_header = coset::HeaderBuilder::new() - .build(); - let nonce = crate::xchacha20::generate_nonce(); - unprotected_header.iv = nonce.to_vec(); - let cose = coset::CoseEncrypt0Builder::new() + let mut nonce = [0u8; 24]; + let cose_encrypt0 = coset::CoseEncrypt0Builder::new() + .protected(protected_header) .try_create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305( - nonce.as_slice().try_into().map_err(|_| CryptoError::InvalidKey)?, key.enc_key .as_slice() .try_into() .expect("XChaChaPoly1305 key is 32 bytes long"), data, aad, - )?; - Ok(ciphertext) - }).map_err(|_a: CryptoError| CryptoError::EncodingError)?; - let cose = cose.unprotected(unprotected_header) + ); + nonce.copy_from_slice(ciphertext.nonce.as_slice()); + Ok(ciphertext.ciphertext) + }).map_err(|_a: CryptoError| CryptoError::EncodingError)? + .unprotected(coset::HeaderBuilder::new() + .iv(nonce.to_vec()) + .build()) .build(); Ok(EncString::COSE_B64 { - data: cose.to_vec().map_err(|_| CryptoError::EncodingError)?, + data: cose_encrypt0.to_vec().map_err(|_| CryptoError::EncodingError)?, }) } diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 22efcd777..a77c4631d 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -67,7 +67,8 @@ impl MasterKey { /// Generate a new random user key and encrypt it with the master key. pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { - make_user_key(self) + let mut rng = rand::thread_rng(); + make_user_key(&mut rng, self) } /// Encrypt the users user key @@ -150,9 +151,10 @@ pub(super) fn decrypt_user_key( /// Generate a new random user key and encrypt it with the master key. fn make_user_key( + rng: impl rand::RngCore, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { - let user_key = SymmetricCryptoKey::generate(); + let user_key = SymmetricCryptoKey::generate_internal(rng, false); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index d6f8609d6..49b04ec88 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use std::{cmp::max, pin::Pin}; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; @@ -87,20 +87,27 @@ impl SymmetricCryptoKey { // enc type 2 old static format const AES256_CBC_HMAC_KEY_LEN: usize = 64; + /** + * Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey] + */ pub fn generate() -> Self { let mut rng = rand::thread_rng(); Self::generate_internal(&mut rng, false) } - pub fn generate_cose() -> Self { + /** + * Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] + */ + pub fn generate_xchacha20() -> Self { let mut rng = rand::thread_rng(); Self::generate_internal(&mut rng, true) } /// Generate a new random [SymmetricCryptoKey] /// @param rng: A random number generator - fn generate_internal(mut rng: impl rand::RngCore, cose: bool) -> Self { - if !cose { + /// @param xchacha: If true, generate an XChaCha20Poly1305 key, otherwise generate an AES256_CBC_HMAC key + pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha: bool) -> Self { + if !xchacha { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); @@ -121,14 +128,14 @@ impl SymmetricCryptoKey { * the key. */ pub fn to_encoded(&self) -> Vec { - let encoded_key = self.to_encoded_raw(); + let mut encoded_key = self.to_encoded_raw(); match self { SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { encoded_key } SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { - let padded_key = pad_key(&encoded_key, Self::AES256_CBC_HMAC_KEY_LEN + 1); - padded_key + pad_key(&mut encoded_key, Self::AES256_CBC_HMAC_KEY_LEN + 1); + encoded_key } } } @@ -151,8 +158,7 @@ impl SymmetricCryptoKey { .add_key_op(iana::KeyOperation::UnwrapKey) .build(); cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305)); - let encoded_key = cose_key.to_vec().map_err(|_| CryptoError::InvalidKey).unwrap(); - encoded_key + cose_key.to_vec().map_err(|_| CryptoError::InvalidKey).expect("Failed to encode key") } } } @@ -225,8 +231,8 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { Ok(SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey { enc_key })) } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN { - let unpadded_value = zeroize::Zeroizing::new(unpad_key(value)); - let cose_key = coset::CoseKey::from_slice(&unpadded_value.as_slice()) + let unpadded_value = unpad_key(value); + let cose_key = coset::CoseKey::from_slice(unpadded_value) .map_err(|_| CryptoError::InvalidKey)?; parse_cose_key(&cose_key) } else { @@ -270,23 +276,20 @@ impl std::fmt::Debug for SymmetricCryptoKey { } } -/// Pad a key to a minimum length; -/// The first byte describes the number (N) of subsequently following null bytes -/// Next, there are N null bytes -/// Finally, the key bytes are appended -fn pad_key(key_bytes: &[u8], min_length: usize) -> Vec { - let null_bytes = min_length.saturating_sub(1).saturating_sub(key_bytes.len()); - let mut padded_key = Vec::with_capacity(min_length); - padded_key.extend_from_slice(&[null_bytes as u8]); - padded_key.extend_from_slice(vec![0u8; null_bytes].as_slice()); - padded_key.extend_from_slice(key_bytes); - padded_key +/// Pad a key to a minimum length using PKCS7-like padding +fn pad_key(key_bytes: &mut Vec, min_length: usize) { + // at least 1 byte of padding is required + let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1); + let padded_length = max(min_length, key_bytes.len() + 1); + key_bytes.resize(padded_length, pad_bytes as u8); } -// Unpad a key that was padded with pad_key -fn unpad_key(key_bytes: &[u8]) -> Vec { - let null_bytes = key_bytes[0] as usize; - key_bytes[1 + null_bytes..].to_vec() +// Unpad a key +fn unpad_key(key_bytes: &[u8]) -> &[u8] { + // this unwrap is safe, the input is always at least 1 byte long + #[allow(clippy::unwrap_used)] + let pad_len = *key_bytes.last().unwrap() as usize; + key_bytes[..key_bytes.len() - pad_len].as_ref() } #[cfg(test)] @@ -335,45 +338,47 @@ mod tests { _ => panic!("Invalid key type"), } } - #[test] fn test_pad_unpad_key_63() { - let key_bytes = vec![1u8; 63]; + let original_key = vec![1u8; 63]; + let mut key_bytes = original_key.clone(); let mut encoded_bytes = vec![1u8; 65]; - encoded_bytes[0] = 1; - encoded_bytes[1] = 0; - let padded_key = pad_key(key_bytes.as_slice(), 65); - assert_eq!(encoded_bytes, padded_key); - let unpadded_key = unpad_key(&padded_key); - assert_eq!(key_bytes, unpadded_key); + encoded_bytes[63] = 2; + encoded_bytes[64] = 2; + pad_key(&mut key_bytes, 65); + assert_eq!(encoded_bytes, key_bytes); + let unpadded_key = unpad_key(&key_bytes); + assert_eq!(original_key, unpadded_key); } #[test] fn test_pad_unpad_key_64() { - let key_bytes = vec![1u8; 64]; + let original_key = vec![1u8; 64]; + let mut key_bytes = original_key.clone(); let mut encoded_bytes = vec![1u8; 65]; - encoded_bytes[0] = 0; - let padded_key = pad_key(key_bytes.as_slice(), 65); - assert_eq!(encoded_bytes, padded_key); - let unpadded_key = unpad_key(&padded_key); - assert_eq!(key_bytes, unpadded_key); + encoded_bytes[64] = 1; + pad_key(&mut key_bytes, 65); + assert_eq!(encoded_bytes, key_bytes); + let unpadded_key = unpad_key(&key_bytes); + assert_eq!(original_key, unpadded_key); } #[test] fn test_pad_unpad_key_65() { - let key_bytes = vec![1u8; 65]; + let original_key = vec![1u8; 65]; + let mut key_bytes = original_key.clone(); let mut encoded_bytes = vec![1u8; 66]; - encoded_bytes[0] = 0; - let padded_key = pad_key(key_bytes.as_slice(), 65); - assert_eq!(encoded_bytes, padded_key); - let unpadded_key = unpad_key(&padded_key); - assert_eq!(key_bytes, unpadded_key); + encoded_bytes[65] = 1; + pad_key(&mut key_bytes, 65); + assert_eq!(encoded_bytes, key_bytes); + let unpadded_key = unpad_key(&key_bytes); + assert_eq!(original_key, unpadded_key); } #[test] fn test_encode_xchacha20_poly1305_key() { let key = SymmetricCryptoKey::generate_internal(rand::thread_rng(), true); - let key_vec = key.to_encoded().unwrap(); + let key_vec = key.to_encoded(); let key_vec_utf8_lossy = String::from_utf8_lossy(&key_vec); println!("key_vec: {:?}", key_vec_utf8_lossy); } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 8cc80981d..30b8e0274 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -73,7 +73,6 @@ static ALLOC: ZeroizingAllocator = ZeroizingAllocator(std::alloc::System); mod aes; -mod xchacha20; mod enc_string; pub use enc_string::{AsymmetricEncString, EncString}; mod error; diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 5e832ff65..ce3e5ad94 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -22,12 +22,12 @@ use crate::CryptoError; #[allow(unused)] pub(crate) struct XChaCha20Poly1305Ciphertext { - nonce: GenericArray::NonceSize>, - ciphertext: Vec, + pub(crate) nonce: GenericArray::NonceSize>, + pub(crate) ciphertext: Vec, } #[allow(unused)] -fn encrypt_xchacha20_poly1305( +pub(crate) fn encrypt_xchacha20_poly1305( key: &[u8; 32], plaintext_secret_data: &[u8], associated_data: &[u8], diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index a87141da7..53ae4b906 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -75,8 +75,8 @@ impl PureCrypto { Ok(result.to_string()) } - pub fn generate_userkey(cose: bool) -> Result, CryptoError> { - let key = if !cose { SymmetricCryptoKey::generate() } else { SymmetricCryptoKey::generate_cose() }; + pub fn generate_userkey(use_xchacha20: bool) -> Result, CryptoError> { + let key = if !use_xchacha20 { SymmetricCryptoKey::generate() } else { SymmetricCryptoKey::generate_xchacha20() }; Ok(key.to_encoded()) } } @@ -105,14 +105,14 @@ mod tests { fn test_symmetric_decrypt() { let enc_string = EncString::from_str(ENCRYPTED).unwrap(); - let result = PureCrypto::symmetric_decrypt(enc_string.to_string(), KEY_B64.to_string()); + let result = PureCrypto::symmetric_decrypt(enc_string.to_string(), KEY_B64.as_bytes().to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED); } #[test] fn test_symmetric_encrypt() { - let result = PureCrypto::symmetric_encrypt(DECRYPTED.to_string(), KEY_B64.to_string()); + let result = PureCrypto::symmetric_encrypt(DECRYPTED.as_bytes().to_vec(), KEY_B64.as_bytes().to_vec()); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption } @@ -120,9 +120,9 @@ mod tests { #[test] fn test_symmetric_round_trip() { let encrypted = - PureCrypto::symmetric_encrypt(DECRYPTED.to_string(), KEY_B64.to_string()).unwrap(); + PureCrypto::symmetric_encrypt(DECRYPTED.as_bytes().to_vec(), KEY_B64.as_bytes().to_vec()).unwrap(); let decrypted = - PureCrypto::symmetric_decrypt(encrypted.clone(), KEY_B64.to_string()).unwrap(); + PureCrypto::symmetric_decrypt(encrypted.clone(), KEY_B64.as_bytes().to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED); } @@ -130,7 +130,7 @@ mod tests { fn test_symmetric_decrypt_array_buffer() { let result = PureCrypto::symmetric_decrypt_array_buffer( ENCRYPTED_BYTES.to_vec(), - KEY_B64.to_string(), + KEY_B64.as_bytes().to_vec(), ); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED_BYTES); @@ -140,7 +140,7 @@ mod tests { fn test_symmetric_encrypt_to_array_buffer() { let result = PureCrypto::symmetric_encrypt_to_array_buffer( DECRYPTED_BYTES.to_vec(), - KEY_B64.to_string(), + KEY_B64.as_bytes().to_vec(), ); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption @@ -150,11 +150,11 @@ mod tests { fn test_symmetric_buffer_round_trip() { let encrypted = PureCrypto::symmetric_encrypt_to_array_buffer( DECRYPTED_BYTES.to_vec(), - KEY_B64.to_string(), + KEY_B64.as_bytes().to_vec(), ) .unwrap(); let decrypted = - PureCrypto::symmetric_decrypt_array_buffer(encrypted.clone(), KEY_B64.to_string()) + PureCrypto::symmetric_decrypt_array_buffer(encrypted.clone(), KEY_B64.as_bytes().to_vec()) .unwrap(); assert_eq!(decrypted, DECRYPTED_BYTES); } From 43b117cde80cf7b17a83e02df72a3340c3217af1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 18:05:52 +0100 Subject: [PATCH 013/152] Add keyids --- .../src/enc_string/symmetric.rs | 16 +++++++-------- crates/bitwarden-crypto/src/error.rs | 2 ++ crates/bitwarden-crypto/src/keys/key_id.rs | 20 +++++++++++++++++++ .../bitwarden-crypto/src/keys/master_key.rs | 8 ++------ crates/bitwarden-crypto/src/keys/mod.rs | 1 + .../src/keys/symmetric_crypto_key.rs | 12 ++++++----- 6 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 crates/bitwarden-crypto/src/keys/key_id.rs diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index ee10de5b8..04cf88550 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -60,7 +60,7 @@ pub enum EncString { data: Vec, }, // 7 The actual enc type is contained in the cose struct - COSE_B64 { + XChaCha20_Poly1305_Cose_B64 { data: Vec, }, } @@ -95,7 +95,7 @@ impl FromStr for EncString { ("7", 1) => { let buffer = from_b64_vec(parts[0])?; - Ok(EncString::COSE_B64 { data: buffer }) + Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: buffer }) } (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), @@ -135,7 +135,7 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } 7 => { - Ok(EncString::COSE_B64 { data: buf[1..].to_vec() }) + Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: buf[1..].to_vec() }) } _ => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), @@ -162,7 +162,7 @@ impl EncString { buf.extend_from_slice(mac); buf.extend_from_slice(data); } - EncString::COSE_B64 { + EncString::XChaCha20_Poly1305_Cose_B64 { data, } => { buf = Vec::with_capacity(1 + data.len()); @@ -192,7 +192,7 @@ impl Display for EncString { Ok(()) } - EncString::COSE_B64 { + EncString::XChaCha20_Poly1305_Cose_B64 { data, } => { write!(f, "{}.{}", self.enc_type(), STANDARD.encode(data))?; @@ -263,7 +263,7 @@ impl EncString { .build()) .build(); - Ok(EncString::COSE_B64 { + Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: cose_encrypt0.to_vec().map_err(|_| CryptoError::EncodingError)?, }) } @@ -273,7 +273,7 @@ impl EncString { match self { EncString::AesCbc256_B64 { .. } => 0, EncString::AesCbc256_HmacSha256_B64 { .. } => 2, - EncString::COSE_B64 { .. } => 7, + EncString::XChaCha20_Poly1305_Cose_B64 { .. } => 7, } } } @@ -304,7 +304,7 @@ impl KeyDecryptable> for EncString { SymmetricCryptoKey::Aes256CbcHmacKey(key), ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), ( - EncString::COSE_B64 { + EncString::XChaCha20_Poly1305_Cose_B64 { data, }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 11a565f1e..f34a387f7 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -27,6 +27,8 @@ pub enum CryptoError { MissingField(&'static str), #[error("Missing Key for Id: {0}")] MissingKeyId(String), + #[error("The provided Id is not valid: {0}")] + InvalidKeyId(String), #[error("Crypto store is read-only")] ReadOnlyKeyStore, diff --git a/crates/bitwarden-crypto/src/keys/key_id.rs b/crates/bitwarden-crypto/src/keys/key_id.rs new file mode 100644 index 000000000..ca248faa8 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/key_id.rs @@ -0,0 +1,20 @@ +use rand::RngCore; + +pub(crate) struct KeyId([u8; 24]); + +impl KeyId { + pub fn as_bytes(&self) -> &[u8; 24] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; 24]) -> Self { + KeyId(bytes) + } + + pub fn generate() -> Self { + let mut rng = rand::thread_rng(); + let mut key_id = [0u8; 24]; + rng.fill_bytes(&mut key_id); + Self::from_bytes(key_id) + } +} diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index a77c4631d..ca1aacfb6 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -137,12 +137,8 @@ pub(super) fn decrypt_user_key( let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.decrypt_with_key(&stretched_key)? } - EncString::COSE_B64 { .. } => { - let key = SymmetricCryptoKey::XChaCha20Poly1305Key(super::XChaCha20Poly1305Key { - enc_key: Box::pin(GenericArray::clone_from_slice(key)), - }); - - user_key.decrypt_with_key(&key)? + EncString::XChaCha20_Poly1305_Cose_B64 { .. } => { + return Err(CryptoError::OperationNotSupported(crate::error::UnsupportedOperation::EncryptionNotImplementedForKey)); } }; diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 4dd55658d..73794ebce 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -20,6 +20,7 @@ mod device_key; pub use device_key::{DeviceKey, TrustDeviceResponse}; mod pin_key; pub use pin_key::PinKey; +mod key_id; mod kdf; pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 49b04ec88..75f69c6df 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -8,7 +8,7 @@ use rand::Rng; use subtle::{Choice, ConstantTimeEq}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::key_encryptable::CryptoKey; +use super::{key_encryptable::CryptoKey, key_id::KeyId}; use crate::{cose, CryptoError}; /// Aes256CbcKey is a symmetric encryption key, consisting of one 256-bit key, @@ -57,6 +57,7 @@ impl PartialEq for Aes256CbcHmacKey { #[derive(Zeroize, Clone)] #[cfg_attr(test, derive(Debug))] pub struct XChaCha20Poly1305Key { + pub(crate) key_id: [u8; 24], pub(crate) enc_key: Pin>>, } @@ -106,8 +107,8 @@ impl SymmetricCryptoKey { /// Generate a new random [SymmetricCryptoKey] /// @param rng: A random number generator /// @param xchacha: If true, generate an XChaCha20Poly1305 key, otherwise generate an AES256_CBC_HMAC key - pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha: bool) -> Self { - if !xchacha { + pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha20: bool) -> Self { + if !xchacha20 { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); @@ -118,7 +119,7 @@ impl SymmetricCryptoKey { } else { let mut enc_key = Box::pin(GenericArray::::default()); rng.fill(enc_key.as_mut_slice()); - SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key }) + SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key, key_id: *KeyId::generate().as_bytes() }) } } @@ -152,6 +153,7 @@ impl SymmetricCryptoKey { SymmetricCryptoKey::XChaCha20Poly1305Key(key) => { let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec()); let mut cose_key = builder + .key_id(key.key_id.to_vec()) .add_key_op(iana::KeyOperation::Decrypt) .add_key_op(iana::KeyOperation::Encrypt) .add_key_op(iana::KeyOperation::WrapKey) @@ -258,7 +260,7 @@ fn parse_cose_key(cose_key: &coset::CoseKey) -> Result::default()); enc_key.copy_from_slice(key_bytes); - Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key })) + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key, key_id: cose_key.key_id.clone().try_into().map_err(|_| CryptoError::InvalidKey)? })) } else { Err(CryptoError::InvalidKey) } From 4c7c7244f6774d7ca21e12dd9a17b313b3c6cd09 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 18:11:06 +0100 Subject: [PATCH 014/152] Remove blake3 dependency --- Cargo.lock | 36 ------------------------------ crates/bitwarden-crypto/Cargo.toml | 1 - 2 files changed, 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index deaf455ae..08c061ea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,21 +169,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "zeroize", -] - [[package]] name = "askama" version = "0.12.1" @@ -437,7 +422,6 @@ dependencies = [ "argon2", "base64", "bitwarden-error", - "blake3", "cbc", "chacha20poly1305", "ciborium", @@ -705,20 +689,6 @@ dependencies = [ "digest", ] -[[package]] -name = "blake3" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "zeroize", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -1054,12 +1024,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "core-foundation" version = "0.10.0" diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 21589f379..fed6b0188 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -28,7 +28,6 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ ], default-features = false } base64 = ">=0.22.1, <0.23" bitwarden-error = { workspace = true } -blake3 = { version = "1.5.5", features = ["zeroize"] } cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } chacha20poly1305 = { version = "0.10.1" } ciborium = "0.2.2" From 5455f179c985ccbf1b550be61288b5ec6e195b1b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 18:12:36 +0100 Subject: [PATCH 015/152] Remove poly1305 --- Cargo.lock | 2 -- crates/bitwarden-crypto/Cargo.toml | 1 - 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08c061ea1..841d195f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,7 +433,6 @@ dependencies = [ "num-bigint", "num-traits", "pbkdf2", - "poly1305", "rand", "rand_chacha", "rayon", @@ -2841,7 +2840,6 @@ dependencies = [ "cpufeatures", "opaque-debug", "universal-hash", - "zeroize", ] [[package]] diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index fed6b0188..30e30a624 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -38,7 +38,6 @@ hmac = ">=0.12.1, <0.13" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } -poly1305 = { version = "0.8.0", features = ["zeroize"] } rand = ">=0.8.5, <0.9" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" From c9882c63ea414615b55ac2076df31dd7b73409ec Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 18:14:48 +0100 Subject: [PATCH 016/152] Remove hash parse error --- crates/bitwarden-crypto/src/error.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index f34a387f7..6a0eec6a5 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -50,12 +50,6 @@ pub enum CryptoError { #[error("Number is zero")] ZeroNumber, - #[error("Invalid key hash algorithm")] - InvalidHashAlgorithm, - - #[error("Error parsing key hash")] - HashParseError, - #[error("Unsupported operation, {0}")] OperationNotSupported(UnsupportedOperation), From cac04d350ff066d519bacc3a128495daeddf4b1b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 20:19:31 +0100 Subject: [PATCH 017/152] tmp --- crates/bitwarden-crypto/src/cose.rs | 7 +++- .../src/enc_string/symmetric.rs | 35 ++++++------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index f6255fb89..bedde5fe7 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -5,4 +5,9 @@ use coset::iana; pub(crate) const XCHACHA20_POLY1305: i64 = -70000; -pub(crate) const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; \ No newline at end of file +pub(crate) const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; + +pub enum ContentFormat { + PaddedUtf8, + Pkcs8, +} \ No newline at end of file diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 04cf88550..10817e9dd 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ - cose, error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key + cose::{self, ContentFormat}, error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key }; #[cfg(feature = "wasm")] @@ -236,10 +236,9 @@ impl EncString { pub(crate) fn encrypt_xchacha20_poly1305( data_dec: &[u8], key: &XChaCha20Poly1305Key, - content_format: CoapContentFormat, + content_format: ContentFormat, ) -> Result { let mut protected_header = coset::HeaderBuilder::new() - .content_format(content_format) .build(); protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305)); @@ -364,31 +363,19 @@ impl schemars::JsonSchema for EncString { } } -// Pads the bytes to the next block size -// The format is as follows: -// N|0x00..0x00|data -// ^ N null bytes -fn pad_bytes(bytes: &[u8], block_size: usize) -> Vec { +/// Pads bytes to a minimum length using PKCS7-like padding +fn pad_bytes(bytes: &mut Vec, block_size: usize) { let padding_len = block_size - (bytes.len() % block_size); - let mut padded = vec![0; 1 + padding_len + bytes.len()]; - padded[0] = padding_len as u8; - padded[1..=padding_len].fill(0); - padded[1 + padding_len..].copy_from_slice(bytes); - padded + let padded_length = padding_len + bytes.len(); + bytes.resize(padded_length, padding_len as u8); } // Unpads the bytes -fn unpad_bytes(bytes: &[u8]) -> Result<&[u8]> { - if bytes.is_empty() { - return Err(CryptoError::EncodingError); - } - - let padding_len = bytes[0] as usize; - if (padding_len + 1) >= bytes.len() { - return Err(CryptoError::EncodingError); - } - - Ok(&bytes[1 + padding_len..]) +fn unpad_bytes(bytes: &[u8]) -> &[u8] { + // this unwrap is safe, the input is always at least 1 byte long + #[allow(clippy::unwrap_used)] + let pad_len = *bytes.last().unwrap() as usize; + bytes[..bytes.len() - pad_len].as_ref() } #[cfg(test)] From df9327ccac703e7eb6a33db957b07c4f9bb3ab69 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 26 Mar 2025 21:23:56 +0100 Subject: [PATCH 018/152] Rename encrypt/decrypt to encapsulate/decapsulate and remove asymmetric keyencryptable trait impl --- .../bitwarden-core/src/auth/auth_request.rs | 10 +-- crates/bitwarden-core/src/auth/tde.rs | 2 +- crates/bitwarden-core/src/mobile/crypto.rs | 4 +- .../src/enc_string/asymmetric.rs | 56 ++++++------- .../src/keys/asymmetric_crypto_key.rs | 11 +-- .../bitwarden-crypto/src/keys/device_key.rs | 4 +- crates/bitwarden-crypto/src/store/context.rs | 2 +- .../src/traits/decryptable.rs | 23 +----- .../src/traits/encryptable.rs | 80 +------------------ 9 files changed, 45 insertions(+), 147 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 3f1d30e10..ac1a5f1d8 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -4,7 +4,7 @@ use bitwarden_crypto::{ AsymmetricPublicCryptoKey, CryptoError, }; #[cfg(feature = "internal")] -use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; +use bitwarden_crypto::{EncString, SymmetricCryptoKey}; use thiserror::Error; #[cfg(feature = "internal")] @@ -54,7 +54,7 @@ pub(crate) fn auth_request_decrypt_user_key( user_key: AsymmetricEncString, ) -> Result { let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut key: Vec = user_key.decrypt_with_key(&key)?; + let mut key: Vec = user_key.decapsulate_key_unsigned(&key)?; Ok(SymmetricCryptoKey::try_from(key.as_mut_slice())?) } @@ -69,7 +69,7 @@ pub(crate) fn auth_request_decrypt_master_key( use bitwarden_crypto::MasterKey; let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut master_key: Vec = master_key.decrypt_with_key(&key)?; + let mut master_key: Vec = master_key.decapsulate_key_unsigned(&key)?; let master_key = MasterKey::try_from(master_key.as_mut_slice())?; Ok(master_key.decrypt_user_key(user_key)?) @@ -101,7 +101,7 @@ pub(crate) fn approve_auth_request( #[allow(deprecated)] let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; - Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + Ok(AsymmetricEncString::encapsulate_key_unsigned( &key.to_vec(), &public_key, )?) @@ -121,7 +121,7 @@ fn test_auth_request() { let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); - let encrypted = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret, &private_key).unwrap(); + let encrypted = AsymmetricEncString::encapsulate_key_unsigned(secret, &private_key).unwrap(); let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index 94b916457..aab772024 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -23,7 +23,7 @@ pub(super) fn make_register_tde_keys( let key_pair = user_key.make_key_pair()?; let admin_reset = - AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&user_key.0.to_vec(), &public_key)?; + AsymmetricEncString::encapsulate_key_unsigned(&user_key.0.to_vec(), &public_key)?; let device_key = if remember_device { Some(DeviceKey::trust_device(&user_key.0)?) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 7992ce95d..1f3b3dbb7 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -374,7 +374,7 @@ pub(super) fn enroll_admin_password_reset( #[allow(deprecated)] let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; - Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + Ok(AsymmetricEncString::encapsulate_key_unsigned( &key.to_vec(), &public_key, )?) @@ -737,7 +737,7 @@ mod tests { let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ="; let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap(); - let decrypted: Vec = encrypted.decrypt_with_key(&private_key).unwrap(); + let decrypted: Vec = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); let key_store = client.internal.get_key_store(); let ctx = key_store.context(); diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 8e9f84cf4..75cf5ab54 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -9,7 +9,7 @@ use super::{from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, rsa::encrypt_rsa2048_oaep_sha1, - AsymmetricCryptoKey, AsymmetricEncryptable, KeyDecryptable, + AsymmetricCryptoKey, AsymmetricEncryptable, }; // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop // macro expansion @@ -150,11 +150,11 @@ impl serde::Serialize for AsymmetricEncString { impl AsymmetricEncString { /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. - pub fn encrypt_rsa2048_oaep_sha1( + pub fn encapsulate_key_unsigned( data_dec: &[u8], - key: &dyn AsymmetricEncryptable, + encapsulation_key: &dyn AsymmetricEncryptable, ) -> Result { - let enc = encrypt_rsa2048_oaep_sha1(key.to_public_key(), data_dec)?; + let enc = encrypt_rsa2048_oaep_sha1(encapsulation_key.to_public_key(), data_dec)?; Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) } @@ -171,32 +171,32 @@ impl AsymmetricEncString { } } -impl KeyDecryptable> for AsymmetricEncString { - fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result> { +impl AsymmetricEncString { + pub fn decapsulate_key_unsigned( + &self, + decapsulation_key: &AsymmetricCryptoKey, + ) -> Result> { use AsymmetricEncString::*; match self { - Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), - Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::(), data), + Rsa2048_OaepSha256_B64 { data } => decapsulation_key + .key + .decrypt(Oaep::new::(), data), + Rsa2048_OaepSha1_B64 { data } => decapsulation_key + .key + .decrypt(Oaep::new::(), data), #[allow(deprecated)] - Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => { - key.key.decrypt(Oaep::new::(), data) - } + Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => decapsulation_key + .key + .decrypt(Oaep::new::(), data), #[allow(deprecated)] - Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => { - key.key.decrypt(Oaep::new::(), data) - } + Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => decapsulation_key + .key + .decrypt(Oaep::new::(), data), } .map_err(|_| CryptoError::KeyDecrypt) } } -impl KeyDecryptable for AsymmetricEncString { - fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { - let dec: Vec = self.decrypt_with_key(key)?; - String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) - } -} - /// Usually we wouldn't want to expose AsymmetricEncStrings in the API or the schemas. /// But during the transition phase we will expose endpoints using the AsymmetricEncString type. impl schemars::JsonSchema for AsymmetricEncString { @@ -213,7 +213,7 @@ impl schemars::JsonSchema for AsymmetricEncString { mod tests { use schemars::schema_for; - use super::{AsymmetricCryptoKey, AsymmetricEncString, KeyDecryptable}; + use super::{AsymmetricCryptoKey, AsymmetricEncString}; const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS @@ -252,8 +252,8 @@ XKZBokBGnjFnTnKcs7nv/O8= assert_eq!(enc_string.enc_type(), 3); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!".as_bytes()); } #[test] @@ -264,8 +264,8 @@ XKZBokBGnjFnTnKcs7nv/O8= assert_eq!(enc_string.enc_type(), 4); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!".as_bytes()); } #[test] @@ -276,8 +276,8 @@ XKZBokBGnjFnTnKcs7nv/O8= assert_eq!(enc_string.enc_type(), 6); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!".as_bytes()); } #[test] diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index a00a1f842..7de31ccf9 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -120,9 +120,7 @@ impl std::fmt::Debug for AsymmetricCryptoKey { mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use crate::{ - AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, KeyDecryptable, - }; + use crate::{AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey}; #[test] fn test_asymmetric_crypto_key() { @@ -215,11 +213,10 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); - let plaintext = "Hello, world!"; + let plaintext = "Hello, world!".as_bytes(); let encrypted = - AsymmetricEncString::encrypt_rsa2048_oaep_sha1(plaintext.as_bytes(), &public_key) - .unwrap(); - let decrypted: String = encrypted.decrypt_with_key(&private_key).unwrap(); + AsymmetricEncString::encapsulate_key_unsigned(plaintext, &public_key).unwrap(); + let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); assert_eq!(plaintext, decrypted); } diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index 11ae078a4..d3546c712 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -38,7 +38,7 @@ impl DeviceKey { let data = user_key.to_vec(); let protected_user_key = - AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&data, &device_private_key)?; + AsymmetricEncString::encapsulate_key_unsigned(&data, &device_private_key)?; let protected_device_public_key = device_private_key .to_public_der()? @@ -65,7 +65,7 @@ impl DeviceKey { let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?; - let dec: Vec = protected_user_key.decrypt_with_key(&device_private_key)?; + let dec: Vec = protected_user_key.decapsulate_key_unsigned(&device_private_key)?; let user_key = SymmetricCryptoKey::try_from(dec)?; Ok(user_key) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 22eaf7088..80a4dbb1d 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -385,7 +385,7 @@ impl KeyStoreContext<'_, Ids> { data: &[u8], ) -> Result { let key = self.get_asymmetric_key(key)?; - AsymmetricEncString::encrypt_rsa2048_oaep_sha1(data, key) + AsymmetricEncString::encapsulate_key_unsigned(data, key) } } diff --git a/crates/bitwarden-crypto/src/traits/decryptable.rs b/crates/bitwarden-crypto/src/traits/decryptable.rs index 20fd3658c..2cfeff148 100644 --- a/crates/bitwarden-crypto/src/traits/decryptable.rs +++ b/crates/bitwarden-crypto/src/traits/decryptable.rs @@ -1,4 +1,4 @@ -use crate::{store::KeyStoreContext, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds}; +use crate::{store::KeyStoreContext, CryptoError, EncString, KeyId, KeyIds}; /// A decryption operation that takes the input value and decrypts it into the output value. /// Implementations should generally consist of calling [Decryptable::decrypt] for all the fields of @@ -17,16 +17,6 @@ impl Decryptable> for EncString { } } -impl Decryptable> for AsymmetricEncString { - fn decrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result, CryptoError> { - ctx.decrypt_data_with_asymmetric_key(key, self) - } -} - impl Decryptable for EncString { fn decrypt( &self, @@ -38,17 +28,6 @@ impl Decryptable for EncString { } } -impl Decryptable for AsymmetricEncString { - fn decrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result { - let bytes: Vec = self.decrypt(ctx, key)?; - String::from_utf8(bytes).map_err(|_| CryptoError::InvalidUtf8String) - } -} - impl, Output> Decryptable> for Option { diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/traits/encryptable.rs index afc46ec08..5f8be3208 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/traits/encryptable.rs @@ -1,4 +1,4 @@ -use crate::{store::KeyStoreContext, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds}; +use crate::{store::KeyStoreContext, CryptoError, EncString, KeyId, KeyIds}; /// An encryption operation that takes the input value and encrypts it into the output value. /// Implementations should generally consist of calling [Encryptable::encrypt] for all the fields of @@ -17,16 +17,6 @@ impl Encryptable for &[u8] { } } -impl Encryptable for &[u8] { - fn encrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result { - ctx.encrypt_data_with_asymmetric_key(key, self) - } -} - impl Encryptable for Vec { fn encrypt( &self, @@ -37,16 +27,6 @@ impl Encryptable for Vec { } } -impl Encryptable for Vec { - fn encrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result { - ctx.encrypt_data_with_asymmetric_key(key, self) - } -} - impl Encryptable for &str { fn encrypt( &self, @@ -57,16 +37,6 @@ impl Encryptable for &str { } } -impl Encryptable for &str { - fn encrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result { - self.as_bytes().encrypt(ctx, key) - } -} - impl Encryptable for String { fn encrypt( &self, @@ -77,16 +47,6 @@ impl Encryptable for String { } } -impl Encryptable for String { - fn encrypt( - &self, - ctx: &mut KeyStoreContext, - key: Ids::Asymmetric, - ) -> Result { - self.as_bytes().encrypt(ctx, key) - } -} - impl, Output> Encryptable> for Option { @@ -178,44 +138,6 @@ mod tests { assert_eq!(str_data, str_decrypted); } - #[test] - fn test_encryptable_bytes_asymmetric() { - let store = test_store(); - let mut ctx = store.context(); - let key = TestAsymmKey::A(0); - - let vec_data = vec![1, 2, 3, 4, 5]; - let slice_data: &[u8] = &vec_data; - - let vec_encrypted = vec_data.encrypt(&mut ctx, key).unwrap(); - let slice_encrypted = slice_data.encrypt(&mut ctx, key).unwrap(); - - let vec_decrypted: Vec = vec_encrypted.decrypt(&mut ctx, key).unwrap(); - let slice_decrypted: Vec = slice_encrypted.decrypt(&mut ctx, key).unwrap(); - - assert_eq!(vec_data, vec_decrypted); - assert_eq!(slice_data, slice_decrypted); - } - - #[test] - fn test_encryptable_string_asymmetric() { - let store = test_store(); - let mut ctx = store.context(); - let key = TestAsymmKey::A(0); - - let string_data = "Hello, World!".to_string(); - let str_data: &str = string_data.as_str(); - - let string_encrypted = string_data.encrypt(&mut ctx, key).unwrap(); - let str_encrypted = str_data.encrypt(&mut ctx, key).unwrap(); - - let string_decrypted: String = string_encrypted.decrypt(&mut ctx, key).unwrap(); - let str_decrypted: String = str_encrypted.decrypt(&mut ctx, key).unwrap(); - - assert_eq!(string_data, string_decrypted); - assert_eq!(str_data, str_decrypted); - } - #[test] fn test_encryptable_option_some() { let store = test_store(); From 8df3dff6f7eebe4603987e3579b6f91767ba8255 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:25:36 +0100 Subject: [PATCH 019/152] Only allow symmetric keys for encapsulation --- .../bitwarden-core/src/auth/auth_request.rs | 16 ++--- crates/bitwarden-core/src/auth/tde.rs | 2 +- .../src/client/encryption_settings.rs | 2 +- crates/bitwarden-core/src/mobile/crypto.rs | 6 +- .../src/enc_string/asymmetric.rs | 40 +++++++----- .../src/keys/asymmetric_crypto_key.rs | 8 +-- .../bitwarden-crypto/src/keys/device_key.rs | 9 +-- .../bitwarden-crypto/src/keys/master_key.rs | 11 ++++ crates/bitwarden-crypto/src/store/context.rs | 62 +++++-------------- 9 files changed, 70 insertions(+), 86 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index ac1a5f1d8..a1a9c1a45 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -54,9 +54,8 @@ pub(crate) fn auth_request_decrypt_user_key( user_key: AsymmetricEncString, ) -> Result { let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut key: Vec = user_key.decapsulate_key_unsigned(&key)?; - - Ok(SymmetricCryptoKey::try_from(key.as_mut_slice())?) + let key: SymmetricCryptoKey = user_key.decapsulate_key_unsigned(&key)?; + Ok(key) } /// Decrypt the user key using the private key generated previously. @@ -69,8 +68,8 @@ pub(crate) fn auth_request_decrypt_master_key( use bitwarden_crypto::MasterKey; let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut master_key: Vec = master_key.decapsulate_key_unsigned(&key)?; - let master_key = MasterKey::try_from(master_key.as_mut_slice())?; + let master_key: SymmetricCryptoKey = master_key.decapsulate_key_unsigned(&key)?; + let master_key = MasterKey::try_from(&master_key)?; Ok(master_key.decrypt_user_key(user_key)?) } @@ -102,7 +101,7 @@ pub(crate) fn approve_auth_request( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encapsulate_key_unsigned( - &key.to_vec(), + &key, &public_key, )?) } @@ -117,15 +116,16 @@ fn test_auth_request() { 96, 31, 114, 127, 212, 187, 167, 110, 205, 116, 198, 243, 218, 72, 137, 53, 248, 43, 255, 67, 35, 61, 245, 93, ]; + let secret = secret.to_vec(); let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); - let encrypted = AsymmetricEncString::encapsulate_key_unsigned(secret, &private_key).unwrap(); + let encrypted = AsymmetricEncString::encapsulate_key_unsigned(&SymmetricCryptoKey::try_from(secret.clone()).unwrap(), &private_key).unwrap(); let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); - assert_eq!(&decrypted.to_vec(), secret); + assert_eq!(decrypted.to_vec(), secret); } #[cfg(test)] diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index aab772024..12ced0177 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -23,7 +23,7 @@ pub(super) fn make_register_tde_keys( let key_pair = user_key.make_key_pair()?; let admin_reset = - AsymmetricEncString::encapsulate_key_unsigned(&user_key.0.to_vec(), &public_key)?; + AsymmetricEncString::encapsulate_key_unsigned(&user_key.0, &public_key)?; let device_key = if remember_device { Some(DeviceKey::trust_device(&user_key.0)?) diff --git a/crates/bitwarden-core/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs index 775fa57be..7c2b3fb4f 100644 --- a/crates/bitwarden-core/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -111,7 +111,7 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - ctx.decrypt_symmetric_key_with_asymmetric_key( + ctx.decapsulate_symmetric_key_unsigned( AsymmetricKeyId::UserPrivateKey, SymmetricKeyId::Organization(org_id), &org_enc_key, diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 1f3b3dbb7..ff257d949 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -375,7 +375,7 @@ pub(super) fn enroll_admin_password_reset( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encapsulate_key_unsigned( - &key.to_vec(), + &key, &public_key, )?) } @@ -737,7 +737,7 @@ mod tests { let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ="; let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap(); - let decrypted: Vec = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); + let decrypted: SymmetricCryptoKey = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); let key_store = client.internal.get_key_store(); let ctx = key_store.context(); @@ -746,7 +746,7 @@ mod tests { .dangerous_get_symmetric_key(SymmetricKeyId::User) .unwrap(); - assert_eq!(&decrypted, &expected.to_vec()); + assert_eq!(&decrypted.to_vec(), &expected.to_vec()); } #[test] diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 75cf5ab54..728911111 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -9,7 +9,7 @@ use super::{from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, rsa::encrypt_rsa2048_oaep_sha1, - AsymmetricCryptoKey, AsymmetricEncryptable, + AsymmetricCryptoKey, AsymmetricEncryptable, SymmetricCryptoKey, }; // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop // macro expansion @@ -151,10 +151,10 @@ impl serde::Serialize for AsymmetricEncString { impl AsymmetricEncString { /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. pub fn encapsulate_key_unsigned( - data_dec: &[u8], + encapsulated_key: &SymmetricCryptoKey, encapsulation_key: &dyn AsymmetricEncryptable, ) -> Result { - let enc = encrypt_rsa2048_oaep_sha1(encapsulation_key.to_public_key(), data_dec)?; + let enc = encrypt_rsa2048_oaep_sha1(encapsulation_key.to_public_key(), &encapsulated_key.to_vec())?; Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) } @@ -175,9 +175,9 @@ impl AsymmetricEncString { pub fn decapsulate_key_unsigned( &self, decapsulation_key: &AsymmetricCryptoKey, - ) -> Result> { + ) -> Result { use AsymmetricEncString::*; - match self { + let mut key_data = match self { Rsa2048_OaepSha256_B64 { data } => decapsulation_key .key .decrypt(Oaep::new::(), data), @@ -193,7 +193,8 @@ impl AsymmetricEncString { .key .decrypt(Oaep::new::(), data), } - .map_err(|_| CryptoError::KeyDecrypt) + .map_err(|_| CryptoError::KeyDecrypt)?; + SymmetricCryptoKey::try_from(key_data.as_mut_slice()) } } @@ -211,7 +212,12 @@ impl schemars::JsonSchema for AsymmetricEncString { #[cfg(test)] mod tests { + use rsa::Oaep; use schemars::schema_for; + use sha1::Sha1; + use sha2::Sha256; + + use crate::{AsymmetricEncryptable, SymmetricCryptoKey}; use super::{AsymmetricCryptoKey, AsymmetricEncString}; @@ -246,38 +252,44 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_enc_string_rsa2048_oaep_sha256_b64() { - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "3.YFqzW9LL/uLjCnl0RRLtndzGJ1FV27mcwQwGjfJPOVrgCX9nJSUYCCDd0iTIyOZ/zRxG47b6L1Z3qgkEfcxjmrSBq60gijc3E2TBMAg7OCLVcjORZ+i1sOVOudmOPWro6uA8refMrg4lqbieDlbLMzjVEwxfi5WpcL876cD0vYyRwvLO3bzFrsE7x33HHHtZeOPW79RqMn5efsB5Dj9wVheC9Ix9AYDjbo+rjg9qR6guwKmS7k2MSaIQlrDR7yu8LP+ePtiSjx+gszJV5jQGfcx60dtiLQzLS/mUD+RmU7B950Bpx0H7x56lT5yXZbWK5YkoP6qd8B8D2aKbP68Ywg=="; + let key_pair = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "3.BfwZTwBYbU5WQ5X7Vm8yl0hYmHTRdkVACCRZYcqhcjicoaPVDEP03CIRmtnppu0aXOppoQzhw5S2OKTUaqoOGKZg7+PrmVEhjiUFfVAptInBD6XGHZ0Z3u3F+JY1E3xIFebOFiX7KLQ+7D0bJhBEnl8P7phmanKF3Cil5ayDGRpAjAsBHMwlNRKXy05YpYs3/x+V+zjlxVrBU9gYFCpacKUbxT51I8tf21ISqo6H9ZBwqDE2QUPhYJl5op7SJgySdd3YCKnsObXa8fFj2OwxGLAXJAyvF6qZyl08RO/ZYUOOOPlbC7ywXxAISw3qmrwxqpLSBqAm9BYPa/zxBnTHrA=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let mut test_key = vec![0u8; 64]; + let test_key = SymmetricCryptoKey::try_from(test_key.as_mut_slice()).unwrap(); assert_eq!(enc_string.enc_type(), 3); - let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!".as_bytes()); + let res = enc_string.decapsulate_key_unsigned(&key_pair).unwrap(); + assert_eq!(res, test_key); } #[test] fn test_enc_string_rsa2048_oaep_sha1_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; + let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let mut test_bytes = vec![0u8; 64]; + let test_key = SymmetricCryptoKey::try_from(test_bytes.as_mut_slice()).unwrap(); assert_eq!(enc_string.enc_type(), 4); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!".as_bytes()); + assert_eq!(res, test_key); } #[test] fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let mut test_bytes = vec![0u8; 64]; + let test_key = SymmetricCryptoKey::try_from(test_bytes.as_mut_slice()).unwrap(); assert_eq!(enc_string.enc_type(), 6); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!".as_bytes()); + assert_eq!(res, test_key); } #[test] diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 7de31ccf9..bbe3b5ce7 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -120,7 +120,7 @@ impl std::fmt::Debug for AsymmetricCryptoKey { mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use crate::{AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey}; + use crate::{AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, SymmetricCryptoKey}; #[test] fn test_asymmetric_crypto_key() { @@ -213,11 +213,11 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); - let plaintext = "Hello, world!".as_bytes(); + let raw_key = SymmetricCryptoKey::generate(&mut rand::thread_rng()); let encrypted = - AsymmetricEncString::encapsulate_key_unsigned(plaintext, &public_key).unwrap(); + AsymmetricEncString::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(plaintext, decrypted); + assert_eq!(raw_key, decrypted); } } diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index d3546c712..c0b06baac 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -34,11 +34,8 @@ impl DeviceKey { let device_private_key = AsymmetricCryptoKey::generate(&mut rng); - // Encrypt both the key and mac_key of the user key - let data = user_key.to_vec(); - let protected_user_key = - AsymmetricEncString::encapsulate_key_unsigned(&data, &device_private_key)?; + AsymmetricEncString::encapsulate_key_unsigned(&user_key, &device_private_key)?; let protected_device_public_key = device_private_key .to_public_der()? @@ -65,9 +62,7 @@ impl DeviceKey { let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?; - let dec: Vec = protected_user_key.decapsulate_key_unsigned(&device_private_key)?; - let user_key = SymmetricCryptoKey::try_from(dec)?; - + let user_key: SymmetricCryptoKey = protected_user_key.decapsulate_key_unsigned(&device_private_key)?; Ok(user_key) } diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 3f287e9dc..fad52e141 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -107,6 +107,17 @@ impl From for MasterKey { } } +impl TryFrom<&SymmetricCryptoKey> for MasterKey { + type Error = CryptoError; + + fn try_from(value: &SymmetricCryptoKey) -> Result { + match value { + SymmetricCryptoKey::Aes256CbcKey(key) => Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone()))), + _ => Err(CryptoError::InvalidKey), + } + } +} + /// Helper function to encrypt a user key with a master or pin key. pub(super) fn encrypt_user_key( master_key: &Pin>>, diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 80a4dbb1d..ccf131aed 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -3,14 +3,13 @@ use std::{ sync::{RwLockReadGuard, RwLockWriteGuard}, }; -use rsa::Oaep; use zeroize::Zeroizing; use super::KeyStoreInner; use crate::{ derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, - SymmetricCryptoKey, + SymmetricCryptoKey }; /// The context of a crypto operation using [super::KeyStore] @@ -182,40 +181,39 @@ impl KeyStoreContext<'_, Ids> { /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten /// * `encrypted_key` - The key to decrypt - pub fn decrypt_symmetric_key_with_asymmetric_key( + pub fn decapsulate_symmetric_key_unsigned( &mut self, - encryption_key: Ids::Asymmetric, + decapsulation_key: Ids::Asymmetric, new_key_id: Ids::Symmetric, - encrypted_key: &AsymmetricEncString, + encapsulated_key: &AsymmetricEncString, ) -> Result { - let mut new_key_material = - self.decrypt_data_with_asymmetric_key(encryption_key, encrypted_key)?; + let decapsulation_key = self.get_asymmetric_key(decapsulation_key)?; + let decapsulated_key = encapsulated_key.decapsulate_key_unsigned(decapsulation_key)?; #[allow(deprecated)] self.set_symmetric_key( new_key_id, - SymmetricCryptoKey::try_from(new_key_material.as_mut_slice())?, + decapsulated_key )?; // Returning the new key identifier for convenience Ok(new_key_id) } - /// Encrypt and return a symmetric key from the context by using an already existing asymmetric + /// Encapsulate and return a symmetric key from the context by using an already existing asymmetric /// key /// /// # Arguments /// - /// * `encryption_key` - The key id used to encrypt the `key_to_encrypt`. It must already exist + /// * `encapsulation_key` - The key id used to encrypt the `encapsulated_key`. It must already exist /// in the context - /// * `key_to_encrypt` - The key id to encrypt. It must already exist in the context - pub fn encrypt_symmetric_key_with_asymmetric_key( + /// * `encapsulated_key` - The key id to encrypt. It must already exist in the context + pub fn encapsulate_symmetric_key_unsigned( &self, - encryption_key: Ids::Asymmetric, - key_to_encrypt: Ids::Symmetric, + encapsulation_key: Ids::Asymmetric, + encapsulated_key: Ids::Symmetric, ) -> Result { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - self.encrypt_data_with_asymmetric_key(encryption_key, &key_to_encrypt.to_vec()) + AsymmetricEncString::encapsulate_key_unsigned(self.get_symmetric_key(encapsulated_key)?, self.get_asymmetric_key(encapsulation_key)?) } /// Returns `true` if the context has a symmetric key with the given identifier @@ -355,38 +353,6 @@ impl KeyStoreContext<'_, Ids> { SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key), } } - - pub(crate) fn decrypt_data_with_asymmetric_key( - &self, - key: Ids::Asymmetric, - data: &AsymmetricEncString, - ) -> Result> { - let key = self.get_asymmetric_key(key)?; - - use AsymmetricEncString::*; - match data { - Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), - Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::(), data), - #[allow(deprecated)] - Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => { - key.key.decrypt(Oaep::new::(), data) - } - #[allow(deprecated)] - Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => { - key.key.decrypt(Oaep::new::(), data) - } - } - .map_err(|_| CryptoError::KeyDecrypt) - } - - pub(crate) fn encrypt_data_with_asymmetric_key( - &self, - key: Ids::Asymmetric, - data: &[u8], - ) -> Result { - let key = self.get_asymmetric_key(key)?; - AsymmetricEncString::encapsulate_key_unsigned(data, key) - } } #[cfg(test)] From 16c1a845e1a3cfcb43150ab385fa92766b30dec6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:26:40 +0100 Subject: [PATCH 020/152] Remove unused imports --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 728911111..1ed041192 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -212,12 +212,9 @@ impl schemars::JsonSchema for AsymmetricEncString { #[cfg(test)] mod tests { - use rsa::Oaep; use schemars::schema_for; - use sha1::Sha1; - use sha2::Sha256; - use crate::{AsymmetricEncryptable, SymmetricCryptoKey}; + use crate::SymmetricCryptoKey; use super::{AsymmetricCryptoKey, AsymmetricEncString}; From 351394f297916390c2e39cbb33ddcb3f9ef3ee1a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:28:26 +0100 Subject: [PATCH 021/152] Cleanup --- crates/bitwarden-core/src/auth/auth_request.rs | 2 +- crates/bitwarden-core/src/mobile/crypto.rs | 2 +- crates/bitwarden-crypto/src/keys/device_key.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index a1a9c1a45..ec17a8fbb 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -101,7 +101,7 @@ pub(crate) fn approve_auth_request( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encapsulate_key_unsigned( - &key, + key, &public_key, )?) } diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index ff257d949..9eaa62e10 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -375,7 +375,7 @@ pub(super) fn enroll_admin_password_reset( let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; Ok(AsymmetricEncString::encapsulate_key_unsigned( - &key, + key, &public_key, )?) } diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index c0b06baac..7cd2cb7fa 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -35,7 +35,7 @@ impl DeviceKey { let device_private_key = AsymmetricCryptoKey::generate(&mut rng); let protected_user_key = - AsymmetricEncString::encapsulate_key_unsigned(&user_key, &device_private_key)?; + AsymmetricEncString::encapsulate_key_unsigned(user_key, &device_private_key)?; let protected_device_public_key = device_private_key .to_public_der()? From e221150b04272031862893f924662c8ee4326d14 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:28:38 +0100 Subject: [PATCH 022/152] Cargo fmt --- .../bitwarden-core/src/auth/auth_request.rs | 6 +++++- crates/bitwarden-core/src/auth/tde.rs | 3 +-- crates/bitwarden-core/src/mobile/crypto.rs | 3 ++- .../src/enc_string/asymmetric.rs | 8 +++++--- .../src/keys/asymmetric_crypto_key.rs | 4 +++- .../bitwarden-crypto/src/keys/device_key.rs | 3 ++- .../bitwarden-crypto/src/keys/master_key.rs | 4 +++- crates/bitwarden-crypto/src/store/context.rs | 20 +++++++++---------- 8 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index ec17a8fbb..b70fcf8e0 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -121,7 +121,11 @@ fn test_auth_request() { let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); - let encrypted = AsymmetricEncString::encapsulate_key_unsigned(&SymmetricCryptoKey::try_from(secret.clone()).unwrap(), &private_key).unwrap(); + let encrypted = AsymmetricEncString::encapsulate_key_unsigned( + &SymmetricCryptoKey::try_from(secret.clone()).unwrap(), + &private_key, + ) + .unwrap(); let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index 12ced0177..ef96d610a 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -22,8 +22,7 @@ pub(super) fn make_register_tde_keys( let user_key = UserKey::new(SymmetricCryptoKey::generate(&mut rng)); let key_pair = user_key.make_key_pair()?; - let admin_reset = - AsymmetricEncString::encapsulate_key_unsigned(&user_key.0, &public_key)?; + let admin_reset = AsymmetricEncString::encapsulate_key_unsigned(&user_key.0, &public_key)?; let device_key = if remember_device { Some(DeviceKey::trust_device(&user_key.0)?) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 9eaa62e10..e62e3e573 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -737,7 +737,8 @@ mod tests { let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ="; let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap(); - let decrypted: SymmetricCryptoKey = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); + let decrypted: SymmetricCryptoKey = + encrypted.decapsulate_key_unsigned(&private_key).unwrap(); let key_store = client.internal.get_key_store(); let ctx = key_store.context(); diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 1ed041192..0a4f6fa38 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -154,7 +154,10 @@ impl AsymmetricEncString { encapsulated_key: &SymmetricCryptoKey, encapsulation_key: &dyn AsymmetricEncryptable, ) -> Result { - let enc = encrypt_rsa2048_oaep_sha1(encapsulation_key.to_public_key(), &encapsulated_key.to_vec())?; + let enc = encrypt_rsa2048_oaep_sha1( + encapsulation_key.to_public_key(), + &encapsulated_key.to_vec(), + )?; Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) } @@ -214,9 +217,8 @@ impl schemars::JsonSchema for AsymmetricEncString { mod tests { use schemars::schema_for; - use crate::SymmetricCryptoKey; - use super::{AsymmetricCryptoKey, AsymmetricEncString}; + use crate::SymmetricCryptoKey; const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index bbe3b5ce7..45c0262f5 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -120,7 +120,9 @@ impl std::fmt::Debug for AsymmetricCryptoKey { mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use crate::{AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, SymmetricCryptoKey}; + use crate::{ + AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, SymmetricCryptoKey, + }; #[test] fn test_asymmetric_crypto_key() { diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index 7cd2cb7fa..fffdfadd4 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -62,7 +62,8 @@ impl DeviceKey { let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?; - let user_key: SymmetricCryptoKey = protected_user_key.decapsulate_key_unsigned(&device_private_key)?; + let user_key: SymmetricCryptoKey = + protected_user_key.decapsulate_key_unsigned(&device_private_key)?; Ok(user_key) } diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index fad52e141..4ba4174ca 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -112,7 +112,9 @@ impl TryFrom<&SymmetricCryptoKey> for MasterKey { fn try_from(value: &SymmetricCryptoKey) -> Result { match value { - SymmetricCryptoKey::Aes256CbcKey(key) => Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone()))), + SymmetricCryptoKey::Aes256CbcKey(key) => { + Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone()))) + } _ => Err(CryptoError::InvalidKey), } } diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index ccf131aed..5f51cb6a3 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -9,7 +9,7 @@ use super::KeyStoreInner; use crate::{ derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, - SymmetricCryptoKey + SymmetricCryptoKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -191,29 +191,29 @@ impl KeyStoreContext<'_, Ids> { let decapsulated_key = encapsulated_key.decapsulate_key_unsigned(decapsulation_key)?; #[allow(deprecated)] - self.set_symmetric_key( - new_key_id, - decapsulated_key - )?; + self.set_symmetric_key(new_key_id, decapsulated_key)?; // Returning the new key identifier for convenience Ok(new_key_id) } - /// Encapsulate and return a symmetric key from the context by using an already existing asymmetric - /// key + /// Encapsulate and return a symmetric key from the context by using an already existing + /// asymmetric key /// /// # Arguments /// - /// * `encapsulation_key` - The key id used to encrypt the `encapsulated_key`. It must already exist - /// in the context + /// * `encapsulation_key` - The key id used to encrypt the `encapsulated_key`. It must already + /// exist in the context /// * `encapsulated_key` - The key id to encrypt. It must already exist in the context pub fn encapsulate_symmetric_key_unsigned( &self, encapsulation_key: Ids::Asymmetric, encapsulated_key: Ids::Symmetric, ) -> Result { - AsymmetricEncString::encapsulate_key_unsigned(self.get_symmetric_key(encapsulated_key)?, self.get_asymmetric_key(encapsulation_key)?) + AsymmetricEncString::encapsulate_key_unsigned( + self.get_symmetric_key(encapsulated_key)?, + self.get_asymmetric_key(encapsulation_key)?, + ) } /// Returns `true` if the context has a symmetric key with the given identifier From ccf04c35853c681ebc2b2ba7a5285d3b38525829 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:31:04 +0100 Subject: [PATCH 023/152] Add comments --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 0a4f6fa38..b2f10a438 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -149,7 +149,8 @@ impl serde::Serialize for AsymmetricEncString { } impl AsymmetricEncString { - /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. + /// Encapsulate a symmetric key, to be shared asymmetrically. Produces a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. + /// Note, this does not sign the data and thus does not guarantee sender authenticity. pub fn encapsulate_key_unsigned( encapsulated_key: &SymmetricCryptoKey, encapsulation_key: &dyn AsymmetricEncryptable, @@ -175,6 +176,8 @@ impl AsymmetricEncString { } impl AsymmetricEncString { + /// Decapsulate a symmetric key, shared asymmetrically. + /// Note: The shared key does not have a sender signature and sender authenticity is not guaranteed. pub fn decapsulate_key_unsigned( &self, decapsulation_key: &AsymmetricCryptoKey, From cce0a959092122deaec74143b2dd85b4ee337ed4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:34:55 +0100 Subject: [PATCH 024/152] Fix tests --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index b2f10a438..72688cf2e 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -283,7 +283,7 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; + let enc_str: &str = "6.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); let mut test_bytes = vec![0u8; 64]; From b075993aaee8ec02a5ba12d091685a1337ef8e97 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:37:27 +0100 Subject: [PATCH 025/152] Cargo fmt --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 72688cf2e..739f54b59 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -149,8 +149,9 @@ impl serde::Serialize for AsymmetricEncString { } impl AsymmetricEncString { - /// Encapsulate a symmetric key, to be shared asymmetrically. Produces a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. - /// Note, this does not sign the data and thus does not guarantee sender authenticity. + /// Encapsulate a symmetric key, to be shared asymmetrically. Produces a + /// [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. Note, this does not sign the data + /// and thus does not guarantee sender authenticity. pub fn encapsulate_key_unsigned( encapsulated_key: &SymmetricCryptoKey, encapsulation_key: &dyn AsymmetricEncryptable, @@ -176,8 +177,9 @@ impl AsymmetricEncString { } impl AsymmetricEncString { - /// Decapsulate a symmetric key, shared asymmetrically. - /// Note: The shared key does not have a sender signature and sender authenticity is not guaranteed. + /// Decapsulate a symmetric key, shared asymmetrically. + /// Note: The shared key does not have a sender signature and sender authenticity is not + /// guaranteed. pub fn decapsulate_key_unsigned( &self, decapsulation_key: &AsymmetricCryptoKey, From 20420e74e3fbf215a75b9618bc8f1faf276106b7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:45:12 +0100 Subject: [PATCH 026/152] Rename key encapsulation function --- crates/bitwarden-core/src/client/encryption_settings.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-core/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs index 7c2b3fb4f..7d3825d26 100644 --- a/crates/bitwarden-core/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -111,7 +111,7 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - ctx.decapsulate_symmetric_key_unsigned( + ctx.decapsulate_key_unsigned( AsymmetricKeyId::UserPrivateKey, SymmetricKeyId::Organization(org_id), &org_enc_key, diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 5f51cb6a3..6645044c4 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -181,7 +181,7 @@ impl KeyStoreContext<'_, Ids> { /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten /// * `encrypted_key` - The key to decrypt - pub fn decapsulate_symmetric_key_unsigned( + pub fn decapsulate_key_unsigned( &mut self, decapsulation_key: Ids::Asymmetric, new_key_id: Ids::Symmetric, @@ -205,7 +205,7 @@ impl KeyStoreContext<'_, Ids> { /// * `encapsulation_key` - The key id used to encrypt the `encapsulated_key`. It must already /// exist in the context /// * `encapsulated_key` - The key id to encrypt. It must already exist in the context - pub fn encapsulate_symmetric_key_unsigned( + pub fn encapsulate_key_unsigned( &self, encapsulation_key: Ids::Asymmetric, encapsulated_key: Ids::Symmetric, From 96094cfd9e884d636d952e95d75ec916cb5edfef Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 27 Mar 2025 15:46:25 +0100 Subject: [PATCH 027/152] Fix comment --- crates/bitwarden-crypto/src/store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 55520d206..62b3650df 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -153,7 +153,7 @@ impl KeyStore { /// If you want to access the key material to encrypt it or derive a new key from it, we /// provide functions for that: /// - [KeyStoreContext::encrypt_symmetric_key_with_symmetric_key] - /// - [KeyStoreContext::encrypt_symmetric_key_with_asymmetric_key] + /// - [KeyStoreContext::encapsulate_key_unsigned] /// - [KeyStoreContext::derive_shareable_key] pub fn context(&'_ self) -> KeyStoreContext<'_, Ids> { KeyStoreContext { From fbddd87f1b6001b823e4a9e655ba4c9b7bb2a7f3 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 13:01:45 +0100 Subject: [PATCH 028/152] Fix build and cleanup --- crates/bitwarden-crypto/src/cose.rs | 17 +-- .../src/enc_string/symmetric.rs | 114 +++++++++--------- .../bitwarden-crypto/src/keys/master_key.rs | 12 +- crates/bitwarden-crypto/src/keys/mod.rs | 2 +- crates/bitwarden-crypto/src/keys/pin_key.rs | 6 +- .../src/keys/symmetric_crypto_key.rs | 59 ++++++--- crates/bitwarden-crypto/src/lib.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 35 +++--- .../src/pure_crypto.rs | 35 ++++-- 9 files changed, 156 insertions(+), 126 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index bedde5fe7..2d1e0e20a 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -1,13 +1,8 @@ -/** - * This file contains private-use constants for COSE encoded key types and algorithms. - */ -use coset::iana; +//! This file contains private-use constants for COSE encoded key types and algorithms. +//! Standardized values from https://www.iana.org/assignments/cose/cose.xhtml should always be preferred +//! unless there is a specific reason to use a private-use value. +// XChaCha20 (https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03) is used over ChaCha20 +// to be able to randomly generate nonces, and to not have to worry about key wearout. Since +// the draft was never published as an RFC, we use a private-use value for the algorithm. pub(crate) const XCHACHA20_POLY1305: i64 = -70000; - -pub(crate) const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; - -pub enum ContentFormat { - PaddedUtf8, - Pkcs8, -} \ No newline at end of file diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 10817e9dd..1bf33fd74 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -1,12 +1,14 @@ use std::{fmt::Display, str::FromStr}; use base64::{engine::general_purpose::STANDARD, Engine}; -use coset::{iana::CoapContentFormat, CborSerializable}; +use coset::CborSerializable; use serde::Deserialize; use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ - cose::{self, ContentFormat}, error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key + cose, + error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, + Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key, }; #[cfg(feature = "wasm")] @@ -51,7 +53,10 @@ export type EncString = string; #[allow(unused, non_camel_case_types)] pub enum EncString { /// 0 - AesCbc256_B64 { iv: [u8; 16], data: Vec }, + AesCbc256_B64 { + iv: [u8; 16], + data: Vec, + }, /// 1 was the now removed `AesCbc128_HmacSha256_B64`. /// 2 AesCbc256_HmacSha256_B64 { @@ -134,9 +139,9 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } - 7 => { - Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: buf[1..].to_vec() }) - } + 7 => Ok(EncString::XChaCha20_Poly1305_Cose_B64 { + data: buf[1..].to_vec(), + }), _ => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), parts: 1, @@ -162,9 +167,7 @@ impl EncString { buf.extend_from_slice(mac); buf.extend_from_slice(data); } - EncString::XChaCha20_Poly1305_Cose_B64 { - data, - } => { + EncString::XChaCha20_Poly1305_Cose_B64 { data } => { buf = Vec::with_capacity(1 + data.len()); buf.push(self.enc_type()); buf.extend_from_slice(data); @@ -192,9 +195,7 @@ impl Display for EncString { Ok(()) } - EncString::XChaCha20_Poly1305_Cose_B64 { - data, - } => { + EncString::XChaCha20_Poly1305_Cose_B64 { data } => { write!(f, "{}.{}", self.enc_type(), STANDARD.encode(data))?; Ok(()) @@ -222,8 +223,6 @@ impl serde::Serialize for EncString { } impl EncString { - const XCHACHA20_PAD_BLOCK_SIZE: usize = 24; - pub(crate) fn encrypt_aes256_hmac( data_dec: &[u8], key: &Aes256CbcHmacKey, @@ -236,10 +235,8 @@ impl EncString { pub(crate) fn encrypt_xchacha20_poly1305( data_dec: &[u8], key: &XChaCha20Poly1305Key, - content_format: ContentFormat, ) -> Result { - let mut protected_header = coset::HeaderBuilder::new() - .build(); + let mut protected_header = coset::HeaderBuilder::new().build(); protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305)); let mut nonce = [0u8; 24]; @@ -256,14 +253,15 @@ impl EncString { ); nonce.copy_from_slice(ciphertext.nonce.as_slice()); Ok(ciphertext.ciphertext) - }).map_err(|_a: CryptoError| CryptoError::EncodingError)? - .unprotected(coset::HeaderBuilder::new() - .iv(nonce.to_vec()) - .build()) + }) + .map_err(|_a: CryptoError| CryptoError::EncodingError)? + .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) .build(); Ok(EncString::XChaCha20_Poly1305_Cose_B64 { - data: cose_encrypt0.to_vec().map_err(|_| CryptoError::EncodingError)?, + data: cose_encrypt0 + .to_vec() + .map_err(|_| CryptoError::EncodingError)?, }) } @@ -282,8 +280,7 @@ impl KeyEncryptable for &[u8] { match key { SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key), SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => { - let padded_data = pad_bytes(self, EncString::XCHACHA20_PAD_BLOCK_SIZE); - EncString::encrypt_xchacha20_poly1305(&padded_data, inner_key, CoapContentFormat::OctetStream) + EncString::encrypt_xchacha20_poly1305(self, inner_key) } SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, @@ -303,29 +300,27 @@ impl KeyDecryptable> for EncString { SymmetricCryptoKey::Aes256CbcHmacKey(key), ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), ( - EncString::XChaCha20_Poly1305_Cose_B64 { - data, - }, + EncString::XChaCha20_Poly1305_Cose_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { - // parse cose - let msg = coset::CoseEncrypt0::from_slice(data.as_slice()).map_err(|_| { - CryptoError::EncString(EncStringParseError::InvalidEncoding) - })?; - let decrypted_message = msg.decrypt(&[], |data, aad| { - let nonce = msg.protected.header.iv.as_slice(); + let msg = coset::CoseEncrypt0::from_slice(data.as_slice()) + .map_err(|_| CryptoError::EncString(EncStringParseError::InvalidEncoding))?; + let decrypted_message = msg + .decrypt(&[], |data, aad| { + let nonce = msg.unprotected.iv.as_slice(); crate::xchacha20::decrypt_xchacha20_poly1305( - nonce.try_into().map_err(|_| CryptoError::EncodingError)?, - key.enc_key - .as_slice() - .try_into() - .expect("XChaChaPoly1305 key is 32 bytes long"), - data, - aad - ).map_err(|_| CryptoError::EncodingError) - } - ).map_err(|_| CryptoError::EncodingError)?; - Ok(unpad_bytes(&decrypted_message)?.to_vec()) + nonce.try_into().map_err(|_| CryptoError::EncodingError)?, + key.enc_key + .as_slice() + .try_into() + .expect("XChaChaPoly1305 key is 32 bytes long"), + data, + aad, + ) + .map_err(|_| CryptoError::EncodingError) + }) + .map_err(|_| CryptoError::EncodingError)?; + Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), } @@ -363,23 +358,9 @@ impl schemars::JsonSchema for EncString { } } -/// Pads bytes to a minimum length using PKCS7-like padding -fn pad_bytes(bytes: &mut Vec, block_size: usize) { - let padding_len = block_size - (bytes.len() % block_size); - let padded_length = padding_len + bytes.len(); - bytes.resize(padded_length, padding_len as u8); -} - -// Unpads the bytes -fn unpad_bytes(bytes: &[u8]) -> &[u8] { - // this unwrap is safe, the input is always at least 1 byte long - #[allow(clippy::unwrap_used)] - let pad_len = *bytes.last().unwrap() as usize; - bytes[..bytes.len() - pad_len].as_ref() -} - #[cfg(test)] mod tests { + use generic_array::GenericArray; use schemars::schema_for; use super::EncString; @@ -387,6 +368,21 @@ mod tests { derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; + #[test] + fn test_enc_roundtrip_xchacha20() { + let key_id = [0u8; 24]; + let enc_key = [0u8; 32]; + let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key { + key_id, + enc_key: Box::pin(*GenericArray::from_slice(&enc_key.as_slice())), + }); + + let test_string = "encrypted_test_string"; + let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap(); + let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); + assert_eq!(decrypted_str, test_string); + } + #[test] fn test_enc_string_roundtrip() { let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test")); diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index ca1aacfb6..5a08af135 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -138,7 +138,9 @@ pub(super) fn decrypt_user_key( user_key.decrypt_with_key(&stretched_key)? } EncString::XChaCha20_Poly1305_Cose_B64 { .. } => { - return Err(CryptoError::OperationNotSupported(crate::error::UnsupportedOperation::EncryptionNotImplementedForKey)); + return Err(CryptoError::OperationNotSupported( + crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, + )); } }; @@ -146,10 +148,7 @@ pub(super) fn decrypt_user_key( } /// Generate a new random user key and encrypt it with the master key. -fn make_user_key( - rng: impl rand::RngCore, - master_key: &MasterKey, -) -> Result<(UserKey, EncString)> { +fn make_user_key(rng: impl rand::RngCore, master_key: &MasterKey) -> Result<(UserKey, EncString)> { let user_key = SymmetricCryptoKey::generate_internal(rng, false); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) @@ -225,8 +224,7 @@ mod tests { 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, ] .into(), - ) - ) + )) .into(); let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap(); diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 73794ebce..ac3792806 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -20,8 +20,8 @@ mod device_key; pub use device_key::{DeviceKey, TrustDeviceResponse}; mod pin_key; pub use pin_key::PinKey; -mod key_id; mod kdf; +mod key_id; pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 0a0e1bbdd..83a3d9a72 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -22,12 +22,12 @@ impl PinKey { /// /// @param user_key: The user key to encrypt pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { - encrypt_user_key(&self.0.0, user_key) + encrypt_user_key(&self.0 .0, user_key) } /// Decrypt the users user key pub fn decrypt_user_key(&self, user_key: EncString) -> Result { - decrypt_user_key(&self.0.0, user_key) + decrypt_user_key(&self.0 .0, user_key) } } @@ -35,7 +35,7 @@ impl CryptoKey for PinKey {} impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &PinKey) -> Result { - let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&key.0.0)?); + let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&key.0 .0)?); self.encrypt_with_key(&stretched_key) } } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 75f69c6df..871b97693 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -95,7 +95,7 @@ impl SymmetricCryptoKey { let mut rng = rand::thread_rng(); Self::generate_internal(&mut rng, false) } - + /** * Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] */ @@ -106,7 +106,8 @@ impl SymmetricCryptoKey { /// Generate a new random [SymmetricCryptoKey] /// @param rng: A random number generator - /// @param xchacha: If true, generate an XChaCha20Poly1305 key, otherwise generate an AES256_CBC_HMAC key + /// @param xchacha: If true, generate an XChaCha20Poly1305 key, otherwise generate an + /// AES256_CBC_HMAC key pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha20: bool) -> Self { if !xchacha20 { let mut enc_key = Box::pin(GenericArray::::default()); @@ -119,14 +120,17 @@ impl SymmetricCryptoKey { } else { let mut enc_key = Box::pin(GenericArray::::default()); rng.fill(enc_key.as_mut_slice()); - SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key, key_id: *KeyId::generate().as_bytes() }) + SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { + enc_key, + key_id: *KeyId::generate().as_bytes(), + }) } } /** - * Encodes the key to a byte array representation. This can be used for storage and transmission - * in the old byte array format. When the wrapping key is a COSE key, then COSE MUST be used to encode - * the key. + * Encodes the key to a byte array representation. This can be used for storage and + * transmission in the old byte array format. When the wrapping key is a COSE key, then + * COSE MUST be used to encode the key. */ pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); @@ -159,8 +163,13 @@ impl SymmetricCryptoKey { .add_key_op(iana::KeyOperation::WrapKey) .add_key_op(iana::KeyOperation::UnwrapKey) .build(); - cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305)); - cose_key.to_vec().map_err(|_| CryptoError::InvalidKey).expect("Failed to encode key") + cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse( + cose::XCHACHA20_POLY1305, + )); + cose_key + .to_vec() + .map_err(|_| CryptoError::InvalidKey) + .expect("Failed to encode key") } } } @@ -234,8 +243,8 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { Ok(SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey { enc_key })) } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN { let unpadded_value = unpad_key(value); - let cose_key = coset::CoseKey::from_slice(unpadded_value) - .map_err(|_| CryptoError::InvalidKey)?; + let cose_key = + coset::CoseKey::from_slice(unpadded_value).map_err(|_| CryptoError::InvalidKey)?; parse_cose_key(&cose_key) } else { Err(CryptoError::InvalidKeyLen) @@ -247,20 +256,34 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { } fn parse_cose_key(cose_key: &coset::CoseKey) -> Result { - let key_bytes = cose_key.params.iter().find_map(|(label, value)| { - if let (Label::Int(cose::SYMMETRIC_KEY), ciborium::Value::Bytes(bytes)) = (label, value) { - Some(bytes) - } else { - None - } - }).ok_or(CryptoError::InvalidKey)?; + let key_bytes = cose_key + .params + .iter() + .find_map(|(label, value)| { + const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; + if let (Label::Int(SYMMETRIC_KEY), ciborium::Value::Bytes(bytes)) = (label, value) { + Some(bytes) + } else { + None + } + }) + .ok_or(CryptoError::InvalidKey)?; match cose_key.alg.clone().ok_or(CryptoError::InvalidKey)? { coset::RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305) => { if key_bytes.len() == 32 { let mut enc_key = Box::pin(GenericArray::::default()); enc_key.copy_from_slice(key_bytes); - Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key, key_id: cose_key.key_id.clone().try_into().map_err(|_| CryptoError::InvalidKey)? })) + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( + XChaCha20Poly1305Key { + enc_key, + key_id: cose_key + .key_id + .clone() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?, + }, + )) } else { Err(CryptoError::InvalidKey) } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 30b8e0274..f3ed02f31 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -90,8 +90,8 @@ mod wordlist; pub use wordlist::EFF_LONG_WORD_LIST; mod store; pub use store::{KeyStore, KeyStoreContext}; -mod traits; mod cose; +mod traits; mod xchacha20; pub use traits::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index b02d74b42..7455056a8 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -8,9 +8,9 @@ use zeroize::Zeroizing; use super::KeyStoreInner; use crate::{ - derive_shareable_key, error::UnsupportedOperation, - store::backend::StoreBackend, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, - KeyId, KeyIds, Result, SymmetricCryptoKey, + derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, + AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, + SymmetricCryptoKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -171,26 +171,31 @@ impl KeyStoreContext<'_, Ids> { ) -> Result { let wrapping_key = self.get_symmetric_key(encryption_key)?; match wrapping_key { - // These keys wrap directly by encrypting the key bytes of the inner key, with padding applied in case it is needed + // These keys wrap directly by encrypting the key bytes of the inner key, with padding + // applied in case it is needed SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_encoded()) } - // These keys wrap using CBOR. The content type needs to indicate what the format of the inner key is + // These keys wrap using CBOR. The content type needs to indicate what the format of the + // inner key is SymmetricCryptoKey::XChaCha20Poly1305Key(k) => { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - match key_to_encrypt { - SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { + let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; + match key_to_encrypt { + SymmetricCryptoKey::Aes256CbcKey(_) + | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { let encoded_key = key_to_encrypt.to_encoded_raw(); - let encrypted = EncString::encrypt_xchacha20_poly1305(encoded_key.as_slice(), k, coset::iana::CoapContentFormat::OctetStream); + let encrypted = + EncString::encrypt_xchacha20_poly1305(encoded_key.as_slice(), k); encrypted } SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { let cose_encoded_key = key_to_encrypt.to_encoded_raw(); - let encrypted = EncString::encrypt_xchacha20_poly1305(cose_encoded_key.as_slice(), k, coset::iana::CoapContentFormat::CoseKey); + let encrypted = + EncString::encrypt_xchacha20_poly1305(cose_encoded_key.as_slice(), k); encrypted } - } + } } } } @@ -375,11 +380,9 @@ impl KeyStoreContext<'_, Ids> { UnsupportedOperation::EncryptionNotImplementedForKey, )), SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key), - SymmetricCryptoKey::XChaCha20Poly1305Key(key) => EncString::encrypt_xchacha20_poly1305( - data, - key, - coset::iana::CoapContentFormat::OctetStream, - ), + SymmetricCryptoKey::XChaCha20Poly1305Key(key) => { + EncString::encrypt_xchacha20_poly1305(data, key) + } } } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 53ae4b906..f35a24195 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use bitwarden_crypto::{ - CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey + CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey, }; use wasm_bindgen::prelude::*; @@ -59,7 +59,9 @@ impl PureCrypto { ) -> Result, CryptoError> { let masterkey = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; let encrypted_userkey = EncString::from_str(&encrypted_userkey)?; - let result = masterkey.decrypt_user_key(encrypted_userkey).map_err(|_| CryptoError::InvalidKey)?; + let result = masterkey + .decrypt_user_key(encrypted_userkey) + .map_err(|_| CryptoError::InvalidKey)?; Ok(result.to_encoded()) } @@ -76,7 +78,11 @@ impl PureCrypto { } pub fn generate_userkey(use_xchacha20: bool) -> Result, CryptoError> { - let key = if !use_xchacha20 { SymmetricCryptoKey::generate() } else { SymmetricCryptoKey::generate_xchacha20() }; + let key = if !use_xchacha20 { + SymmetricCryptoKey::generate() + } else { + SymmetricCryptoKey::generate_xchacha20() + }; Ok(key.to_encoded()) } } @@ -105,22 +111,29 @@ mod tests { fn test_symmetric_decrypt() { let enc_string = EncString::from_str(ENCRYPTED).unwrap(); - let result = PureCrypto::symmetric_decrypt(enc_string.to_string(), KEY_B64.as_bytes().to_vec()); + let result = + PureCrypto::symmetric_decrypt(enc_string.to_string(), KEY_B64.as_bytes().to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED); } #[test] fn test_symmetric_encrypt() { - let result = PureCrypto::symmetric_encrypt(DECRYPTED.as_bytes().to_vec(), KEY_B64.as_bytes().to_vec()); + let result = PureCrypto::symmetric_encrypt( + DECRYPTED.as_bytes().to_vec(), + KEY_B64.as_bytes().to_vec(), + ); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption } #[test] fn test_symmetric_round_trip() { - let encrypted = - PureCrypto::symmetric_encrypt(DECRYPTED.as_bytes().to_vec(), KEY_B64.as_bytes().to_vec()).unwrap(); + let encrypted = PureCrypto::symmetric_encrypt( + DECRYPTED.as_bytes().to_vec(), + KEY_B64.as_bytes().to_vec(), + ) + .unwrap(); let decrypted = PureCrypto::symmetric_decrypt(encrypted.clone(), KEY_B64.as_bytes().to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED); @@ -153,9 +166,11 @@ mod tests { KEY_B64.as_bytes().to_vec(), ) .unwrap(); - let decrypted = - PureCrypto::symmetric_decrypt_array_buffer(encrypted.clone(), KEY_B64.as_bytes().to_vec()) - .unwrap(); + let decrypted = PureCrypto::symmetric_decrypt_array_buffer( + encrypted.clone(), + KEY_B64.as_bytes().to_vec(), + ) + .unwrap(); assert_eq!(decrypted, DECRYPTED_BYTES); } } From d4dc234ef62c96abff7c5cc86b584ba3fd14318f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 13:51:13 +0100 Subject: [PATCH 029/152] Fix clippy --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 1bf33fd74..7fded84f8 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -374,7 +374,7 @@ mod tests { let enc_key = [0u8; 32]; let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key { key_id, - enc_key: Box::pin(*GenericArray::from_slice(&enc_key.as_slice())), + enc_key: Box::pin(*GenericArray::from_slice(enc_key.as_slice())), }); let test_string = "encrypted_test_string"; From d5ecbdbbb3c226b1960d05e28ee349aa6c75b966 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 13:55:24 +0100 Subject: [PATCH 030/152] Fix example --- crates/bitwarden-crypto/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/README.md b/crates/bitwarden-crypto/README.md index 2d5b40b83..4abf78329 100644 --- a/crates/bitwarden-crypto/README.md +++ b/crates/bitwarden-crypto/README.md @@ -16,7 +16,7 @@ secure. use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; async fn example() -> Result<(), CryptoError> { - let key = SymmetricCryptoKey::generate(rand::thread_rng()); + let key = SymmetricCryptoKey::generate(); let data = "Hello, World!".to_owned(); let encrypted = data.clone().encrypt_with_key(&key)?; From b0f3bbc22e1f12c246b7d94ff01bfb03ee787110 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 14:00:44 +0100 Subject: [PATCH 031/152] Remove unused dep --- Cargo.lock | 10 ---------- crates/bitwarden-crypto/Cargo.toml | 1 - 2 files changed, 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 841d195f1..7ffefb1e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,7 +439,6 @@ dependencies = [ "rsa", "schemars", "serde", - "serde_bytes", "serde_json", "sha1", "sha2", @@ -3437,15 +3436,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde_bytes" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.218" diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 49e8ed31a..dfa30b824 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -44,7 +44,6 @@ rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" schemars = { workspace = true } serde = { workspace = true } -serde_bytes = "0.11.15" sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" subtle = ">=2.5.0, <3.0" From fd6ee3858efc30b80adc57a58fc7e848df3dd5a8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 14:05:05 +0100 Subject: [PATCH 032/152] Remove unused error --- crates/bitwarden-crypto/src/error.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 6a0eec6a5..c5257c4d6 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -78,8 +78,6 @@ pub enum EncStringParseError { InvalidBase64(#[from] base64::DecodeError), #[error("Invalid length: expected {expected}, got {got}")] InvalidLength { expected: usize, got: usize }, - #[error("Invalid additional data")] - InvalidAdditionalData, #[error("Invalid encoding")] InvalidEncoding, } From e18b3df1e101e1cb856e9502cb88b7abbd1dc6d5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 28 Mar 2025 14:07:39 +0100 Subject: [PATCH 033/152] Update comment --- crates/bitwarden-crypto/src/cose.rs | 4 ++-- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 2d1e0e20a..5e05f0a29 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -1,8 +1,8 @@ //! This file contains private-use constants for COSE encoded key types and algorithms. -//! Standardized values from https://www.iana.org/assignments/cose/cose.xhtml should always be preferred +//! Standardized values from should always be preferred //! unless there is a specific reason to use a private-use value. -// XChaCha20 (https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03) is used over ChaCha20 +// XChaCha20 is used over ChaCha20 // to be able to randomly generate nonces, and to not have to worry about key wearout. Since // the draft was never published as an RFC, we use a private-use value for the algorithm. pub(crate) const XCHACHA20_POLY1305: i64 = -70000; diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index fffec7e18..2a9f654ab 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -305,7 +305,7 @@ impl std::fmt::Debug for SymmetricCryptoKey { /// Pad a key to a minimum length using PKCS7-like padding. /// The last N bytes of the padded bytes all have the value N. -/// For example, padded to size 4, the value [0,0] becomes [0,0,2,2]. +/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. fn pad_key(key_bytes: &mut Vec, min_length: usize) { // at least 1 byte of padding is required let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1); From c1c1dbb5a7cb1745942f4146749c3e720e3c431f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 2 Apr 2025 15:52:21 +0200 Subject: [PATCH 034/152] Simplify test code --- crates/bitwarden-core/src/auth/auth_request.rs | 3 +-- .../bitwarden-crypto/src/enc_string/asymmetric.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index b70fcf8e0..64a72700d 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -110,13 +110,12 @@ pub(crate) fn approve_auth_request( fn test_auth_request() { let request = new_auth_request("test@bitwarden.com").unwrap(); - let secret: &[u8] = &[ + let secret = vec![ 111, 32, 97, 169, 4, 241, 174, 74, 239, 206, 113, 86, 174, 68, 216, 238, 52, 85, 156, 27, 134, 149, 54, 55, 91, 147, 45, 130, 131, 237, 51, 31, 191, 106, 155, 14, 160, 82, 47, 40, 96, 31, 114, 127, 212, 187, 167, 110, 205, 116, 198, 243, 218, 72, 137, 53, 248, 43, 255, 67, 35, 61, 245, 93, ]; - let secret = secret.to_vec(); let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 739f54b59..d270e34b2 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -260,8 +260,8 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "3.BfwZTwBYbU5WQ5X7Vm8yl0hYmHTRdkVACCRZYcqhcjicoaPVDEP03CIRmtnppu0aXOppoQzhw5S2OKTUaqoOGKZg7+PrmVEhjiUFfVAptInBD6XGHZ0Z3u3F+JY1E3xIFebOFiX7KLQ+7D0bJhBEnl8P7phmanKF3Cil5ayDGRpAjAsBHMwlNRKXy05YpYs3/x+V+zjlxVrBU9gYFCpacKUbxT51I8tf21ISqo6H9ZBwqDE2QUPhYJl5op7SJgySdd3YCKnsObXa8fFj2OwxGLAXJAyvF6qZyl08RO/ZYUOOOPlbC7ywXxAISw3qmrwxqpLSBqAm9BYPa/zxBnTHrA=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); - let mut test_key = vec![0u8; 64]; - let test_key = SymmetricCryptoKey::try_from(test_key.as_mut_slice()).unwrap(); + let test_bytes = vec![0u8; 64]; + let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); assert_eq!(enc_string.enc_type(), 3); let res = enc_string.decapsulate_key_unsigned(&key_pair).unwrap(); @@ -274,12 +274,12 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); - let mut test_bytes = vec![0u8; 64]; - let test_key = SymmetricCryptoKey::try_from(test_bytes.as_mut_slice()).unwrap(); + let test_bytes = vec![0u8; 64]; + let test_bytes = SymmetricCryptoKey::try_from(test_bytes).unwrap(); assert_eq!(enc_string.enc_type(), 4); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, test_key); + assert_eq!(res, test_bytes); } #[test] @@ -288,8 +288,8 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "6.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); - let mut test_bytes = vec![0u8; 64]; - let test_key = SymmetricCryptoKey::try_from(test_bytes.as_mut_slice()).unwrap(); + let test_bytes = vec![0u8; 64]; + let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); assert_eq!(enc_string.enc_type(), 6); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); From d6a52d46ce87da2693b6806681ec9d37277868a0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 3 Apr 2025 14:12:11 +0200 Subject: [PATCH 035/152] Rename --- crates/bitwarden-crypto/src/store/context.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 6645044c4..9d836e619 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -176,19 +176,19 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `encryption_key` - The key id used to decrypt the `encrypted_key`. It must already exist + /// * `decapsulation_key` - The key id used to decrypt the `encrypted_key`. It must already exist /// in the context /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten - /// * `encrypted_key` - The key to decrypt + /// * `encapsulated_shared_key` - The symmetric key to decrypt pub fn decapsulate_key_unsigned( &mut self, decapsulation_key: Ids::Asymmetric, new_key_id: Ids::Symmetric, - encapsulated_key: &AsymmetricEncString, + encapsulated_shared_key: &AsymmetricEncString, ) -> Result { let decapsulation_key = self.get_asymmetric_key(decapsulation_key)?; - let decapsulated_key = encapsulated_key.decapsulate_key_unsigned(decapsulation_key)?; + let decapsulated_key = encapsulated_shared_key.decapsulate_key_unsigned(decapsulation_key)?; #[allow(deprecated)] self.set_symmetric_key(new_key_id, decapsulated_key)?; @@ -204,14 +204,14 @@ impl KeyStoreContext<'_, Ids> { /// /// * `encapsulation_key` - The key id used to encrypt the `encapsulated_key`. It must already /// exist in the context - /// * `encapsulated_key` - The key id to encrypt. It must already exist in the context + /// * `shared_key` - The key id to encrypt. It must already exist in the context pub fn encapsulate_key_unsigned( &self, encapsulation_key: Ids::Asymmetric, - encapsulated_key: Ids::Symmetric, + shared_key: Ids::Symmetric, ) -> Result { AsymmetricEncString::encapsulate_key_unsigned( - self.get_symmetric_key(encapsulated_key)?, + self.get_symmetric_key(shared_key)?, self.get_asymmetric_key(encapsulation_key)?, ) } From cc32629449f5a0c94cc537d663d2d0aa6d1e909e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 3 Apr 2025 14:45:41 +0200 Subject: [PATCH 036/152] Cargo fmt --- crates/bitwarden-crypto/src/store/context.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 9d836e619..54ef4bb8a 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -176,8 +176,8 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `decapsulation_key` - The key id used to decrypt the `encrypted_key`. It must already exist - /// in the context + /// * `decapsulation_key` - The key id used to decrypt the `encrypted_key`. It must already + /// exist in the context /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten /// * `encapsulated_shared_key` - The symmetric key to decrypt @@ -188,7 +188,8 @@ impl KeyStoreContext<'_, Ids> { encapsulated_shared_key: &AsymmetricEncString, ) -> Result { let decapsulation_key = self.get_asymmetric_key(decapsulation_key)?; - let decapsulated_key = encapsulated_shared_key.decapsulate_key_unsigned(decapsulation_key)?; + let decapsulated_key = + encapsulated_shared_key.decapsulate_key_unsigned(decapsulation_key)?; #[allow(deprecated)] self.set_symmetric_key(new_key_id, decapsulated_key)?; From 951664b7bdbbff3f719bc112b8ad8a8561fea9b1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 13:56:17 +0200 Subject: [PATCH 037/152] Add comment to unpad_key --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 2a9f654ab..50ba8333b 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -130,7 +130,7 @@ impl SymmetricCryptoKey { /** * Encodes the key to a byte array representation. This can be used for storage and * transmission in the old byte array format. When the wrapping key is a COSE key, then - * COSE MUST be used to encode the key. + * the returned Vec is a COSE encoded message. */ pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); @@ -313,7 +313,9 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { key_bytes.resize(padded_length, pad_bytes as u8); } -// Unpad a key +/// Unpad a key that is padded using the PKCS7-like padding defined by `pad_key`. +/// The last N bytes of the padded bytes all have the value N. +/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. fn unpad_key(key_bytes: &[u8]) -> &[u8] { // this unwrap is safe, the input is always at least 1 byte long #[allow(clippy::unwrap_used)] From e52e614192742c2c71bfb986d78fe381ec36ccbe Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 13:59:26 +0200 Subject: [PATCH 038/152] Add more docs to pad_key and unpad_key --- .../src/keys/symmetric_crypto_key.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 50ba8333b..b85920637 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -130,7 +130,7 @@ impl SymmetricCryptoKey { /** * Encodes the key to a byte array representation. This can be used for storage and * transmission in the old byte array format. When the wrapping key is a COSE key, then - * the returned Vec is a COSE encoded message. + * the returned Vec is a COSE encoded message. */ pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); @@ -306,6 +306,12 @@ impl std::fmt::Debug for SymmetricCryptoKey { /// Pad a key to a minimum length using PKCS7-like padding. /// The last N bytes of the padded bytes all have the value N. /// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. +/// +/// Keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable +/// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays +/// with no additional content format included in the encoding message. For this reason, the +/// padding is used to make sure that the byte representation uniquely separates the keys by +/// size of the byte array. fn pad_key(key_bytes: &mut Vec, min_length: usize) { // at least 1 byte of padding is required let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1); @@ -316,6 +322,12 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { /// Unpad a key that is padded using the PKCS7-like padding defined by `pad_key`. /// The last N bytes of the padded bytes all have the value N. /// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. +/// +/// keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable +/// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays +/// with no additional content format included in the encoding message. For this reason, the +/// padding is used to make sure that the byte representation uniquely separates the keys by +/// size of the byte array. fn unpad_key(key_bytes: &[u8]) -> &[u8] { // this unwrap is safe, the input is always at least 1 byte long #[allow(clippy::unwrap_used)] From f83641a2f1a01887391d198aa798620a6ec23d75 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:00:46 +0200 Subject: [PATCH 039/152] Fix capitalization --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index b85920637..f2b9e1b83 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -323,7 +323,7 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { /// The last N bytes of the padded bytes all have the value N. /// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. /// -/// keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable +/// Keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable /// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays /// with no additional content format included in the encoding message. For this reason, the /// padding is used to make sure that the byte representation uniquely separates the keys by From ca2c1fae2e5c89d652968d4b613bb60a9adb033b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:03:23 +0200 Subject: [PATCH 040/152] Further improve docs --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index f2b9e1b83..95ec811a0 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -311,7 +311,8 @@ impl std::fmt::Debug for SymmetricCryptoKey { /// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays /// with no additional content format included in the encoding message. For this reason, the /// padding is used to make sure that the byte representation uniquely separates the keys by -/// size of the byte array. +/// size of the byte array. The previous key types `SymmetricCryptoKey::Aes256CbcHmacKey` and +/// `SymmetricCryptoKey::Aes256CbcKey` are 64 and 32 bytes long respectively. fn pad_key(key_bytes: &mut Vec, min_length: usize) { // at least 1 byte of padding is required let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1); @@ -327,7 +328,8 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { /// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays /// with no additional content format included in the encoding message. For this reason, the /// padding is used to make sure that the byte representation uniquely separates the keys by -/// size of the byte array. +/// size of the byte array the previous key types `SymmetricCryptoKey::Aes256CbcHmacKey` and +/// `SymmetricCryptoKey::Aes256CbcKey` are 64 and 32 bytes long respectively. fn unpad_key(key_bytes: &[u8]) -> &[u8] { // this unwrap is safe, the input is always at least 1 byte long #[allow(clippy::unwrap_used)] From dbc47132e32572e290433c739e6c154c3e9b06fb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:07:10 +0200 Subject: [PATCH 041/152] Improve comment for to_encoded --- .../src/keys/symmetric_crypto_key.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 95ec811a0..159b7810d 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -127,11 +127,14 @@ impl SymmetricCryptoKey { } } - /** - * Encodes the key to a byte array representation. This can be used for storage and - * transmission in the old byte array format. When the wrapping key is a COSE key, then - * the returned Vec is a COSE encoded message. - */ + /// Encodes the key to a byte array representation, that is separated by size. + /// `SymmetricCryptoKey::Aes256CbcHmacKey` and `SymmetricCryptoKey::Aes256CbcKey` are + /// encoded as 64 and 32 bytes respectively. `SymmetricCryptoKey::XChaCha20Poly1305Key` + /// is encoded as at least 65 bytes, by using padding defined in `pad_key`. + /// + /// This can be used for storage and transmission in the old byte array format. + /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should + /// not use the byte representation but instead use the COSE key representation. pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); match self { From 75899e248ab6835c5582501919bbb1af86eeb3fc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:17:02 +0200 Subject: [PATCH 042/152] Update crates/bitwarden-crypto/src/keys/key_id.rs Co-authored-by: Matt Gibson --- crates/bitwarden-crypto/src/keys/key_id.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bitwarden-crypto/src/keys/key_id.rs b/crates/bitwarden-crypto/src/keys/key_id.rs index ca248faa8..8d0967a58 100644 --- a/crates/bitwarden-crypto/src/keys/key_id.rs +++ b/crates/bitwarden-crypto/src/keys/key_id.rs @@ -2,6 +2,10 @@ use rand::RngCore; pub(crate) struct KeyId([u8; 24]); +/// Fixed length identifiers for keys. +/// These are intended to be unique and constant per-key. +/// +/// Currently these are randomly generated 24 byte identifiers, which is considered safe to randomly generate with vanishingly small collision chance. However, the generation of IDs is an internal concern and may change in the future. impl KeyId { pub fn as_bytes(&self) -> &[u8; 24] { &self.0 From 38265a827e4e80f79173082e073ec799e3bfa9ed Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:18:03 +0200 Subject: [PATCH 043/152] Delete param docs --- crates/bitwarden-crypto/src/keys/pin_key.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 83a3d9a72..e069a4c62 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -19,8 +19,6 @@ impl PinKey { } /// Encrypt the users user key - /// - /// @param user_key: The user key to encrypt pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { encrypt_user_key(&self.0 .0, user_key) } From f7e75ac92885dfed280e719269d20bc623d8c388 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 14:33:43 +0200 Subject: [PATCH 044/152] Update cose encstring display impl --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 7fded84f8..f81966792 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -196,7 +196,11 @@ impl Display for EncString { Ok(()) } EncString::XChaCha20_Poly1305_Cose_B64 { data } => { - write!(f, "{}.{}", self.enc_type(), STANDARD.encode(data))?; + if let Ok(msg) = coset::CoseEncrypt0::from_slice(data.as_slice()) { + write!(f, "{}.{:?}", self.enc_type(), msg)?; + } else { + write!(f, "{}.{}", self.enc_type(), "Invalid Cose")?; + } Ok(()) } @@ -379,6 +383,7 @@ mod tests { let test_string = "encrypted_test_string"; let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap(); + println!("Cipher: {}", cipher); let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); assert_eq!(decrypted_str, test_string); } From 665223a2bf0bae410e4f799aa61c8bfa997eccb9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 16:03:18 +0200 Subject: [PATCH 045/152] Fix formatting --- crates/bitwarden-crypto/src/keys/key_id.rs | 4 +++- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/key_id.rs b/crates/bitwarden-crypto/src/keys/key_id.rs index 8d0967a58..8b17287f0 100644 --- a/crates/bitwarden-crypto/src/keys/key_id.rs +++ b/crates/bitwarden-crypto/src/keys/key_id.rs @@ -5,7 +5,9 @@ pub(crate) struct KeyId([u8; 24]); /// Fixed length identifiers for keys. /// These are intended to be unique and constant per-key. /// -/// Currently these are randomly generated 24 byte identifiers, which is considered safe to randomly generate with vanishingly small collision chance. However, the generation of IDs is an internal concern and may change in the future. +/// Currently these are randomly generated 24 byte identifiers, which is considered safe to randomly +/// generate with vanishingly small collision chance. However, the generation of IDs is an internal +/// concern and may change in the future. impl KeyId { pub fn as_bytes(&self) -> &[u8; 24] { &self.0 diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 159b7810d..aa69a1f65 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -131,7 +131,7 @@ impl SymmetricCryptoKey { /// `SymmetricCryptoKey::Aes256CbcHmacKey` and `SymmetricCryptoKey::Aes256CbcKey` are /// encoded as 64 and 32 bytes respectively. `SymmetricCryptoKey::XChaCha20Poly1305Key` /// is encoded as at least 65 bytes, by using padding defined in `pad_key`. - /// + /// /// This can be used for storage and transmission in the old byte array format. /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should /// not use the byte representation but instead use the COSE key representation. From 8cd4d7b7f169dc6a6079ef0bae919304c33ebdb1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 16:08:45 +0200 Subject: [PATCH 046/152] Fix formatting when displaying unparseable cose --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index f81966792..c79983a1c 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -199,7 +199,7 @@ impl Display for EncString { if let Ok(msg) = coset::CoseEncrypt0::from_slice(data.as_slice()) { write!(f, "{}.{:?}", self.enc_type(), msg)?; } else { - write!(f, "{}.{}", self.enc_type(), "Invalid Cose")?; + write!(f, "{}.INVALID_COSE", self.enc_type())?; } Ok(()) @@ -383,7 +383,6 @@ mod tests { let test_string = "encrypted_test_string"; let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap(); - println!("Cipher: {}", cipher); let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); assert_eq!(decrypted_str, test_string); } From 0efa7647850d77bfdc6822ad9154da80a2b844d8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:11:12 +0200 Subject: [PATCH 047/152] Update comment for decapsulate_key_unsigned --- crates/bitwarden-crypto/src/store/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 54ef4bb8a..2ddb3956f 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -172,7 +172,7 @@ impl KeyStoreContext<'_, Ids> { self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_vec()) } - /// Decrypt a symmetric key into the context by using an already existing asymmetric key + /// Decapsulate a symmetric key into the context by using an already existing asymmetric key /// /// # Arguments /// From 2e2c3f5eb012d0204f9f0a42ddc44aeb6763afbf Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:12:38 +0200 Subject: [PATCH 048/152] Update naming to be consistent with sdk guidelines --- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index c6d34b6c8..d151ef7cc 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -47,7 +47,7 @@ impl PureCrypto { .to_buffer() } - pub fn decrypt_userkey_with_masterpassword( + pub fn decrypt_user_key_with_master_password( encrypted_userkey: String, master_password: String, email: String, From e156847173fe585b7e4a13235416bdcd3717124d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:15:53 +0200 Subject: [PATCH 049/152] Split generate functions and fix namings --- .../src/pure_crypto.rs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index d151ef7cc..b50a5ac98 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -48,38 +48,37 @@ impl PureCrypto { } pub fn decrypt_user_key_with_master_password( - encrypted_userkey: String, + encrypted_user_key: String, master_password: String, email: String, kdf: Kdf, ) -> Result, CryptoError> { - let masterkey = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; - let encrypted_userkey = EncString::from_str(&encrypted_userkey)?; - let result = masterkey - .decrypt_user_key(encrypted_userkey) + let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; + let encrypted_user_key = EncString::from_str(&encrypted_user_key)?; + let result = master_key + .decrypt_user_key(encrypted_user_key) .map_err(|_| CryptoError::InvalidKey)?; Ok(result.to_encoded()) } - pub fn encrypt_userkey_with_masterpassword( - userkey: Vec, + pub fn encrypt_user_key_with_master_password( + user_key: Vec, master_password: String, email: String, kdf: Kdf, ) -> Result { - let masterkey = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; - let userkey = SymmetricCryptoKey::try_from(userkey)?; - let result = masterkey.encrypt_user_key(&userkey)?; + let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; + let user_key = SymmetricCryptoKey::try_from(user_key)?; + let result = master_key.encrypt_user_key(&user_key)?; Ok(result.to_string()) } - pub fn generate_userkey(use_xchacha20: bool) -> Result, CryptoError> { - let key = if !use_xchacha20 { - SymmetricCryptoKey::generate() - } else { - SymmetricCryptoKey::generate_xchacha20() - }; - Ok(key.to_encoded()) + pub fn generate_user_key() -> Vec { + SymmetricCryptoKey::generate().to_encoded() + } + + pub fn generate_user_key_xchacha20() -> Vec { + SymmetricCryptoKey::generate_xchacha20() } } From c6592473836eb8f897650ecd589e812acf582122 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:16:10 +0200 Subject: [PATCH 050/152] Fix build --- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index b50a5ac98..ae9b787a3 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -78,7 +78,7 @@ impl PureCrypto { } pub fn generate_user_key_xchacha20() -> Vec { - SymmetricCryptoKey::generate_xchacha20() + SymmetricCryptoKey::generate_xchacha20().to_encoded() } } From 9efe790179cc710e8c970f4c24ff7ba2e2f8e3e1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:24:58 +0200 Subject: [PATCH 051/152] Change ciborium and coset versions to ranges --- crates/bitwarden-crypto/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index dfa30b824..619a1c69a 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -31,8 +31,8 @@ base64 = ">=0.22.1, <0.23" bitwarden-error = { workspace = true } cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } chacha20poly1305 = { version = "0.10.1" } -ciborium = "0.2.2" -coset = "0.3.8" +ciborium = { version = ">=0.2.2, <0.3" } +coset = { version = ">=0.3.8, <0.4" } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" From 4436cf98c61425746f25acdd1b558d43ebdd5a9b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:30:30 +0200 Subject: [PATCH 052/152] Remove allow(unused) --- crates/bitwarden-crypto/src/xchacha20.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index ce3e5ad94..98d954545 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -20,19 +20,17 @@ use rand::{CryptoRng, RngCore}; use crate::CryptoError; -#[allow(unused)] pub(crate) struct XChaCha20Poly1305Ciphertext { pub(crate) nonce: GenericArray::NonceSize>, pub(crate) ciphertext: Vec, } -#[allow(unused)] pub(crate) fn encrypt_xchacha20_poly1305( key: &[u8; 32], plaintext_secret_data: &[u8], associated_data: &[u8], ) -> XChaCha20Poly1305Ciphertext { - let mut rng = rand::thread_rng(); + let rng = rand::thread_rng(); encrypt_xchacha20_poly1305_internal(rng, key, plaintext_secret_data, associated_data) } @@ -55,7 +53,6 @@ fn encrypt_xchacha20_poly1305_internal( } } -#[allow(unused)] pub(crate) fn decrypt_xchacha20_poly1305( nonce: &[u8; 24], key: &[u8; 32], From 50af1a7308e89c89942c74ab0cf20743551e1d35 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:32:44 +0200 Subject: [PATCH 053/152] Remove unused error --- crates/bitwarden-crypto/src/error.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index c5257c4d6..042d0d986 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -27,8 +27,6 @@ pub enum CryptoError { MissingField(&'static str), #[error("Missing Key for Id: {0}")] MissingKeyId(String), - #[error("The provided Id is not valid: {0}")] - InvalidKeyId(String), #[error("Crypto store is read-only")] ReadOnlyKeyStore, From 5bb8d7f2479a8d358613f2b1c244414503f6630e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:35:41 +0200 Subject: [PATCH 054/152] Undo change to rng in make_user_key --- crates/bitwarden-crypto/src/keys/master_key.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 4c67bf957..635bf36ac 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -67,8 +67,7 @@ impl MasterKey { /// Generate a new random user key and encrypt it with the master key. pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { - let mut rng = rand::thread_rng(); - make_user_key(&mut rng, self) + make_user_key(&mut rand::thread_rng(), self) } /// Encrypt the users user key From eb13169bd931ed4ae994d0bb46f3db19eda4a638 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:49:35 +0200 Subject: [PATCH 055/152] Clean up errors and pass through Cose error --- .../bitwarden-crypto/src/enc_string/symmetric.rs | 15 ++++++--------- crates/bitwarden-crypto/src/error.rs | 8 ++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index c79983a1c..201d0e004 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -246,7 +246,7 @@ impl EncString { let mut nonce = [0u8; 24]; let cose_encrypt0 = coset::CoseEncrypt0Builder::new() .protected(protected_header) - .try_create_ciphertext(data_dec, &[], |data, aad| { + .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305( key.enc_key .as_slice() @@ -256,16 +256,15 @@ impl EncString { aad, ); nonce.copy_from_slice(ciphertext.nonce.as_slice()); - Ok(ciphertext.ciphertext) + ciphertext.ciphertext }) - .map_err(|_a: CryptoError| CryptoError::EncodingError)? .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) .build(); Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: cose_encrypt0 .to_vec() - .map_err(|_| CryptoError::EncodingError)?, + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?, }) } @@ -308,12 +307,12 @@ impl KeyDecryptable> for EncString { SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { let msg = coset::CoseEncrypt0::from_slice(data.as_slice()) - .map_err(|_| CryptoError::EncString(EncStringParseError::InvalidEncoding))?; + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?; let decrypted_message = msg .decrypt(&[], |data, aad| { let nonce = msg.unprotected.iv.as_slice(); crate::xchacha20::decrypt_xchacha20_poly1305( - nonce.try_into().map_err(|_| CryptoError::EncodingError)?, + nonce.try_into().map_err(|_| CryptoError::InvalidNonceLength)?, key.enc_key .as_slice() .try_into() @@ -321,9 +320,7 @@ impl KeyDecryptable> for EncString { data, aad, ) - .map_err(|_| CryptoError::EncodingError) - }) - .map_err(|_| CryptoError::EncodingError)?; + })?; Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 042d0d986..50a66130f 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -54,8 +54,8 @@ pub enum CryptoError { #[error("Key algorithm does not match encrypted data type")] WrongKeyType, - #[error("Encoding error")] - EncodingError, + #[error("Invalid nonce length")] + InvalidNonceLength, } #[derive(Debug, Error)] @@ -76,8 +76,8 @@ pub enum EncStringParseError { InvalidBase64(#[from] base64::DecodeError), #[error("Invalid length: expected {expected}, got {got}")] InvalidLength { expected: usize, got: usize }, - #[error("Invalid encoding")] - InvalidEncoding, + #[error("Invalid encoding {0}")] + InvalidCoseEncoding(coset::CoseError), } #[derive(Debug, Error)] From 077f265a611345a97acedbae0b1650bbd6e5fef0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:52:14 +0200 Subject: [PATCH 056/152] Cargo fmt --- .../src/enc_string/symmetric.rs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 201d0e004..36c45abb2 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -262,9 +262,9 @@ impl EncString { .build(); Ok(EncString::XChaCha20_Poly1305_Cose_B64 { - data: cose_encrypt0 - .to_vec() - .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?, + data: cose_encrypt0.to_vec().map_err(|err| { + CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) + })?, }) } @@ -306,21 +306,23 @@ impl KeyDecryptable> for EncString { EncString::XChaCha20_Poly1305_Cose_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { - let msg = coset::CoseEncrypt0::from_slice(data.as_slice()) - .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?; - let decrypted_message = msg - .decrypt(&[], |data, aad| { - let nonce = msg.unprotected.iv.as_slice(); - crate::xchacha20::decrypt_xchacha20_poly1305( - nonce.try_into().map_err(|_| CryptoError::InvalidNonceLength)?, - key.enc_key - .as_slice() - .try_into() - .expect("XChaChaPoly1305 key is 32 bytes long"), - data, - aad, - ) - })?; + let msg = coset::CoseEncrypt0::from_slice(data.as_slice()).map_err(|err| { + CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) + })?; + let decrypted_message = msg.decrypt(&[], |data, aad| { + let nonce = msg.unprotected.iv.as_slice(); + crate::xchacha20::decrypt_xchacha20_poly1305( + nonce + .try_into() + .map_err(|_| CryptoError::InvalidNonceLength)?, + key.enc_key + .as_slice() + .try_into() + .expect("XChaChaPoly1305 key is 32 bytes long"), + data, + aad, + ) + })?; Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), From fa305fea23b38cc0412b9082352bdb8362362cb3 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:58:28 +0200 Subject: [PATCH 057/152] Clean up constant time compare of symmetric crypto keys --- .../src/keys/symmetric_crypto_key.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index aa69a1f65..eab9c63b8 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -184,14 +184,14 @@ impl SymmetricCryptoKey { impl ConstantTimeEq for SymmetricCryptoKey { fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice { + use SymmetricCryptoKey::*; match (self, other) { - (SymmetricCryptoKey::Aes256CbcKey(a), SymmetricCryptoKey::Aes256CbcKey(b)) => { - a.ct_eq(b) - } - (SymmetricCryptoKey::Aes256CbcHmacKey(a), SymmetricCryptoKey::Aes256CbcHmacKey(b)) => { - a.ct_eq(b) - } - _ => Choice::from(0), + (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b), + (Aes256CbcKey(_), _) => Choice::from(0), + (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b), + (Aes256CbcHmacKey(_), _) => Choice::from(0), + (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b), + (XChaCha20Poly1305Key(_), _) => Choice::from(0), } } } From 2ce007414902c07dcd82645680bf1176de79762e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 15:58:57 +0200 Subject: [PATCH 058/152] Add empty lines in match for readability --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index eab9c63b8..a88d5a28f 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -188,8 +188,10 @@ impl ConstantTimeEq for SymmetricCryptoKey { match (self, other) { (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b), (Aes256CbcKey(_), _) => Choice::from(0), + (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b), (Aes256CbcHmacKey(_), _) => Choice::from(0), + (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b), (XChaCha20Poly1305Key(_), _) => Choice::from(0), } From b4e7ddba5df7cdc93325927287f863e50a8469c5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 16:02:40 +0200 Subject: [PATCH 059/152] Prevent unreachable code in encstring fmt function --- .../src/enc_string/symmetric.rs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 36c45abb2..6d418e998 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -180,28 +180,29 @@ impl EncString { impl Display for EncString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - EncString::AesCbc256_B64 { .. } | EncString::AesCbc256_HmacSha256_B64 { .. } => { - let parts: Vec<&[u8]> = match self { - EncString::AesCbc256_B64 { iv, data } => vec![iv, data], - EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], - _ => unreachable!(), - }; - - let encoded_parts: Vec = - parts.iter().map(|part| STANDARD.encode(part)).collect(); + fn fmt_parts( + f: &mut std::fmt::Formatter<'_>, + enc_type: u8, + parts: &[&[u8]], + ) -> std::fmt::Result { + let encoded_parts: Vec = + parts.iter().map(|part| STANDARD.encode(part)).collect(); + write!(f, "{}.{}", enc_type, encoded_parts.join("|")) + } - write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; + let enc_type = self.enc_type(); - Ok(()) + match self { + EncString::AesCbc256_B64 { iv, data } => fmt_parts(f, enc_type, &[iv, data]), + EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { + fmt_parts(f, enc_type, &[iv, data, mac]) } EncString::XChaCha20_Poly1305_Cose_B64 { data } => { if let Ok(msg) = coset::CoseEncrypt0::from_slice(data.as_slice()) { - write!(f, "{}.{:?}", self.enc_type(), msg)?; + write!(f, "{}.{:?}", enc_type, msg)?; } else { - write!(f, "{}.INVALID_COSE", self.enc_type())?; + write!(f, "{}.INVALID_COSE", enc_type)?; } - Ok(()) } } From 46301b1d3c6b15783ad0dee48ea8f931a780a2e8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 16:08:51 +0200 Subject: [PATCH 060/152] Cleanup key conversion for xchacha20 encrypt/decrypt --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 6d418e998..5991f1cd4 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -249,10 +249,7 @@ impl EncString { .protected(protected_header) .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305( - key.enc_key - .as_slice() - .try_into() - .expect("XChaChaPoly1305 key is 32 bytes long"), + &(*key.enc_key).into(), data, aad, ); @@ -316,10 +313,7 @@ impl KeyDecryptable> for EncString { nonce .try_into() .map_err(|_| CryptoError::InvalidNonceLength)?, - key.enc_key - .as_slice() - .try_into() - .expect("XChaChaPoly1305 key is 32 bytes long"), + &(*key.enc_key).into(), data, aad, ) From 59e4e55d1fd74f93ccfee0257b6c0daf9c5ba070 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 14 Apr 2025 16:12:13 +0200 Subject: [PATCH 061/152] Cargo fmt --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 5991f1cd4..b2fce367a 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -248,11 +248,8 @@ impl EncString { let cose_encrypt0 = coset::CoseEncrypt0Builder::new() .protected(protected_header) .create_ciphertext(data_dec, &[], |data, aad| { - let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305( - &(*key.enc_key).into(), - data, - aad, - ); + let ciphertext = + crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); nonce.copy_from_slice(ciphertext.nonce.as_slice()); ciphertext.ciphertext }) From 77afef58c21a7931f9670a3a31a72a91ee3f0e89 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 15:57:01 +0200 Subject: [PATCH 062/152] Rename to UnauthenticatedSharedKey --- crates/bitwarden-core/src/auth/auth_client.rs | 4 +- .../bitwarden-core/src/auth/auth_request.rs | 14 +-- crates/bitwarden-core/src/auth/tde.rs | 8 +- .../src/client/encryption_settings.rs | 4 +- crates/bitwarden-core/src/client/internal.rs | 4 +- crates/bitwarden-core/src/mobile/crypto.rs | 16 +-- .../src/mobile/crypto_client.rs | 4 +- crates/bitwarden-core/src/uniffi_support.rs | 4 +- .../src/enc_string/asymmetric.rs | 112 +++++++++--------- crates/bitwarden-crypto/src/enc_string/mod.rs | 4 +- .../src/keys/asymmetric_crypto_key.rs | 11 +- .../bitwarden-crypto/src/keys/device_key.rs | 12 +- crates/bitwarden-crypto/src/lib.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 10 +- crates/bitwarden-crypto/src/uniffi_support.rs | 6 +- crates/bitwarden-uniffi/src/auth/mod.rs | 6 +- crates/bitwarden-uniffi/src/crypto.rs | 7 +- crates/bitwarden-uniffi/src/uniffi_support.rs | 4 +- 18 files changed, 122 insertions(+), 110 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_client.rs b/crates/bitwarden-core/src/auth/auth_client.rs index fb5f80a35..ae182778f 100644 --- a/crates/bitwarden-core/src/auth/auth_client.rs +++ b/crates/bitwarden-core/src/auth/auth_client.rs @@ -1,6 +1,6 @@ #[cfg(feature = "internal")] use bitwarden_crypto::{ - AsymmetricEncString, CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, + CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, UnauthenticatedSharedKey, }; #[cfg(feature = "secrets")] @@ -152,7 +152,7 @@ impl AuthClient<'_> { pub fn approve_auth_request( &self, public_key: String, - ) -> Result { + ) -> Result { approve_auth_request(self.client, public_key) } diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 64a72700d..e8125b204 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -1,7 +1,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ - fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricEncString, - AsymmetricPublicCryptoKey, CryptoError, + fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricPublicCryptoKey, + CryptoError, UnauthenticatedSharedKey, }; #[cfg(feature = "internal")] use bitwarden_crypto::{EncString, SymmetricCryptoKey}; @@ -51,7 +51,7 @@ pub(crate) fn new_auth_request(email: &str) -> Result Result { let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; let key: SymmetricCryptoKey = user_key.decapsulate_key_unsigned(&key)?; @@ -62,7 +62,7 @@ pub(crate) fn auth_request_decrypt_user_key( #[cfg(feature = "internal")] pub(crate) fn auth_request_decrypt_master_key( private_key: String, - master_key: AsymmetricEncString, + master_key: UnauthenticatedSharedKey, user_key: EncString, ) -> Result { use bitwarden_crypto::MasterKey; @@ -90,7 +90,7 @@ pub enum ApproveAuthRequestError { pub(crate) fn approve_auth_request( client: &Client, public_key: String, -) -> Result { +) -> Result { let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; let key_store = client.internal.get_key_store(); @@ -100,7 +100,7 @@ pub(crate) fn approve_auth_request( #[allow(deprecated)] let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; - Ok(AsymmetricEncString::encapsulate_key_unsigned( + Ok(UnauthenticatedSharedKey::encapsulate_key_unsigned( key, &public_key, )?) @@ -120,7 +120,7 @@ fn test_auth_request() { let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); - let encrypted = AsymmetricEncString::encapsulate_key_unsigned( + let encrypted = UnauthenticatedSharedKey::encapsulate_key_unsigned( &SymmetricCryptoKey::try_from(secret.clone()).unwrap(), &private_key, ) diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index ef96d610a..97c6bf365 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -1,7 +1,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ - AsymmetricEncString, AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey, - TrustDeviceResponse, UserKey, + AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey, TrustDeviceResponse, + UnauthenticatedSharedKey, UserKey, }; use crate::{client::encryption_settings::EncryptionSettingsError, Client}; @@ -22,7 +22,7 @@ pub(super) fn make_register_tde_keys( let user_key = UserKey::new(SymmetricCryptoKey::generate(&mut rng)); let key_pair = user_key.make_key_pair()?; - let admin_reset = AsymmetricEncString::encapsulate_key_unsigned(&user_key.0, &public_key)?; + let admin_reset = UnauthenticatedSharedKey::encapsulate_key_unsigned(&user_key.0, &public_key)?; let device_key = if remember_device { Some(DeviceKey::trust_device(&user_key.0)?) @@ -57,6 +57,6 @@ pub struct RegisterTdeKeyResponse { pub private_key: EncString, pub public_key: String, - pub admin_reset: AsymmetricEncString, + pub admin_reset: UnauthenticatedSharedKey, pub device_key: Option, } diff --git a/crates/bitwarden-core/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs index 7d3825d26..3cb326c2b 100644 --- a/crates/bitwarden-core/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -1,6 +1,6 @@ use bitwarden_crypto::{AsymmetricCryptoKey, KeyStore, SymmetricCryptoKey}; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{EncString, UnauthenticatedSharedKey}; use bitwarden_error::bitwarden_error; use thiserror::Error; use uuid::Uuid; @@ -91,7 +91,7 @@ impl EncryptionSettings { #[cfg(feature = "internal")] pub(crate) fn set_org_keys( - org_enc_keys: Vec<(Uuid, AsymmetricEncString)>, + org_enc_keys: Vec<(Uuid, UnauthenticatedSharedKey)>, store: &KeyStore, ) -> Result<(), EncryptionSettingsError> { let mut ctx = store.context_mut(); diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs index e365f0250..ca65e8e2a 100644 --- a/crates/bitwarden-core/src/client/internal.rs +++ b/crates/bitwarden-core/src/client/internal.rs @@ -4,7 +4,7 @@ use bitwarden_crypto::KeyStore; #[cfg(any(feature = "internal", feature = "secrets"))] use bitwarden_crypto::SymmetricCryptoKey; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString, Kdf, MasterKey, PinKey}; +use bitwarden_crypto::{EncString, Kdf, MasterKey, PinKey, UnauthenticatedSharedKey}; use chrono::Utc; use uuid::Uuid; @@ -215,7 +215,7 @@ impl InternalClient { #[cfg(feature = "internal")] pub fn initialize_org_crypto( &self, - org_keys: Vec<(Uuid, AsymmetricEncString)>, + org_keys: Vec<(Uuid, UnauthenticatedSharedKey)>, ) -> Result<(), EncryptionSettingsError> { EncryptionSettings::set_org_keys(org_keys, &self.key_store) } diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index e62e3e573..a8fffa62d 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ - AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, Kdf, KeyDecryptable, - KeyEncryptable, MasterKey, SymmetricCryptoKey, UserKey, + AsymmetricCryptoKey, CryptoError, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, + SymmetricCryptoKey, UnauthenticatedSharedKey, UserKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -76,7 +76,7 @@ pub enum InitUserCryptoMethod { /// The Device Private Key protected_device_private_key: EncString, /// The user's symmetric crypto key, encrypted with the Device Key. - device_protected_user_key: AsymmetricEncString, + device_protected_user_key: UnauthenticatedSharedKey, }, KeyConnector { /// Base64 encoded master key, retrieved from the key connector. @@ -93,11 +93,11 @@ pub enum InitUserCryptoMethod { pub enum AuthRequestMethod { UserKey { /// User Key protected by the private key provided in `AuthRequestResponse`. - protected_user_key: AsymmetricEncString, + protected_user_key: UnauthenticatedSharedKey, }, MasterKey { /// Master Key protected by the private key provided in `AuthRequestResponse`. - protected_master_key: AsymmetricEncString, + protected_master_key: UnauthenticatedSharedKey, /// User Key protected by the MasterKey, provided by the auth response. auth_request_key: EncString, }, @@ -208,7 +208,7 @@ pub async fn initialize_user_crypto( #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitOrgCryptoRequest { /// The encryption keys for all the organizations the user is a part of - pub organization_keys: HashMap, + pub organization_keys: HashMap, } pub async fn initialize_org_crypto( @@ -363,7 +363,7 @@ pub enum EnrollAdminPasswordResetError { pub(super) fn enroll_admin_password_reset( client: &Client, public_key: String, -) -> Result { +) -> Result { use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::AsymmetricPublicCryptoKey; @@ -374,7 +374,7 @@ pub(super) fn enroll_admin_password_reset( #[allow(deprecated)] let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?; - Ok(AsymmetricEncString::encapsulate_key_unsigned( + Ok(UnauthenticatedSharedKey::encapsulate_key_unsigned( key, &public_key, )?) diff --git a/crates/bitwarden-core/src/mobile/crypto_client.rs b/crates/bitwarden-core/src/mobile/crypto_client.rs index 90462a489..ccd34f632 100644 --- a/crates/bitwarden-core/src/mobile/crypto_client.rs +++ b/crates/bitwarden-core/src/mobile/crypto_client.rs @@ -1,6 +1,6 @@ use bitwarden_crypto::CryptoError; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{EncString, UnauthenticatedSharedKey}; use super::crypto::{ derive_key_connector, make_key_pair, verify_asymmetric_keys, DeriveKeyConnectorError, @@ -59,7 +59,7 @@ impl CryptoClient<'_> { pub fn enroll_admin_password_reset( &self, public_key: String, - ) -> Result { + ) -> Result { enroll_admin_password_reset(self.client, public_key) } diff --git a/crates/bitwarden-core/src/uniffi_support.rs b/crates/bitwarden-core/src/uniffi_support.rs index fcdd2aa78..4db93574b 100644 --- a/crates/bitwarden-core/src/uniffi_support.rs +++ b/crates/bitwarden-core/src/uniffi_support.rs @@ -1,6 +1,6 @@ use std::num::NonZeroU32; -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{EncString, UnauthenticatedSharedKey}; use uuid::Uuid; use crate::UniffiCustomTypeConverter; @@ -8,7 +8,7 @@ use crate::UniffiCustomTypeConverter; uniffi::ffi_converter_forward!(NonZeroU32, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!( - AsymmetricEncString, + UnauthenticatedSharedKey, bitwarden_crypto::UniFfiTag, crate::UniFfiTag ); diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index d270e34b2..1a991609f 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, str::FromStr}; use base64::{engine::general_purpose::STANDARD, Engine}; -pub use internal::AsymmetricEncString; +pub use internal::UnauthenticatedSharedKey; use rsa::Oaep; use serde::Deserialize; @@ -18,16 +18,17 @@ mod internal { #[cfg(feature = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] const TS_CUSTOM_TYPES: &'static str = r#" - export type AsymmetricEncString = string; + export type UnauthenticatedSharedKey = string; "#; /// # Encrypted string primitive /// - /// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically - /// encrypted string. They are used together with the KeyDecryptable and KeyEncryptable - /// traits to encrypt and decrypt data using [crate::AsymmetricCryptoKey]s. + /// [UnauthenticatedSharedKey] is a Bitwarden specific primitive that represents an + /// asymmetrically encrypted symmetric key. Since the symmetric key is directly encrypted + /// with the public key, without any further signature, the receiver cannot guarantee the + /// senders identity. /// - /// The flexibility of the [AsymmetricEncString] type allows for different encryption algorithms + /// [UnauthenticatedSharedKey] type allows for different encryption algorithms /// to be used which is represented by the different variants of the enum. /// /// ## Note @@ -36,13 +37,13 @@ mod internal { /// old variants, but we should be opinionated in which variants are used for encrypting. /// /// ## Variants - /// - [Rsa2048_OaepSha256_B64](AsymmetricEncString::Rsa2048_OaepSha256_B64) - /// - [Rsa2048_OaepSha1_B64](AsymmetricEncString::Rsa2048_OaepSha1_B64) + /// - [Rsa2048_OaepSha256_B64](UnauthenticatedSharedKey::Rsa2048_OaepSha256_B64) + /// - [Rsa2048_OaepSha1_B64](UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64) /// /// ## Serialization /// - /// [AsymmetricEncString] implements [std::fmt::Display] and [std::str::FromStr] to allow for - /// easy serialization and uses a custom scheme to represent the different variants. + /// [UnauthenticatedSharedKey] implements [std::fmt::Display] and [std::str::FromStr] to allow + /// for easy serialization and uses a custom scheme to represent the different variants. /// /// The scheme is one of the following schemes: /// - `[type].[data]` @@ -52,7 +53,7 @@ mod internal { /// - `[data]`: is the encrypted data. #[derive(Clone, zeroize::ZeroizeOnDrop)] #[allow(unused, non_camel_case_types)] - pub enum AsymmetricEncString { + pub enum UnauthenticatedSharedKey { /// 3 Rsa2048_OaepSha256_B64 { data: Vec }, /// 4 @@ -66,16 +67,16 @@ mod internal { } } -/// To avoid printing sensitive information, [AsymmetricEncString] debug prints to -/// `AsymmetricEncString`. -impl std::fmt::Debug for AsymmetricEncString { +/// To avoid printing sensitive information, [UnauthenticatedSharedKey] debug prints to +/// `UnauthenticatedSharedKey`. +impl std::fmt::Debug for UnauthenticatedSharedKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AsymmetricEncString").finish() + f.debug_struct("UnauthenticatedSharedKey").finish() } } -/// Deserializes an [AsymmetricEncString] from a string. -impl FromStr for AsymmetricEncString { +/// Deserializes an [UnauthenticatedSharedKey] from a string. +impl FromStr for UnauthenticatedSharedKey { type Err = CryptoError; fn from_str(s: &str) -> Result { @@ -83,23 +84,23 @@ impl FromStr for AsymmetricEncString { match (enc_type, parts.len()) { ("3", 1) => { let data = from_b64_vec(parts[0])?; - Ok(AsymmetricEncString::Rsa2048_OaepSha256_B64 { data }) + Ok(UnauthenticatedSharedKey::Rsa2048_OaepSha256_B64 { data }) } ("4", 1) => { let data = from_b64_vec(parts[0])?; - Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data }) + Ok(UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64 { data }) } #[allow(deprecated)] ("5", 2) => { let data = from_b64_vec(parts[0])?; let mac: Vec = from_b64_vec(parts[1])?; - Ok(AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac }) + Ok(UnauthenticatedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac }) } #[allow(deprecated)] ("6", 2) => { let data = from_b64_vec(parts[0])?; let mac: Vec = from_b64_vec(parts[1])?; - Ok(AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac }) + Ok(UnauthenticatedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac }) } (enc_type, parts) => Err(EncStringParseError::InvalidTypeAsymm { @@ -111,15 +112,19 @@ impl FromStr for AsymmetricEncString { } } -impl Display for AsymmetricEncString { +impl Display for UnauthenticatedSharedKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let parts: Vec<&[u8]> = match self { - AsymmetricEncString::Rsa2048_OaepSha256_B64 { data } => vec![data], - AsymmetricEncString::Rsa2048_OaepSha1_B64 { data } => vec![data], + UnauthenticatedSharedKey::Rsa2048_OaepSha256_B64 { data } => vec![data], + UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64 { data } => vec![data], #[allow(deprecated)] - AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac } => vec![data, mac], + UnauthenticatedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac } => { + vec![data, mac] + } #[allow(deprecated)] - AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => vec![data, mac], + UnauthenticatedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => { + vec![data, mac] + } }; let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); @@ -130,7 +135,7 @@ impl Display for AsymmetricEncString { } } -impl<'de> Deserialize<'de> for AsymmetricEncString { +impl<'de> Deserialize<'de> for UnauthenticatedSharedKey { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -139,7 +144,7 @@ impl<'de> Deserialize<'de> for AsymmetricEncString { } } -impl serde::Serialize for AsymmetricEncString { +impl serde::Serialize for UnauthenticatedSharedKey { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -148,35 +153,35 @@ impl serde::Serialize for AsymmetricEncString { } } -impl AsymmetricEncString { +impl UnauthenticatedSharedKey { /// Encapsulate a symmetric key, to be shared asymmetrically. Produces a - /// [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. Note, this does not sign the data + /// [UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64] variant. Note, this does not sign the data /// and thus does not guarantee sender authenticity. pub fn encapsulate_key_unsigned( encapsulated_key: &SymmetricCryptoKey, encapsulation_key: &dyn AsymmetricEncryptable, - ) -> Result { + ) -> Result { let enc = encrypt_rsa2048_oaep_sha1( encapsulation_key.to_public_key(), &encapsulated_key.to_vec(), )?; - Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) + Ok(UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64 { data: enc }) } - /// The numerical representation of the encryption type of the [AsymmetricEncString]. + /// The numerical representation of the encryption type of the [UnauthenticatedSharedKey]. const fn enc_type(&self) -> u8 { match self { - AsymmetricEncString::Rsa2048_OaepSha256_B64 { .. } => 3, - AsymmetricEncString::Rsa2048_OaepSha1_B64 { .. } => 4, + UnauthenticatedSharedKey::Rsa2048_OaepSha256_B64 { .. } => 3, + UnauthenticatedSharedKey::Rsa2048_OaepSha1_B64 { .. } => 4, #[allow(deprecated)] - AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, + UnauthenticatedSharedKey::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, #[allow(deprecated)] - AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, + UnauthenticatedSharedKey::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, } } } -impl AsymmetricEncString { +impl UnauthenticatedSharedKey { /// Decapsulate a symmetric key, shared asymmetrically. /// Note: The shared key does not have a sender signature and sender authenticity is not /// guaranteed. @@ -184,7 +189,7 @@ impl AsymmetricEncString { &self, decapsulation_key: &AsymmetricCryptoKey, ) -> Result { - use AsymmetricEncString::*; + use UnauthenticatedSharedKey::*; let mut key_data = match self { Rsa2048_OaepSha256_B64 { data } => decapsulation_key .key @@ -206,11 +211,12 @@ impl AsymmetricEncString { } } -/// Usually we wouldn't want to expose AsymmetricEncStrings in the API or the schemas. -/// But during the transition phase we will expose endpoints using the AsymmetricEncString type. -impl schemars::JsonSchema for AsymmetricEncString { +/// Usually we wouldn't want to expose UnauthenticatedSharedKeys in the API or the schemas. +/// But during the transition phase we will expose endpoints using the UnauthenticatedSharedKey +/// type. +impl schemars::JsonSchema for UnauthenticatedSharedKey { fn schema_name() -> String { - "AsymmetricEncString".to_string() + "UnauthenticatedSharedKey".to_string() } fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { @@ -222,7 +228,7 @@ impl schemars::JsonSchema for AsymmetricEncString { mod tests { use schemars::schema_for; - use super::{AsymmetricCryptoKey, AsymmetricEncString}; + use super::{AsymmetricCryptoKey, UnauthenticatedSharedKey}; use crate::SymmetricCryptoKey; const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- @@ -258,7 +264,7 @@ XKZBokBGnjFnTnKcs7nv/O8= fn test_enc_string_rsa2048_oaep_sha256_b64() { let key_pair = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "3.BfwZTwBYbU5WQ5X7Vm8yl0hYmHTRdkVACCRZYcqhcjicoaPVDEP03CIRmtnppu0aXOppoQzhw5S2OKTUaqoOGKZg7+PrmVEhjiUFfVAptInBD6XGHZ0Z3u3F+JY1E3xIFebOFiX7KLQ+7D0bJhBEnl8P7phmanKF3Cil5ayDGRpAjAsBHMwlNRKXy05YpYs3/x+V+zjlxVrBU9gYFCpacKUbxT51I8tf21ISqo6H9ZBwqDE2QUPhYJl5op7SJgySdd3YCKnsObXa8fFj2OwxGLAXJAyvF6qZyl08RO/ZYUOOOPlbC7ywXxAISw3qmrwxqpLSBqAm9BYPa/zxBnTHrA=="; - let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); let test_bytes = vec![0u8; 64]; let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); @@ -272,7 +278,7 @@ XKZBokBGnjFnTnKcs7nv/O8= fn test_enc_string_rsa2048_oaep_sha1_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg=="; - let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); let test_bytes = vec![0u8; 64]; let test_bytes = SymmetricCryptoKey::try_from(test_bytes).unwrap(); @@ -286,7 +292,7 @@ XKZBokBGnjFnTnKcs7nv/O8= fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "6.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; - let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); let test_bytes = vec![0u8; 64]; let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); @@ -300,7 +306,7 @@ XKZBokBGnjFnTnKcs7nv/O8= fn test_enc_string_serialization() { #[derive(serde::Serialize, serde::Deserialize)] struct Test { - key: AsymmetricEncString, + key: UnauthenticatedSharedKey, } let cipher = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; @@ -315,7 +321,7 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_from_str_invalid() { let enc_str = "7.ABC"; - let enc_string: Result = enc_str.parse(); + let enc_string: Result = enc_str.parse(); let err = enc_string.unwrap_err(); assert_eq!( @@ -327,19 +333,19 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_debug_format() { let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; - let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); let debug_string = format!("{:?}", enc_string); - assert_eq!(debug_string, "AsymmetricEncString"); + assert_eq!(debug_string, "UnauthenticatedSharedKey"); } #[test] fn test_json_schema() { - let schema = schema_for!(AsymmetricEncString); + let schema = schema_for!(UnauthenticatedSharedKey); assert_eq!( serde_json::to_string(&schema).unwrap(), - r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"AsymmetricEncString","type":"string"}"# + r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"UnauthenticatedSharedKey","type":"string"}"# ); } } diff --git a/crates/bitwarden-crypto/src/enc_string/mod.rs b/crates/bitwarden-crypto/src/enc_string/mod.rs index 3278b8064..694148dfe 100644 --- a/crates/bitwarden-crypto/src/enc_string/mod.rs +++ b/crates/bitwarden-crypto/src/enc_string/mod.rs @@ -1,6 +1,6 @@ //! Encrypted string types //! -//! [EncString] and [AsymmetricEncString] are Bitwarden specific primitive that represents a +//! [EncString] and [UnauthenticatedSharedKey] are Bitwarden specific primitive that represents a //! encrypted string. They are are used together with the [KeyDecryptable][crate::KeyDecryptable] //! and [KeyEncryptable][crate::KeyEncryptable] traits to encrypt and decrypt data using //! [SymmetricCryptoKey][crate::SymmetricCryptoKey] and @@ -11,7 +11,7 @@ mod symmetric; use std::str::FromStr; -pub use asymmetric::AsymmetricEncString; +pub use asymmetric::UnauthenticatedSharedKey; use base64::{engine::general_purpose::STANDARD, Engine}; pub use symmetric::EncString; diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 45c0262f5..f693408c3 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -6,13 +6,13 @@ use super::key_encryptable::CryptoKey; use crate::error::{CryptoError, Result}; /// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to -/// encrypt [AsymmetricEncString](crate::AsymmetricEncString). +/// encrypt [UnauthenticatedSharedKey](crate::UnauthenticatedSharedKey). pub trait AsymmetricEncryptable { fn to_public_key(&self) -> &RsaPublicKey; } /// An asymmetric public encryption key. Can only encrypt -/// [AsymmetricEncString](crate::AsymmetricEncString), usually accompanied by a +/// [UnauthenticatedSharedKey](crate::UnauthenticatedSharedKey), usually accompanied by a /// [AsymmetricCryptoKey] pub struct AsymmetricPublicCryptoKey { key: RsaPublicKey, @@ -35,7 +35,7 @@ impl AsymmetricEncryptable for AsymmetricPublicCryptoKey { } /// An asymmetric encryption key. Contains both the public and private key. Can be used to both -/// encrypt and decrypt [`AsymmetricEncString`](crate::AsymmetricEncString). +/// encrypt and decrypt [`UnauthenticatedSharedKey`](crate::UnauthenticatedSharedKey). #[derive(Clone)] pub struct AsymmetricCryptoKey { // RsaPrivateKey is not a Copy type so this isn't completely necessary, but @@ -121,7 +121,8 @@ mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; use crate::{ - AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, SymmetricCryptoKey, + AsymmetricCryptoKey, AsymmetricPublicCryptoKey, SymmetricCryptoKey, + UnauthenticatedSharedKey, }; #[test] @@ -217,7 +218,7 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= let raw_key = SymmetricCryptoKey::generate(&mut rand::thread_rng()); let encrypted = - AsymmetricEncString::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); + UnauthenticatedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); assert_eq!(raw_key, decrypted); diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index fffdfadd4..23ea7887f 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -1,6 +1,6 @@ use crate::{ - error::Result, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, - KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + error::Result, AsymmetricCryptoKey, CryptoError, EncString, KeyDecryptable, KeyEncryptable, + SymmetricCryptoKey, UnauthenticatedSharedKey, }; /// Device Key @@ -16,7 +16,7 @@ pub struct TrustDeviceResponse { /// Base64 encoded device key pub device_key: String, /// UserKey encrypted with DevicePublicKey - pub protected_user_key: AsymmetricEncString, + pub protected_user_key: UnauthenticatedSharedKey, /// DevicePrivateKey encrypted with [DeviceKey] pub protected_device_private_key: EncString, /// DevicePublicKey encrypted with [UserKey](super::UserKey) @@ -35,7 +35,7 @@ impl DeviceKey { let device_private_key = AsymmetricCryptoKey::generate(&mut rng); let protected_user_key = - AsymmetricEncString::encapsulate_key_unsigned(user_key, &device_private_key)?; + UnauthenticatedSharedKey::encapsulate_key_unsigned(user_key, &device_private_key)?; let protected_device_public_key = device_private_key .to_public_der()? @@ -57,7 +57,7 @@ impl DeviceKey { pub fn decrypt_user_key( &self, protected_device_private_key: EncString, - protected_user_key: AsymmetricEncString, + protected_user_key: UnauthenticatedSharedKey, ) -> Result { let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?; @@ -122,7 +122,7 @@ mod tests { ]; let device_key = DeviceKey(key_data.try_into().unwrap()); - let protected_user_key: AsymmetricEncString = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap(); + let protected_user_key: UnauthenticatedSharedKey = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap(); let protected_device_private_key: EncString = "2.GyQfUYWW6Byy4UV5icFLxg==|EMiU7OTF79N6tfv3+YUs5zJhBAgqv6sa5YCoPl6yAETh7Tfk+JmbeizxXFPj5Q1X/tcVpDZl/3fGcxtnIxg1YtvDFn7j8uPnoApOWhCKmwcvJSIkt+qvX3lELNBwZXozSiy7PbQ0JbCMe2d4MkimR5k8+lE9FB3208yYK7nOJhlrsUCnOekCYEU9/4NCMA8tz8SpITx/MN4JJ1TQ/KjPJYLt+3JNUxK47QlgREWQvyVzCRt7ZGtcgIJ/U1qycAWMpEg9NkuV8j5QRA1S7VBsA6qliJwys5+dmTuIOmOMwdKFZDc4ZvWoRkPp2TSJBu7L8sSAgU6mmDWac8iQ+9Ka/drdfwYLrH8GAZvURk79tSpRrT7+PAFe2QdUtliUIyiqkh8iJVjZube4hRnEsRuX9V9b+UdtAr6zAj7mugO/VAu5T9J38V79V2ohG3NtXysDeKLXpAlkhjllWXeq/wret2fD4WiwqEDj0G2A/PY3F3OziIgp0UKc00AfqrPq8OVK3A+aowwVqdYadgxyoVCKWJ8unJeAXG7MrMQ9tHpzF6COoaEy7Wwoc17qko33zazwLZbfAjB4oc8Ea26jRKnJZP56sVZAjOSQQMziAsA08MRaa/DQhgRea1+Ygba0gMft8Dww8anN2gQBveTZRBWyqXYgN3U0Ity5gNauT8RnFk9faqVFt2Qxnp0JgJ+PsqEt5Hn4avBRZQQ7o8VvPnxYLDKFe3I2m6HFYFWRhOGeDYxexIuaiF2iIAYFVUmnDuWpgnUiL4XJ3KHDsjkPzcV3z4D2Knr/El2VVXve8jhDjETfovmmN28+i2e29PXvKIymTskMFpFCQPc7wBY/Id7pmgb3SujKYNpkAS2sByDoRir0my49DDGfta0dENssJhFd3x+87fZbEj3cMiikg2pBwpTLgmfIUa5cVZU2s8JZ9wu7gaioYzvX+elHa3EHLcnEUoJTtSf9kjb+Nbq4ktMgYAO2wIC96t1LvmqK4Qn2cOdw5QNlRqALhqe5V31kyIcwRMK0AyIoOPhnSqtpYdFiR3LDTvZA8dU0vSsuchCwHNMeRUtKvdzN/tk+oeznyY/mpakUESN501lEKd/QFLtJZsDZTtNlcA8fU3kDtws4ZIMR0O5+PFmgQFSU8OMobf9ClUzy/wHTvYGyDuSwbOoPeS955QKkUKXCNMj33yrPr+ioHQ1BNwLX3VmMF4bNRBY/vr+CG0/EZi0Gwl0kyHGl0yWEtpQuu+/PaROJeOraWy5D1UoZZhY4n0zJZBt1eg3FZ2rhKv4gdUc50nZpeNWE8pIqZ6RQ7qPJuqfF1Z+G73iOSnLYCHDiiFmhD5ivf9IGkTAcWcBsQ/2wcSj9bFJr4DrKfsbQ4CkSWICWVn/W+InKkO6BTsBbYmvte5SvbaN+UOtiUSkHLBCCr8273VNgcB/hgtbUires3noxYZJxoczr+i7vdlEgQnWEKrpo0CifsFxGwYS3Yy2K79iwvDMaLPDf73zLSbuoUl6602F2Mzcjnals67f+gSpaDvWt7Kg9c/ZfGjq8oNxVaXJnX3gSDsO+fhwVAtnDApL+tL8cFfxGerW4KGi9/74woH+C3MMIViBtNnrpEuvxUW97Dg5nd40oGDeyi/q+8HdcxkneyFY=|JYdol19Yi+n1r7M+06EwK5JCi2s/CWqKui2Cy6hEb3k=".parse().unwrap(); diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 117bf8965..e04818ff1 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -14,7 +14,7 @@ static ALLOC: ZeroizingAllocator = ZeroizingAllocator(std::a mod aes; mod enc_string; -pub use enc_string::{AsymmetricEncString, EncString}; +pub use enc_string::{EncString, UnauthenticatedSharedKey}; mod error; pub use error::CryptoError; pub(crate) use error::Result; diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 2ddb3956f..a149aa649 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -8,8 +8,8 @@ use zeroize::Zeroizing; use super::KeyStoreInner; use crate::{ derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, - AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, - SymmetricCryptoKey, + AsymmetricCryptoKey, CryptoError, EncString, KeyId, KeyIds, Result, SymmetricCryptoKey, + UnauthenticatedSharedKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -185,7 +185,7 @@ impl KeyStoreContext<'_, Ids> { &mut self, decapsulation_key: Ids::Asymmetric, new_key_id: Ids::Symmetric, - encapsulated_shared_key: &AsymmetricEncString, + encapsulated_shared_key: &UnauthenticatedSharedKey, ) -> Result { let decapsulation_key = self.get_asymmetric_key(decapsulation_key)?; let decapsulated_key = @@ -210,8 +210,8 @@ impl KeyStoreContext<'_, Ids> { &self, encapsulation_key: Ids::Asymmetric, shared_key: Ids::Symmetric, - ) -> Result { - AsymmetricEncString::encapsulate_key_unsigned( + ) -> Result { + UnauthenticatedSharedKey::encapsulate_key_unsigned( self.get_symmetric_key(shared_key)?, self.get_asymmetric_key(encapsulation_key)?, ) diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs index 7f1249da0..98f3564d5 100644 --- a/crates/bitwarden-crypto/src/uniffi_support.rs +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -1,6 +1,6 @@ use std::{num::NonZeroU32, str::FromStr}; -use crate::{AsymmetricEncString, CryptoError, EncString, UniffiCustomTypeConverter}; +use crate::{CryptoError, EncString, UnauthenticatedSharedKey, UniffiCustomTypeConverter}; uniffi::custom_type!(NonZeroU32, u32); @@ -30,9 +30,9 @@ impl UniffiCustomTypeConverter for EncString { } } -uniffi::custom_type!(AsymmetricEncString, String); +uniffi::custom_type!(UnauthenticatedSharedKey, String); -impl UniffiCustomTypeConverter for AsymmetricEncString { +impl UniffiCustomTypeConverter for UnauthenticatedSharedKey { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index 00de21afb..a29246e31 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -4,7 +4,9 @@ use bitwarden_core::auth::{ password::MasterPasswordPolicyOptions, AuthRequestResponse, KeyConnectorResponse, RegisterKeyResponse, RegisterTdeKeyResponse, }; -use bitwarden_crypto::{AsymmetricEncString, EncString, HashPurpose, Kdf, TrustDeviceResponse}; +use bitwarden_crypto::{ + EncString, HashPurpose, Kdf, TrustDeviceResponse, UnauthenticatedSharedKey, +}; use crate::{ error::{Error, Result}, @@ -159,7 +161,7 @@ impl AuthClient { } /// Approve an auth request - pub fn approve_auth_request(&self, public_key: String) -> Result { + pub fn approve_auth_request(&self, public_key: String) -> Result { Ok(self .0 .0 diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index ab099c970..f05c3ae65 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -4,7 +4,7 @@ use bitwarden_core::mobile::crypto::{ DeriveKeyConnectorRequest, DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, }; -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{EncString, UnauthenticatedSharedKey}; use crate::{ error::{Error, Result}, @@ -86,7 +86,10 @@ impl CryptoClient { .map_err(Error::MobileCrypto)?) } - pub fn enroll_admin_password_reset(&self, public_key: String) -> Result { + pub fn enroll_admin_password_reset( + &self, + public_key: String, + ) -> Result { Ok(self .0 .0 diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index 282b03460..90be4543f 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{EncString, UnauthenticatedSharedKey}; use uuid::Uuid; // Forward the type definitions to the main bitwarden crate @@ -6,7 +6,7 @@ type DateTime = chrono::DateTime; uniffi::ffi_converter_forward!(DateTime, bitwarden_core::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!(EncString, bitwarden_core::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!( - AsymmetricEncString, + UnauthenticatedSharedKey, bitwarden_core::UniFfiTag, crate::UniFfiTag ); From 1b5d6d4c2e490a01b2b0c30508abcd70bb9bc6c3 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 20:16:32 +0200 Subject: [PATCH 063/152] Add WrappedSymmetricCryptoKey --- crates/bitwarden-core/src/auth/auth_client.rs | 9 ++- .../bitwarden-core/src/auth/auth_request.rs | 29 +++++--- .../bitwarden-core/src/auth/key_connector.rs | 4 +- .../bitwarden-core/src/auth/login/api_key.rs | 8 ++- .../bitwarden-core/src/auth/login/password.rs | 8 ++- .../src/auth/password/validate.rs | 22 +++--- crates/bitwarden-core/src/auth/pin.rs | 18 ++--- crates/bitwarden-core/src/auth/register.rs | 4 +- crates/bitwarden-core/src/client/internal.rs | 8 ++- crates/bitwarden-core/src/mobile/crypto.rs | 21 +++--- .../src/platform/generate_fingerprint.rs | 6 +- .../bitwarden-crypto/src/keys/master_key.rs | 32 ++++----- crates/bitwarden-crypto/src/keys/pin_key.rs | 6 +- crates/bitwarden-crypto/src/lib.rs | 2 + crates/bitwarden-crypto/src/safe/key_wrap.rs | 70 +++++++++++++++++++ crates/bitwarden-crypto/src/safe/mod.rs | 2 + crates/bitwarden-uniffi/src/auth/mod.rs | 7 +- 17 files changed, 172 insertions(+), 84 deletions(-) create mode 100644 crates/bitwarden-crypto/src/safe/key_wrap.rs create mode 100644 crates/bitwarden-crypto/src/safe/mod.rs diff --git a/crates/bitwarden-core/src/auth/auth_client.rs b/crates/bitwarden-core/src/auth/auth_client.rs index 6834083a1..9b8893c84 100644 --- a/crates/bitwarden-core/src/auth/auth_client.rs +++ b/crates/bitwarden-core/src/auth/auth_client.rs @@ -1,7 +1,6 @@ +use bitwarden_crypto::WrappedSymmetricKey; #[cfg(feature = "internal")] -use bitwarden_crypto::{ - AsymmetricEncString, CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, -}; +use bitwarden_crypto::{AsymmetricEncString, CryptoError, DeviceKey, Kdf, TrustDeviceResponse}; #[cfg(feature = "secrets")] use crate::auth::login::{login_access_token, AccessTokenLoginRequest, AccessTokenLoginResponse}; @@ -132,7 +131,7 @@ impl AuthClient { pub fn validate_password_user_key( &self, password: String, - encrypted_user_key: String, + encrypted_user_key: WrappedSymmetricKey, ) -> Result { validate_password_user_key(&self.client, password, encrypted_user_key) } @@ -140,7 +139,7 @@ impl AuthClient { pub fn validate_pin( &self, pin: String, - pin_protected_user_key: EncString, + pin_protected_user_key: WrappedSymmetricKey, ) -> Result { validate_pin(&self.client, pin, pin_protected_user_key) } diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 3f1d30e10..6d6387f21 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -4,7 +4,7 @@ use bitwarden_crypto::{ AsymmetricPublicCryptoKey, CryptoError, }; #[cfg(feature = "internal")] -use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; +use bitwarden_crypto::{KeyDecryptable, SymmetricCryptoKey, WrappedSymmetricKey}; use thiserror::Error; #[cfg(feature = "internal")] @@ -64,7 +64,7 @@ pub(crate) fn auth_request_decrypt_user_key( pub(crate) fn auth_request_decrypt_master_key( private_key: String, master_key: AsymmetricEncString, - user_key: EncString, + user_key: WrappedSymmetricKey, ) -> Result { use bitwarden_crypto::MasterKey; @@ -132,7 +132,7 @@ fn test_auth_request() { mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::{Kdf, MasterKey}; + use bitwarden_crypto::{EncString, Kdf, MasterKey}; use super::*; use crate::{ @@ -153,11 +153,11 @@ mod tests { ) .unwrap(); - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let user_key: EncString = "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 .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key) + .initialize_user_crypto_master_key(master_key, user_key.into(), private_key) .unwrap(); let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyLRDUwXB4BfQ507D4meFPmwn5zwy3IqTPJO4plrrhnclWahXa240BzyFW9gHgYu+Jrgms5xBfRTBMcEsqqNm7+JpB6C1B6yvnik0DpJgWQw1rwvy4SUYidpR/AWbQi47n/hvnmzI/sQxGddVfvWu1iTKOlf5blbKYAXnUE5DZBGnrWfacNXwRRdtP06tFB0LwDgw+91CeLSJ9py6dm1qX5JIxoO8StJOQl65goLCdrTWlox+0Jh4xFUfCkb+s3px+OhSCzJbvG/hlrSRcUz5GnwlCEyF3v5lfUtV96MJD+78d8pmH6CfFAp2wxKRAbGdk+JccJYO6y6oIXd3Fm7twIDAQAB"; @@ -191,10 +191,13 @@ mod tests { let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ="; let enc_master_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap(); - let enc_user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); - let dec = - auth_request_decrypt_master_key(private_key.to_owned(), enc_master_key, enc_user_key) - .unwrap(); + let enc_user_key: EncString = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let dec = auth_request_decrypt_master_key( + private_key.to_owned(), + enc_master_key, + enc_user_key.into(), + ) + .unwrap(); assert_eq!( &dec.to_vec(), @@ -214,7 +217,7 @@ mod tests { }; let email = "test@bitwarden.com"; - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let user_key: EncString = "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="; // Initialize an existing client which is unlocked @@ -224,7 +227,11 @@ mod tests { existing_device .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key.parse().unwrap()) + .initialize_user_crypto_master_key( + master_key, + user_key.into(), + private_key.parse().unwrap(), + ) .unwrap(); // Initialize a new device which will request to be logged in diff --git a/crates/bitwarden-core/src/auth/key_connector.rs b/crates/bitwarden-core/src/auth/key_connector.rs index b4006b472..c680ebef0 100644 --- a/crates/bitwarden-core/src/auth/key_connector.rs +++ b/crates/bitwarden-core/src/auth/key_connector.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{CryptoError, MasterKey, RsaKeyPair}; +use bitwarden_crypto::{CryptoError, EncString, MasterKey, RsaKeyPair}; #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct KeyConnectorResponse { @@ -16,7 +16,7 @@ pub(super) fn make_key_connector_keys( Ok(KeyConnectorResponse { master_key: master_key.to_base64(), - encrypted_user_key: encrypted_user_key.to_string(), + encrypted_user_key: EncString::from(encrypted_user_key).to_string(), keys, }) } diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index 649e448f8..56350252b 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -51,9 +51,11 @@ 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 - .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key.into(), + private_key, + )?; } Ok(ApiKeyLoginResponse::process_response(response)) diff --git a/crates/bitwarden-core/src/auth/login/password.rs b/crates/bitwarden-core/src/auth/login/password.rs index 9d9390b85..9552c008c 100644 --- a/crates/bitwarden-core/src/auth/login/password.rs +++ b/crates/bitwarden-core/src/auth/login/password.rs @@ -53,9 +53,11 @@ 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 - .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key.into(), + private_key, + )?; } Ok(PasswordLoginResponse::process_response(response)) diff --git a/crates/bitwarden-core/src/auth/password/validate.rs b/crates/bitwarden-core/src/auth/password/validate.rs index 8d7b7f520..afa865ab5 100644 --- a/crates/bitwarden-core/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{HashPurpose, MasterKey}; +use bitwarden_crypto::{HashPurpose, MasterKey, WrappedSymmetricKey}; use crate::{ auth::{password::determine_password_hash, AuthValidateError}, @@ -40,7 +40,7 @@ pub(crate) fn validate_password( pub(crate) fn validate_password_user_key( client: &Client, password: String, - encrypted_user_key: String, + encrypted_user_key: WrappedSymmetricKey, ) -> Result { use crate::key_management::SymmetricKeyId; @@ -56,7 +56,7 @@ pub(crate) fn validate_password_user_key( | UserLoginMethod::ApiKey { email, kdf, .. } => { let master_key = MasterKey::derive(&password, email, kdf)?; let user_key = master_key - .decrypt_user_key(encrypted_user_key.parse()?) + .decrypt_user_key(encrypted_user_key) .map_err(|_| WrongPasswordError)?; let key_store = client.internal.get_key_store(); @@ -80,7 +80,7 @@ pub(crate) fn validate_password_user_key( #[cfg(test)] mod tests { - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{EncString, Kdf}; use crate::auth::password::{validate::validate_password_user_key, validate_password}; @@ -135,16 +135,16 @@ mod tests { let master_key = MasterKey::derive(password, email, &kdf).unwrap(); - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; + let user_key: EncString = "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 .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.clone().into(), private_key) .unwrap(); let result = - validate_password_user_key(&client, "asdfasdfasdf".to_owned(), user_key.to_string()) + validate_password_user_key(&client, "asdfasdfasdf".to_owned(), user_key.into()) .unwrap(); assert_eq!(result, "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="); @@ -156,7 +156,7 @@ mod tests { fn test_validate_password_user_key_wrong_password() { use std::num::NonZeroU32; - use bitwarden_crypto::{Kdf, MasterKey}; + use bitwarden_crypto::{EncString, Kdf, MasterKey}; use crate::client::{Client, LoginMethod, UserLoginMethod}; @@ -178,16 +178,16 @@ mod tests { let master_key = MasterKey::derive(password, email, &kdf).unwrap(); - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; + let user_key: EncString = "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 .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.clone().into(), private_key) .unwrap(); let result = - validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string()) + validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.into()) .unwrap(); assert_eq!(result, "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="); diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs index e163e50d7..be8f0791c 100644 --- a/crates/bitwarden-core/src/auth/pin.rs +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{EncString, PinKey}; +use bitwarden_crypto::{PinKey, WrappedSymmetricKey}; use crate::{ auth::AuthValidateError, @@ -10,7 +10,7 @@ use crate::{ pub(crate) fn validate_pin( client: &Client, pin: String, - pin_protected_user_key: EncString, + pin_protected_user_key: WrappedSymmetricKey, ) -> Result { let login_method = client .internal @@ -46,7 +46,7 @@ pub(crate) fn validate_pin( mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::{Kdf, MasterKey}; + use bitwarden_crypto::{EncString, Kdf, MasterKey}; use super::*; use crate::client::{Client, LoginMethod, UserLoginMethod}; @@ -70,12 +70,12 @@ mod tests { let master_key = MasterKey::derive(password, email, &kdf).unwrap(); - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; + let user_key: EncString = "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 .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.into(), private_key) .unwrap(); client @@ -84,22 +84,22 @@ mod tests { #[test] fn test_validate_valid_pin() { let pin = "1234".to_string(); - let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" + let pin_protected_user_key: EncString = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" .parse() .unwrap(); let client = init_client(); - assert!(validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap()); + assert!(validate_pin(&client, pin.clone(), pin_protected_user_key.into()).unwrap()); } #[test] fn test_validate_invalid_pin() { let pin = "1234".to_string(); - let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" + let pin_protected_user_key: EncString = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" .parse() .unwrap(); let client = init_client(); - assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap()); + assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key.into()).unwrap()); } } diff --git a/crates/bitwarden-core/src/auth/register.rs b/crates/bitwarden-core/src/auth/register.rs index 75e43e323..ce04190b1 100644 --- a/crates/bitwarden-core/src/auth/register.rs +++ b/crates/bitwarden-core/src/auth/register.rs @@ -3,7 +3,7 @@ use bitwarden_api_identity::{ models::{KeysRequestModel, RegisterRequestModel}, }; use bitwarden_crypto::{ - default_pbkdf2_iterations, CryptoError, HashPurpose, Kdf, MasterKey, RsaKeyPair, + default_pbkdf2_iterations, CryptoError, EncString, HashPurpose, Kdf, MasterKey, RsaKeyPair, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -77,7 +77,7 @@ pub(super) fn make_register_keys( Ok(RegisterKeyResponse { master_password_hash, - encrypted_user_key: encrypted_user_key.to_string(), + encrypted_user_key: EncString::from(encrypted_user_key).to_string(), keys, }) } diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs index e365f0250..e681c7bf4 100644 --- a/crates/bitwarden-core/src/client/internal.rs +++ b/crates/bitwarden-core/src/client/internal.rs @@ -4,7 +4,9 @@ use bitwarden_crypto::KeyStore; #[cfg(any(feature = "internal", feature = "secrets"))] use bitwarden_crypto::SymmetricCryptoKey; #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString, Kdf, MasterKey, PinKey}; +use bitwarden_crypto::{ + AsymmetricEncString, EncString, Kdf, MasterKey, PinKey, WrappedSymmetricKey, +}; use chrono::Utc; use uuid::Uuid; @@ -176,7 +178,7 @@ impl InternalClient { pub(crate) fn initialize_user_crypto_master_key( &self, master_key: MasterKey, - user_key: EncString, + user_key: WrappedSymmetricKey, private_key: EncString, ) -> Result<(), EncryptionSettingsError> { let user_key = master_key.decrypt_user_key(user_key)?; @@ -200,7 +202,7 @@ impl InternalClient { pub(crate) fn initialize_user_crypto_pin( &self, pin_key: PinKey, - pin_protected_user_key: EncString, + pin_protected_user_key: WrappedSymmetricKey, private_key: EncString, ) -> Result<(), EncryptionSettingsError> { let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 7992ce95d..0217e63e1 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, Kdf, KeyDecryptable, - KeyEncryptable, MasterKey, SymmetricCryptoKey, UserKey, + KeyEncryptable, MasterKey, SymmetricCryptoKey, UserKey, WrappedSymmetricKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -116,6 +116,7 @@ pub async fn initialize_user_crypto( match req.method { InitUserCryptoMethod::Password { password, user_key } => { let user_key: EncString = user_key.parse()?; + let user_key: WrappedSymmetricKey = user_key.into(); let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?; client @@ -135,7 +136,7 @@ pub async fn initialize_user_crypto( let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; client.internal.initialize_user_crypto_pin( pin_key, - pin_protected_user_key, + pin_protected_user_key.into(), private_key, )?; } @@ -153,7 +154,7 @@ pub async fn initialize_user_crypto( } => auth_request_decrypt_master_key( request_private_key, protected_master_key, - auth_request_key, + auth_request_key.into(), )?, }; client @@ -185,7 +186,7 @@ pub async fn initialize_user_crypto( client .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + .initialize_user_crypto_master_key(master_key, user_key.into(), private_key)?; } } @@ -268,13 +269,13 @@ pub fn update_password( 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(), + new_password.as_bytes(), bitwarden_crypto::HashPurpose::ServerAuthorization, )?; Ok(UpdatePasswordResponse { password_hash, - new_key, + new_key: new_key.into(), }) } @@ -346,7 +347,7 @@ fn derive_pin_protected_user_key( LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?, }; - Ok(derived_key.encrypt_user_key(user_key)?) + Ok(derived_key.encrypt_user_key(user_key)?.into()) } /// Catch all errors for mobile crypto operations @@ -404,7 +405,7 @@ pub(super) fn derive_key_connector( ) -> Result { let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?; master_key - .decrypt_user_key(request.user_key_encrypted) + .decrypt_user_key(request.user_key_encrypted.into()) .map_err(|_| WrongPasswordError)?; Ok(master_key.to_base64()) @@ -723,11 +724,11 @@ mod tests { ) .unwrap(); - let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let user_key: EncString = "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 .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key) + .initialize_user_crypto_master_key(master_key, user_key.into(), 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-core/src/platform/generate_fingerprint.rs b/crates/bitwarden-core/src/platform/generate_fingerprint.rs index 7ad1c2a40..502b5b822 100644 --- a/crates/bitwarden-core/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden-core/src/platform/generate_fingerprint.rs @@ -73,14 +73,14 @@ pub(crate) fn generate_user_fingerprint( mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::{Kdf, MasterKey}; + use bitwarden_crypto::{EncString, Kdf, MasterKey}; use super::*; use crate::Client; #[test] fn test_generate_user_fingerprint() { - let user_key = "2.oZg5RYpU2HjUAKI1DUQCkg==|PyRzI9kZpt66P2OedH8CHOeU0/lgKLkhIJiKDijdyFqIemBSIBoslhfQh/P1TK9xgZp0smgD6+5+yNbZfOpBaCVrsT3WWAO78xOWizduRe4=|xfDLDZSJ+yZAdh388flVg7SMDBJuMs0+CHTjutKs4uQ="; + let user_key: EncString = "2.oZg5RYpU2HjUAKI1DUQCkg==|PyRzI9kZpt66P2OedH8CHOeU0/lgKLkhIJiKDijdyFqIemBSIBoslhfQh/P1TK9xgZp0smgD6+5+yNbZfOpBaCVrsT3WWAO78xOWizduRe4=|xfDLDZSJ+yZAdh388flVg7SMDBJuMs0+CHTjutKs4uQ=".parse().unwrap(); let private_key = "2.tY6WsWKUbBwNU8wROuipiQ==|DNFL1d19xVojUKTTy2gxT+9J1VXbMQLcbMnx1HSeA6U3yZhsLR6DPaGibb3Bp8doIHtrsxzL/JeLb4gLDZ8RnDhFfE4iLRaPakX14kbBXrKH9/uW/zc7TqIVciWhI1PaeFlu8wnVuGt3e5Ysx6Y7Uw7RS8pRT5aE3sX3aDPGZTAdTutLn1VUfkShS5OK5HJl9CdiwV2wOcrf4w/WqtaNUUqGdsJ8C4ELlpBzHxqs+lEm+8pGPYmuGQIjVc0eOR9Tza9GTk3ih1XGc1znOCoKUZbtA29RfbwfmJy/yGi/3RLWZFQGCCij4cLC5OpldiX4JWL5Dhox44p/5IVF3rfxTVz3GCyDOoHevRG/06sUBq6nhbdCQf3lJvxwcQJhoQg4rsapM3rgol+u+TbXRiwWPbfswuLkRlvGFKtKUWMa4S57gj0CFYgSBPdTyhZTB44D7JQ2bd901Ur1dYWcDe4Kn3ZawpxL0cX2ZPlE3v8FXFJf2s8DJytL8yu73GasDzVmaGHxueWWVz7EHjh+pmB4oaAHARcY8d3LActAyl/+bcFRPYQJ68ae6DJhYYJGHIBWMImf2BifGgUX8vUFfUAYjne3D82lRyZQHs3xbl+ZxEPgWiPYRWUtxGXLLP4f9mbl+LeJdehtHNjC8kOduBL0CsP4gmugzNNUXI+Izc/9svno6kFr6SU0LA3MGrOU8ao7UCQbf/Pj/RKnG1gRmBDQqf7IMm6jOyTwdde9NpfQb32iH11PkuAKBvEtUuq9BeAKWjoZku+ycsN2jZH0hzd/QrU2c+E4+yHwX3wSxxorNOXt5EZkJbEDBlpRyE1zWoyy0wIYfcChYLvFN8QFHchlw5wmHxL+OOgdgndAtV/2DCx+NB6caY31qLictME+1GPPlQ7QvicMLgmpSWq83rs4ex/My6p3hCRSrJJiLvjEDZLYWKHHLd5tsPRAjX8ADNWB1VeIeiJrj1wpOCc1PbWpbljbbTsBmVPo6iKm/UDGAHBdQ//0j3FQg8f5w/j+McsoaMpDNHNTiLvjWERR+RBmsEA0lEL00wZz/DHlzOAYHLYYqFMT7GBCQD+Wk/l1TL+X2agUy7Irlk7QbZ4ivfdNIpSW8Ct9MGE6o4wV+nIpXURojgBBTcP85RTBLXXGrIprnK1G/VE8ONag3+nkqIyChjYyk5QMsxqOqSHsbiOxhCdXypbCbY4g9yKJtBJ/ADjxmELj0X7pqsTFqC0eRT7rk9qTBcYBBu6rwlAfq8AKjDB7WjNjzLaMi6lBoe4petBn1xcLkXD5hHra0TULxcYrq8MIb+Vk4CBZZdwwyVm/28SwSjHBIBpRysPAonDDsp3KlahwXEFvRDQR/oFww172GI7cx8SoPn93Qh0JfpTAAowsO3meR8bzUSyd7v3rmtaBPsWHE9zUXye/6nloMU5joEcD6uJaxd0kdaWWIoKLH++zHW1R776wJrS6u+TIWZgHqiIJoCd9fV25BnQcbZRKd6mnfNQkchJ6c6ozXKrFaa8DLdERdfh84+isw5mzW2zMJwHEwtKt6LUTyieC2exzPAwPxJT1+IMjuzuwiLnvGKOq+kwE/LWBSB0ZfGuCP/3jMM8OCfe7Hbpt1TfXcUxUzj6sSjkjQB6qBt+TINRdOFA=|fppguME86utsAOKrBYn6XU95q7daVbZ+3dD9OVkQlAw="; let fingerprint_material = "a09726a0-9590-49d1-a5f5-afe300b6a515"; @@ -99,7 +99,7 @@ mod tests { .internal .initialize_user_crypto_master_key( master_key, - user_key.parse().unwrap(), + user_key.into(), private_key.parse().unwrap(), ) .unwrap(); diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 29e5b7e07..b9ca74bd1 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -12,7 +12,7 @@ use super::{ }; use crate::{ util::{self}, - CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey, + CryptoError, EncString, Result, SymmetricCryptoKey, UserKey, WrappedSymmetricKey, }; #[derive(Copy, Clone, JsonSchema)] @@ -66,17 +66,17 @@ impl MasterKey { } /// Generate a new random user key and encrypt it with the master key. - pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { + pub fn make_user_key(&self) -> Result<(UserKey, WrappedSymmetricKey)> { make_user_key(rand::thread_rng(), self) } /// Encrypt the users user key - pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { encrypt_user_key(self.inner_bytes(), user_key) } /// Decrypt the users user key - pub fn decrypt_user_key(&self, user_key: EncString) -> Result { + pub fn decrypt_user_key(&self, user_key: WrappedSymmetricKey) -> Result { decrypt_user_key(self.inner_bytes(), user_key) } @@ -111,18 +111,18 @@ impl From for MasterKey { pub(super) fn encrypt_user_key( master_key: &Pin>>, user_key: &SymmetricCryptoKey, -) -> Result { +) -> Result { let stretched_master_key = stretch_key(master_key)?; let user_key_bytes = Zeroizing::new(user_key.to_vec()); - EncString::encrypt_aes256_hmac(&user_key_bytes, &stretched_master_key) + EncString::encrypt_aes256_hmac(&user_key_bytes, &stretched_master_key).map(Into::into) } /// Helper function to decrypt a user key with a master or pin key or key-connector-key. pub(super) fn decrypt_user_key( key: &Pin>>, - user_key: EncString, + user_key: WrappedSymmetricKey, ) -> Result { - let mut dec: Vec = match user_key { + match user_key.as_ref() { // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support // decrypting these old keys. @@ -130,22 +130,20 @@ pub(super) fn decrypt_user_key( let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey { enc_key: Box::pin(GenericArray::clone_from_slice(key)), }); - user_key.decrypt_with_key(&legacy_key)? + user_key.unwrap(&legacy_key) } _ => { let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); - user_key.decrypt_with_key(&stretched_key)? + user_key.unwrap(&stretched_key) } - }; - - SymmetricCryptoKey::try_from(dec.as_mut_slice()) + } } /// Generate a new random user key and encrypt it with the master key. fn make_user_key( mut rng: impl rand::RngCore, master_key: &MasterKey, -) -> Result<(UserKey, EncString)> { +) -> Result<(UserKey, WrappedSymmetricKey)> { let user_key = SymmetricCryptoKey::generate(&mut rng); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) @@ -245,7 +243,7 @@ mod tests { ); // Ensure we can decrypt the key and get back the same key - let decrypted = master_key.decrypt_user_key(protected).unwrap(); + let decrypted = master_key.decrypt_user_key(protected.into()).unwrap(); assert_eq!( decrypted, user_key.0, @@ -261,7 +259,7 @@ mod tests { let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2")); let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); - let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); + let decrypted = master_key.decrypt_user_key(encrypted.into()).unwrap(); assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key"); } @@ -280,7 +278,7 @@ mod tests { let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap(); - let decrypted = master_key.decrypt_user_key(user_key).unwrap(); + let decrypted = master_key.decrypt_user_key(user_key.into()).unwrap(); let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else { panic!("Decrypted key is not an Aes256CbcHmacKey"); }; diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 0cced165a..4421cd293 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ keys::{key_encryptable::CryptoKey, utils::stretch_key}, - EncString, KeyEncryptable, Result, SymmetricCryptoKey, + EncString, KeyEncryptable, Result, SymmetricCryptoKey, WrappedSymmetricKey, }; /// Pin Key. @@ -19,12 +19,12 @@ impl PinKey { } /// Encrypt the users user key - pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { encrypt_user_key(&self.0 .0, user_key) } /// Decrypt the users user key - pub fn decrypt_user_key(&self, user_key: EncString) -> Result { + pub fn decrypt_user_key(&self, user_key: WrappedSymmetricKey) -> Result { decrypt_user_key(&self.0 .0, user_key) } } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 117bf8965..e929020d8 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -15,6 +15,8 @@ static ALLOC: ZeroizingAllocator = ZeroizingAllocator(std::a mod aes; mod enc_string; pub use enc_string::{AsymmetricEncString, EncString}; +mod safe; +pub use safe::WrappedSymmetricKey; mod error; pub use error::CryptoError; pub(crate) use error::Result; diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs new file mode 100644 index 000000000..09e441910 --- /dev/null +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -0,0 +1,70 @@ +use std::fmt::{Debug, Formatter}; + +use serde::{Deserialize, Serialize}; + +use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; + +pub struct WrappedSymmetricKey(EncString); + +impl Debug for WrappedSymmetricKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Serialize for WrappedSymmetricKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for WrappedSymmetricKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + EncString::deserialize(deserializer).map(WrappedSymmetricKey) + } +} + +impl From for WrappedSymmetricKey { + fn from(enc_string: EncString) -> Self { + WrappedSymmetricKey(enc_string) + } +} + +impl AsRef for WrappedSymmetricKey { + fn as_ref(&self) -> &EncString { + &self.0 + } +} + +impl From for EncString { + fn from(wrapped: WrappedSymmetricKey) -> Self { + wrapped.0 + } +} + +impl WrappedSymmetricKey { + pub fn unwrap( + &self, + wrapping_key: &SymmetricCryptoKey, + ) -> Result { + let decrypted_bytes: Vec = (&self.0).decrypt_with_key(wrapping_key)?; + Ok(SymmetricCryptoKey::try_from(decrypted_bytes)?) + } +} + +impl SymmetricCryptoKey { + pub fn wrap( + &self, + wrapping_key: &SymmetricCryptoKey, + ) -> Result { + let encoded = self.to_vec(); + let enc_string = encoded.encrypt_with_key(wrapping_key)?; + Ok(enc_string.into()) + } +} diff --git a/crates/bitwarden-crypto/src/safe/mod.rs b/crates/bitwarden-crypto/src/safe/mod.rs new file mode 100644 index 000000000..80731521e --- /dev/null +++ b/crates/bitwarden-crypto/src/safe/mod.rs @@ -0,0 +1,2 @@ +mod key_wrap; +pub use key_wrap::*; diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index b91b27e86..e51a427a9 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -110,10 +110,13 @@ impl AuthClient { password: String, encrypted_user_key: String, ) -> Result { + let encrypted_user_key = encrypted_user_key + .parse::() + .map_err(Error::Crypto)?; Ok(self .0 .auth() - .validate_password_user_key(password, encrypted_user_key) + .validate_password_user_key(password, encrypted_user_key.into()) .map_err(Error::AuthValidate)?) } @@ -128,7 +131,7 @@ impl AuthClient { Ok(self .0 .auth() - .validate_pin(pin, pin_protected_user_key) + .validate_pin(pin, pin_protected_user_key.into()) .map_err(Error::AuthValidate)?) } From 3f84a20d55e9ee347c4dd5a17333664f5f5c1d0b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 18 Apr 2025 20:20:24 +0200 Subject: [PATCH 064/152] Cleanup --- crates/bitwarden-crypto/src/keys/master_key.rs | 4 ++-- crates/bitwarden-crypto/src/safe/key_wrap.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index b9ca74bd1..6b1277f4d 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -243,7 +243,7 @@ mod tests { ); // Ensure we can decrypt the key and get back the same key - let decrypted = master_key.decrypt_user_key(protected.into()).unwrap(); + let decrypted = master_key.decrypt_user_key(protected).unwrap(); assert_eq!( decrypted, user_key.0, @@ -259,7 +259,7 @@ mod tests { let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2")); let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); - let decrypted = master_key.decrypt_user_key(encrypted.into()).unwrap(); + let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key"); } diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 09e441910..41e9b8932 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -53,8 +53,8 @@ impl WrappedSymmetricKey { &self, wrapping_key: &SymmetricCryptoKey, ) -> Result { - let decrypted_bytes: Vec = (&self.0).decrypt_with_key(wrapping_key)?; - Ok(SymmetricCryptoKey::try_from(decrypted_bytes)?) + let decrypted_bytes: Vec = self.0.decrypt_with_key(wrapping_key)?; + SymmetricCryptoKey::try_from(decrypted_bytes) } } From 0ff0e989abe56d193cce201df6c5d1ed8fea4bd7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:06:29 +0200 Subject: [PATCH 065/152] Update crates/bitwarden-crypto/src/enc_string/symmetric.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel García --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index b2fce367a..0bf6689a1 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -369,7 +369,7 @@ mod tests { let enc_key = [0u8; 32]; let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key { key_id, - enc_key: Box::pin(*GenericArray::from_slice(enc_key.as_slice())), + enc_key: Box::pin(enc_key.into()), }); let test_string = "encrypted_test_string"; From d892b79b0ffd58018a334e5e1e3fd5291a106aa9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:07:03 +0200 Subject: [PATCH 066/152] Update crates/bitwarden-crypto/src/enc_string/symmetric.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel García --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 0bf6689a1..de40de995 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -250,7 +250,7 @@ impl EncString { .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce.copy_from_slice(ciphertext.nonce.as_slice()); + nonce = ciphertext.nonce.into(); ciphertext.ciphertext }) .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) From a0919920b5d74d8509eeb484f0c83d6d2933d845 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:13:41 +0200 Subject: [PATCH 067/152] Change xchacha keywrap to todo --- crates/bitwarden-crypto/src/store/context.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 3aee6ab33..5dc9adba0 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -189,10 +189,7 @@ impl KeyStoreContext<'_, Ids> { encrypted } SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { - let cose_encoded_key = key_to_encrypt.to_encoded_raw(); - let encrypted = - EncString::encrypt_xchacha20_poly1305(cose_encoded_key.as_slice(), k); - encrypted + todo!(); } } } From 2bde54e1a2cd7b890fecc0e23dd522d1d447817b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:17:58 +0200 Subject: [PATCH 068/152] Make xchacha ciphertext struct members non-pub --- .../src/enc_string/symmetric.rs | 4 +-- crates/bitwarden-crypto/src/xchacha20.rs | 26 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index b2fce367a..f08c6ac8c 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -250,8 +250,8 @@ impl EncString { .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce.copy_from_slice(ciphertext.nonce.as_slice()); - ciphertext.ciphertext + nonce.copy_from_slice(ciphertext.nonce().as_slice()); + ciphertext.encrypted_bytes() }) .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) .build(); diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 98d954545..c85741026 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -21,8 +21,18 @@ use rand::{CryptoRng, RngCore}; use crate::CryptoError; pub(crate) struct XChaCha20Poly1305Ciphertext { - pub(crate) nonce: GenericArray::NonceSize>, - pub(crate) ciphertext: Vec, + nonce: GenericArray::NonceSize>, + encrypted_bytes: Vec, +} + +impl XChaCha20Poly1305Ciphertext { + pub(crate) fn nonce(&self) -> &[u8; 24] { + self.nonce.as_slice().try_into().unwrap() + } + + pub(crate) fn encrypted_bytes(&self) -> Vec { + self.encrypted_bytes.clone() + } } pub(crate) fn encrypt_xchacha20_poly1305( @@ -49,7 +59,7 @@ fn encrypt_xchacha20_poly1305_internal( XChaCha20Poly1305Ciphertext { nonce: *nonce, - ciphertext: buffer, + encrypted_bytes: buffer, } } @@ -83,7 +93,7 @@ mod tests { let decrypted = decrypt_xchacha20_poly1305( &encrypted.nonce.into(), &key, - &encrypted.ciphertext, + &encrypted.encrypted_bytes, authenticated_data, ) .unwrap(); @@ -98,11 +108,11 @@ mod tests { let mut encrypted = encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data); - encrypted.ciphertext[0] = encrypted.ciphertext[0].wrapping_add(1); + encrypted.encrypted_bytes[0] = encrypted.encrypted_bytes[0].wrapping_add(1); let result = decrypt_xchacha20_poly1305( &encrypted.nonce.into(), &key, - &encrypted.ciphertext, + &encrypted.encrypted_bytes, authenticated_data, ); assert!(result.is_err()); @@ -120,7 +130,7 @@ mod tests { let result = decrypt_xchacha20_poly1305( &encrypted.nonce.into(), &key, - &encrypted.ciphertext, + &encrypted.encrypted_bytes, authenticated_data.as_slice(), ); assert!(result.is_err()); @@ -138,7 +148,7 @@ mod tests { let result = decrypt_xchacha20_poly1305( &encrypted.nonce.into(), &key, - &encrypted.ciphertext, + &encrypted.encrypted_bytes, authenticated_data, ); assert!(result.is_err()); From 5ed9e2019084f1b8a13f099fe38fef47b34a65f2 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:26:56 +0200 Subject: [PATCH 069/152] Rename XChaCha20 encstring type to CoseEncrypt0 --- .../src/enc_string/symmetric.rs | 27 ++++++++++--------- .../bitwarden-crypto/src/keys/master_key.rs | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index f08c6ac8c..1cf748d3c 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -19,9 +19,9 @@ export type EncString = string; /// # Encrypted string primitive /// -/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. -/// They are are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and -/// decrypt data using [SymmetricCryptoKey]s. +/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted piece of +/// data, encoded as a string. They are are used together with the [KeyDecryptable] and +/// [KeyEncryptable] traits to encrypt and decrypt data using [SymmetricCryptoKey]s. /// /// The flexibility of the [EncString] type allows for different encryption algorithms to be used /// which is represented by the different variants of the enum. @@ -32,8 +32,10 @@ export type EncString = string; /// variants, but we should be opinionated in which variants are used for encrypting. /// /// ## Variants -/// - [AesCbc256_B64](EncString::AesCbc256_B64) +/// - [AesCbc256_B64](EncString::AesCbc256_B64) - Deprecated and MUST NOT be used for encrypting as +/// it is not authenticated /// - [AesCbc256_HmacSha256_B64](EncString::AesCbc256_HmacSha256_B64) +/// - [Cose_Encrypt0_B64](EncString::Cose_Encrypt0_B64) - The preferred variant for encrypting data. /// /// ## Serialization /// @@ -43,6 +45,7 @@ export type EncString = string; /// The scheme is one of the following schemes: /// - `[type].[iv]|[data]` /// - `[type].[iv]|[data]|[mac]` +/// - `[type].[data]` /// /// Where: /// - `[type]`: is a digit number representing the variant. @@ -65,7 +68,7 @@ pub enum EncString { data: Vec, }, // 7 The actual enc type is contained in the cose struct - XChaCha20_Poly1305_Cose_B64 { + Cose_Encrypt0_B64 { data: Vec, }, } @@ -100,7 +103,7 @@ impl FromStr for EncString { ("7", 1) => { let buffer = from_b64_vec(parts[0])?; - Ok(EncString::XChaCha20_Poly1305_Cose_B64 { data: buffer }) + Ok(EncString::Cose_Encrypt0_B64 { data: buffer }) } (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), @@ -139,7 +142,7 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } - 7 => Ok(EncString::XChaCha20_Poly1305_Cose_B64 { + 7 => Ok(EncString::Cose_Encrypt0_B64 { data: buf[1..].to_vec(), }), _ => Err(EncStringParseError::InvalidTypeSymm { @@ -167,7 +170,7 @@ impl EncString { buf.extend_from_slice(mac); buf.extend_from_slice(data); } - EncString::XChaCha20_Poly1305_Cose_B64 { data } => { + EncString::Cose_Encrypt0_B64 { data } => { buf = Vec::with_capacity(1 + data.len()); buf.push(self.enc_type()); buf.extend_from_slice(data); @@ -197,7 +200,7 @@ impl Display for EncString { EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { fmt_parts(f, enc_type, &[iv, data, mac]) } - EncString::XChaCha20_Poly1305_Cose_B64 { data } => { + EncString::Cose_Encrypt0_B64 { data } => { if let Ok(msg) = coset::CoseEncrypt0::from_slice(data.as_slice()) { write!(f, "{}.{:?}", enc_type, msg)?; } else { @@ -256,7 +259,7 @@ impl EncString { .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) .build(); - Ok(EncString::XChaCha20_Poly1305_Cose_B64 { + Ok(EncString::Cose_Encrypt0_B64 { data: cose_encrypt0.to_vec().map_err(|err| { CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) })?, @@ -268,7 +271,7 @@ impl EncString { match self { EncString::AesCbc256_B64 { .. } => 0, EncString::AesCbc256_HmacSha256_B64 { .. } => 2, - EncString::XChaCha20_Poly1305_Cose_B64 { .. } => 7, + EncString::Cose_Encrypt0_B64 { .. } => 7, } } } @@ -298,7 +301,7 @@ impl KeyDecryptable> for EncString { SymmetricCryptoKey::Aes256CbcHmacKey(key), ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), ( - EncString::XChaCha20_Poly1305_Cose_B64 { data }, + EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { let msg = coset::CoseEncrypt0::from_slice(data.as_slice()).map_err(|err| { diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 635bf36ac..70e8d1d5a 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -149,7 +149,7 @@ pub(super) fn decrypt_user_key( let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.decrypt_with_key(&stretched_key)? } - EncString::XChaCha20_Poly1305_Cose_B64 { .. } => { + EncString::Cose_Encrypt0_B64 { .. } => { return Err(CryptoError::OperationNotSupported( crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, )); From b2f3044695c35d978726c8fbb6be7fcd5a224f99 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:28:47 +0200 Subject: [PATCH 070/152] Merge changes --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 1cf748d3c..d7c4da009 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -253,7 +253,7 @@ impl EncString { .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce.copy_from_slice(ciphertext.nonce().as_slice()); + nonce = *ciphertext.nonce(); ciphertext.encrypted_bytes() }) .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) @@ -372,7 +372,7 @@ mod tests { let enc_key = [0u8; 32]; let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key { key_id, - enc_key: Box::pin(*GenericArray::from_slice(enc_key.as_slice())), + enc_key: Box::pin(enc_key.into()), }); let test_string = "encrypted_test_string"; From 08aa515be8e4f751eaef915b009a52915f59d3de Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:42:04 +0200 Subject: [PATCH 071/152] Fix build --- crates/bitwarden-crypto/src/store/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 085ce90cf..548d3df73 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -354,7 +354,7 @@ impl KeyStoreContext<'_, Ids> { let key = self.get_symmetric_key(key)?; match (data, key) { - (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { + (EncString::AesCbc256_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) } ( From 8441ad5cbd90db6d4408e1a984a8bce7989e076a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:45:14 +0200 Subject: [PATCH 072/152] Fix remaining build errors --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 12 ++++++------ crates/bitwarden-crypto/src/keys/master_key.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 88638f4b9..7310e2835 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -91,7 +91,7 @@ impl FromStr for EncString { let iv = from_b64(parts[0])?; let data = from_b64_vec(parts[1])?; - Ok(EncString::Aes256Cbc_B64 { iv, data }) + Ok(EncString::AesCbc256_B64 { iv, data }) } ("2", 3) => { let iv = from_b64(parts[0])?; @@ -132,7 +132,7 @@ impl EncString { let iv = buf[1..17].try_into().expect("Valid length"); let data = buf[17..].to_vec(); - Ok(EncString::Aes256Cbc_B64 { iv, data }) + Ok(EncString::AesCbc256_B64 { iv, data }) } 2 => { check_length(buf, 50)?; @@ -157,7 +157,7 @@ impl EncString { let mut buf; match self { - EncString::Aes256Cbc_B64 { iv, data } => { + EncString::AesCbc256_B64 { iv, data } => { buf = Vec::with_capacity(1 + 16 + data.len()); buf.push(self.enc_type()); buf.extend_from_slice(iv); @@ -197,7 +197,7 @@ impl Display for EncString { match self { EncString::AesCbc256_B64 { iv, data } => fmt_parts(f, enc_type, &[iv, data]), - EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { + EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => { fmt_parts(f, enc_type, &[iv, data, mac]) } EncString::Cose_Encrypt0_B64 { data } => { @@ -269,7 +269,7 @@ impl EncString { /// The numerical representation of the encryption type of the [EncString]. const fn enc_type(&self) -> u8 { match self { - EncString::Aes256Cbc_B64 { .. } => 0, + EncString::AesCbc256_B64 { .. } => 0, EncString::Aes256Cbc_HmacSha256_B64 { .. } => 2, EncString::Cose_Encrypt0_B64 { .. } => 7, } @@ -293,7 +293,7 @@ impl KeyEncryptable for &[u8] { impl KeyDecryptable> for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { match (self, key) { - (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { + (EncString::AesCbc256_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) } ( diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 14c93e788..359987d70 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -139,13 +139,13 @@ pub(super) fn decrypt_user_key( // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support // decrypting these old keys. - EncString::Aes256Cbc_B64 { .. } => { + EncString::AesCbc256_B64 { .. } => { let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey { enc_key: Box::pin(GenericArray::clone_from_slice(key)), }); user_key.decrypt_with_key(&legacy_key)? } - EncString::AesCbc256_HmacSha256_B64 { .. } => { + EncString::Aes256Cbc_HmacSha256_B64 { .. } => { let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.decrypt_with_key(&stretched_key)? } From 59e644d0290e2ff7c06238bcfea92b52f9c47bcd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:57:11 +0200 Subject: [PATCH 073/152] Fix clippy warnings --- .../src/enc_string/symmetric.rs | 17 ++++++++--------- crates/bitwarden-crypto/src/keys/master_key.rs | 2 +- .../src/keys/symmetric_crypto_key.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 2 +- crates/bitwarden-crypto/src/xchacha20.rs | 2 +- crates/bitwarden-vault/src/cipher/cipher.rs | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 7310e2835..37e291508 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -34,7 +34,7 @@ export type EncString = string; /// ## Variants /// - [Aes256Cbc_B64](EncString::AesCbc256_B64) - Deprecated and MUST NOT be used for encrypting as /// it is not authenticated -/// - [Aes256Cbc_HmacSha256_B64](EncString::AesCbc256_HmacSha256_B64) +/// - [Aes256Cbc_HmacSha256_B64](EncString::Aes256Cbc_HmacSha256_B64) /// - [Cose_Encrypt0_B64](EncString::Cose_Encrypt0_B64) - The preferred variant for encrypting data. /// /// ## Serialization @@ -56,7 +56,7 @@ export type EncString = string; #[allow(unused, non_camel_case_types)] pub enum EncString { /// 0 - AesCbc256_B64 { + Aes256Cbc_B64 { iv: [u8; 16], data: Vec, }, @@ -91,7 +91,7 @@ impl FromStr for EncString { let iv = from_b64(parts[0])?; let data = from_b64_vec(parts[1])?; - Ok(EncString::AesCbc256_B64 { iv, data }) + Ok(EncString::Aes256Cbc_B64 { iv, data }) } ("2", 3) => { let iv = from_b64(parts[0])?; @@ -132,7 +132,7 @@ impl EncString { let iv = buf[1..17].try_into().expect("Valid length"); let data = buf[17..].to_vec(); - Ok(EncString::AesCbc256_B64 { iv, data }) + Ok(EncString::Aes256Cbc_B64 { iv, data }) } 2 => { check_length(buf, 50)?; @@ -157,7 +157,7 @@ impl EncString { let mut buf; match self { - EncString::AesCbc256_B64 { iv, data } => { + EncString::Aes256Cbc_B64 { iv, data } => { buf = Vec::with_capacity(1 + 16 + data.len()); buf.push(self.enc_type()); buf.extend_from_slice(iv); @@ -196,7 +196,7 @@ impl Display for EncString { let enc_type = self.enc_type(); match self { - EncString::AesCbc256_B64 { iv, data } => fmt_parts(f, enc_type, &[iv, data]), + EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(f, enc_type, &[iv, data]), EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => { fmt_parts(f, enc_type, &[iv, data, mac]) } @@ -269,7 +269,7 @@ impl EncString { /// The numerical representation of the encryption type of the [EncString]. const fn enc_type(&self) -> u8 { match self { - EncString::AesCbc256_B64 { .. } => 0, + EncString::Aes256Cbc_B64 { .. } => 0, EncString::Aes256Cbc_HmacSha256_B64 { .. } => 2, EncString::Cose_Encrypt0_B64 { .. } => 7, } @@ -293,7 +293,7 @@ impl KeyEncryptable for &[u8] { impl KeyDecryptable> for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { match (self, key) { - (EncString::AesCbc256_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { + (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) } ( @@ -358,7 +358,6 @@ impl schemars::JsonSchema for EncString { #[cfg(test)] mod tests { - use generic_array::GenericArray; use schemars::schema_for; use super::EncString; diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 359987d70..0c00fae5f 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -139,7 +139,7 @@ pub(super) fn decrypt_user_key( // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support // decrypting these old keys. - EncString::AesCbc256_B64 { .. } => { + EncString::Aes256Cbc_B64 { .. } => { let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey { enc_key: Box::pin(GenericArray::clone_from_slice(key)), }); diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index a88d5a28f..f3a59360e 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -106,7 +106,7 @@ impl SymmetricCryptoKey { /// Generate a new random [SymmetricCryptoKey] /// @param rng: A random number generator - /// @param xchacha: If true, generate an XChaCha20Poly1305 key, otherwise generate an + /// @param xchacha20: If true, generate an XChaCha20Poly1305 key, otherwise generate an /// AES256_CBC_HMAC key pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha20: bool) -> Self { if !xchacha20 { diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 548d3df73..085ce90cf 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -354,7 +354,7 @@ impl KeyStoreContext<'_, Ids> { let key = self.get_symmetric_key(key)?; match (data, key) { - (EncString::AesCbc256_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { + (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) } ( diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index c85741026..3900c5f60 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -27,7 +27,7 @@ pub(crate) struct XChaCha20Poly1305Ciphertext { impl XChaCha20Poly1305Ciphertext { pub(crate) fn nonce(&self) -> &[u8; 24] { - self.nonce.as_slice().try_into().unwrap() + self.nonce.as_slice().try_into().expect("Nonce size is 24 bytes") } pub(crate) fn encrypted_bytes(&self) -> Vec { diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 9dfc3edae..da89f51fa 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1341,7 +1341,7 @@ mod tests { #[test] fn test_decrypt_fido2_private_key() { let key_store = - create_test_crypto_with_user_key(SymmetricCryptoKey::generate(rand::thread_rng())); + create_test_crypto_with_user_key(SymmetricCryptoKey::generate()); let mut ctx = key_store.context(); let mut cipher_view = generate_cipher(); From 4e0895f79acd014463f66a28be24d52203a2338c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:59:30 +0200 Subject: [PATCH 074/152] Cargo fmt --- crates/bitwarden-crypto/src/xchacha20.rs | 5 ++++- crates/bitwarden-vault/src/cipher/cipher.rs | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 3900c5f60..59a800658 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -27,7 +27,10 @@ pub(crate) struct XChaCha20Poly1305Ciphertext { impl XChaCha20Poly1305Ciphertext { pub(crate) fn nonce(&self) -> &[u8; 24] { - self.nonce.as_slice().try_into().expect("Nonce size is 24 bytes") + self.nonce + .as_slice() + .try_into() + .expect("Nonce size is 24 bytes") } pub(crate) fn encrypted_bytes(&self) -> Vec { diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index da89f51fa..e8ac13d18 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1340,8 +1340,7 @@ mod tests { #[test] fn test_decrypt_fido2_private_key() { - let key_store = - create_test_crypto_with_user_key(SymmetricCryptoKey::generate()); + let key_store = create_test_crypto_with_user_key(SymmetricCryptoKey::generate()); let mut ctx = key_store.context(); let mut cipher_view = generate_cipher(); From a8d073b6d506e86296c9908078a8a8dcd0cc4ad5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 13:09:07 +0200 Subject: [PATCH 075/152] Fix documentation --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 37e291508..804a7d920 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -32,7 +32,7 @@ export type EncString = string; /// variants, but we should be opinionated in which variants are used for encrypting. /// /// ## Variants -/// - [Aes256Cbc_B64](EncString::AesCbc256_B64) - Deprecated and MUST NOT be used for encrypting as +/// - [Aes256Cbc_B64](EncString::Aes256Cbc_B64) - Deprecated and MUST NOT be used for encrypting as /// it is not authenticated /// - [Aes256Cbc_HmacSha256_B64](EncString::Aes256Cbc_HmacSha256_B64) /// - [Cose_Encrypt0_B64](EncString::Cose_Encrypt0_B64) - The preferred variant for encrypting data. From 15b0f8e178395adbd6f1655c6ac215d07f7f1169 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 14:32:35 +0200 Subject: [PATCH 076/152] Add non-null test values --- crates/bitwarden-crypto/Cargo.toml | 1 + .../src/enc_string/asymmetric.rs | 19 ++++++++----------- .../src/keys/symmetric_crypto_key.rs | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 0a8c89366..9fd757d94 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -38,6 +38,7 @@ num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } rand = ">=0.8.5, <0.9" +rand_chacha = ">=0.3.1, <0.4.0" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" schemars = { workspace = true } diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 1a991609f..1ef6cb50a 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -263,11 +263,10 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_enc_string_rsa2048_oaep_sha256_b64() { let key_pair = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "3.BfwZTwBYbU5WQ5X7Vm8yl0hYmHTRdkVACCRZYcqhcjicoaPVDEP03CIRmtnppu0aXOppoQzhw5S2OKTUaqoOGKZg7+PrmVEhjiUFfVAptInBD6XGHZ0Z3u3F+JY1E3xIFebOFiX7KLQ+7D0bJhBEnl8P7phmanKF3Cil5ayDGRpAjAsBHMwlNRKXy05YpYs3/x+V+zjlxVrBU9gYFCpacKUbxT51I8tf21ISqo6H9ZBwqDE2QUPhYJl5op7SJgySdd3YCKnsObXa8fFj2OwxGLAXJAyvF6qZyl08RO/ZYUOOOPlbC7ywXxAISw3qmrwxqpLSBqAm9BYPa/zxBnTHrA=="; + let enc_str: &str = "3.SUx5gWrgmAKs/S1BoQrqOmx2Hl5fPVBVHokW17Flvm4TpBnJJRkfoitp7Jc4dfazPYjWGlckJz6X+qe+/AWilS1mxtzS0PmDy7tS5xP0GRlB39dstCd5jDw1wPmTbXiLcQ5VTvzpRAfRMEYVveTsEvVTByvEYAGSn4TnCsUDykyhRbD0YcJ4r1KHLs1b3BCBy2M1Gl5nmwckH08CAXaf8VfuBFStAGRKueovqp4euneQla+4G4fXdVvb8qKPnu0iVuALIE6nUNmeOiA3xN3d+akMxbbGxrQ1Ca4TYWjHVdj9C6abngQHkjKNYQwGUXrYo160hP4LIHn/huK6bZe5dQ=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_bytes = vec![0u8; 64]; - let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); + let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test").into(); assert_eq!(enc_string.enc_type(), 3); let res = enc_string.decapsulate_key_unsigned(&key_pair).unwrap(); @@ -277,29 +276,27 @@ XKZBokBGnjFnTnKcs7nv/O8= #[test] fn test_enc_string_rsa2048_oaep_sha1_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "4.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg=="; + let enc_str: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_bytes = vec![0u8; 64]; - let test_bytes = SymmetricCryptoKey::try_from(test_bytes).unwrap(); + let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test").into(); assert_eq!(enc_string.enc_type(), 4); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, test_bytes); + assert_eq!(res, test_key); } #[test] fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); - let enc_str: &str = "6.KhZmkc7f2WYuZGm/xlKZOK4c5JSwd9JtJvmyk0R+ZCqbRnZi5XNJaqnMiJjiqeLztE97bHRGWyDPvhyIisr7jLi35vL/Znpg3QzSMEDNI7aAM2FwJbCzdUrFDa/h08edv816AL1hAOqtGmjpfRL1j+47hlAiF3/srFCeePHkj0+CmHpHN13BN1XkLKk58mETKh8ky/ZUW2s4NjZaZ/Wxh6I9sv28L+u1hekKxDOdNKBnmqsh8WRBOtmZm1ZM9WI6aPA5tXgp30vxWrc1AsZ5Ts0aVkm8UzPTWuU9d/O9ICAQkr1hX58qO6M5geP+NvaG3UGymw0zp6Hdgz239XYpKg==|AA=="; + let enc_str: &str = "6.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==|AA=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_bytes = vec![0u8; 64]; - let test_key = SymmetricCryptoKey::try_from(test_bytes).unwrap(); + let test_key: SymmetricCryptoKey = SymmetricCryptoKey::generate_seeded_for_unit_tests("test"); assert_eq!(enc_string.enc_type(), 6); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); - assert_eq!(res, test_key); + assert_eq!(res.to_base64(), test_key.to_base64()); } #[test] diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 8908a2d75..7f052f144 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -3,7 +3,9 @@ use std::pin::Pin; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; use generic_array::GenericArray; -use rand::Rng; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaChaRng; +use sha2::Digest; use zeroize::{Zeroize, ZeroizeOnDrop}; use super::key_encryptable::CryptoKey; @@ -53,6 +55,13 @@ impl SymmetricCryptoKey { SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) } + /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS + /// IN PRODUCTION CODE. + pub fn generate_seeded_for_unit_tests(seed: &str) -> Self { + let seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into()); + Self::generate(seeded_rng) + } + fn total_len(&self) -> usize { match self { SymmetricCryptoKey::Aes256CbcKey(_) => 32, @@ -132,6 +141,12 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { } } +impl From for SymmetricCryptoKey { + fn from(key: Aes256CbcHmacKey) -> Self { + SymmetricCryptoKey::Aes256CbcHmacKey(key) + } +} + impl CryptoKey for SymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data From 239a297eeb4bbdea9e6056a7ab614d0b840cc597 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 14:36:15 +0200 Subject: [PATCH 077/152] Cargo fmt --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 1ef6cb50a..833b7d8b1 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -292,7 +292,8 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "6.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==|AA=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_key: SymmetricCryptoKey = SymmetricCryptoKey::generate_seeded_for_unit_tests("test"); + let test_key: SymmetricCryptoKey = + SymmetricCryptoKey::generate_seeded_for_unit_tests("test"); assert_eq!(enc_string.enc_type(), 6); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); From f63ad8b6a81a59628f58cc64d80ca626ff4031a4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 14:42:27 +0200 Subject: [PATCH 078/152] Remove into() --- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 833b7d8b1..b73056774 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -266,7 +266,7 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "3.SUx5gWrgmAKs/S1BoQrqOmx2Hl5fPVBVHokW17Flvm4TpBnJJRkfoitp7Jc4dfazPYjWGlckJz6X+qe+/AWilS1mxtzS0PmDy7tS5xP0GRlB39dstCd5jDw1wPmTbXiLcQ5VTvzpRAfRMEYVveTsEvVTByvEYAGSn4TnCsUDykyhRbD0YcJ4r1KHLs1b3BCBy2M1Gl5nmwckH08CAXaf8VfuBFStAGRKueovqp4euneQla+4G4fXdVvb8qKPnu0iVuALIE6nUNmeOiA3xN3d+akMxbbGxrQ1Ca4TYWjHVdj9C6abngQHkjKNYQwGUXrYo160hP4LIHn/huK6bZe5dQ=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test").into(); + let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test"); assert_eq!(enc_string.enc_type(), 3); let res = enc_string.decapsulate_key_unsigned(&key_pair).unwrap(); @@ -279,7 +279,7 @@ XKZBokBGnjFnTnKcs7nv/O8= let enc_str: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww=="; let enc_string: UnauthenticatedSharedKey = enc_str.parse().unwrap(); - let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test").into(); + let test_key = SymmetricCryptoKey::generate_seeded_for_unit_tests("test"); assert_eq!(enc_string.enc_type(), 4); let res = enc_string.decapsulate_key_unsigned(&private_key).unwrap(); From 7fb1a0a5fef9f5fb00fff2e7aeb386840d932283 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 15:02:01 +0200 Subject: [PATCH 079/152] Fix merge issue --- .../src/keys/symmetric_crypto_key.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 1ea2337d7..93fb21323 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -139,15 +139,6 @@ impl SymmetricCryptoKey { /// not use the byte representation but instead use the COSE key representation. pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); - } - /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS - /// IN PRODUCTION CODE. - pub fn generate_seeded_for_unit_tests(seed: &str) -> Self { - let seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into()); - Self::generate_internal(seeded_rng, false) - } - - fn total_len(&self) -> usize { match self { SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { encoded_key @@ -159,6 +150,13 @@ impl SymmetricCryptoKey { } } + /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS + /// IN PRODUCTION CODE. + pub fn generate_seeded_for_unit_tests(seed: &str) -> Self { + let seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into()); + Self::generate_internal(seeded_rng, false) + } + pub(crate) fn to_encoded_raw(&self) -> Vec { match self { SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), From 68b0ba283e096227d05cf82a7ee8e5feff57388c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 15:55:44 +0200 Subject: [PATCH 080/152] Clean up key wrapping --- crates/bitwarden-crypto/src/store/context.rs | 46 ++++++++------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 085ce90cf..312cd7381 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -160,39 +160,31 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `encryption_key` - The key id used to encrypt the `key_to_encrypt`. It must already exist + /// * `wrapping_key` - The key id used to wrap(encrypt) the `key_to_wrap`. It must already exist /// in the context - /// * `key_to_encrypt` - The key id to encrypt. It must already exist in the context + /// * `key_to_wrap` - The key id to wrap. It must already exist in the context pub fn encrypt_symmetric_key_with_symmetric_key( &self, - encryption_key: Ids::Symmetric, - key_to_encrypt: Ids::Symmetric, + wrapping_key: Ids::Symmetric, + key_to_wrap: Ids::Symmetric, ) -> Result { - let wrapping_key = self.get_symmetric_key(encryption_key)?; - match wrapping_key { + let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; + let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; + match (wrapping_key_instance, key_to_wrap_instance) { // These keys wrap directly by encrypting the key bytes of the inner key, with padding // applied in case it is needed - SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_encoded()) - } - // These keys wrap using CBOR. The content type needs to indicate what the format of the - // inner key is - SymmetricCryptoKey::XChaCha20Poly1305Key(k) => { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - match key_to_encrypt { - SymmetricCryptoKey::Aes256CbcKey(_) - | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { - let encoded_key = key_to_encrypt.to_encoded_raw(); - let encrypted = - EncString::encrypt_xchacha20_poly1305(encoded_key.as_slice(), k); - encrypted - } - SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { - todo!(); - } - } - } + (SymmetricCryptoKey::Aes256CbcHmacKey(_), SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_)) => { + self.encrypt_data_with_symmetric_key(wrapping_key, key_to_wrap_instance.to_encoded().as_slice()) + }, + (SymmetricCryptoKey::XChaCha20Poly1305Key(_), SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_)) => { + // These keys should be represented as octet stream payloads in cose + todo!() + }, + (SymmetricCryptoKey::XChaCha20Poly1305Key(_), SymmetricCryptoKey::XChaCha20Poly1305Key(_)) => { + // These keys should be represented as CoseKey payloads in cose + todo!() + }, + _ => Err(CryptoError::OperationNotSupported(UnsupportedOperation::EncryptionNotImplementedForKey)), } } From 3e99550ad55c81bc5aa6e8315ba882649d04b09e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 15:56:01 +0200 Subject: [PATCH 081/152] Cargo fmt --- crates/bitwarden-crypto/src/store/context.rs | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 312cd7381..f2952bcab 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -169,22 +169,34 @@ impl KeyStoreContext<'_, Ids> { key_to_wrap: Ids::Symmetric, ) -> Result { let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; - let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; + let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; match (wrapping_key_instance, key_to_wrap_instance) { // These keys wrap directly by encrypting the key bytes of the inner key, with padding // applied in case it is needed - (SymmetricCryptoKey::Aes256CbcHmacKey(_), SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_)) => { - self.encrypt_data_with_symmetric_key(wrapping_key, key_to_wrap_instance.to_encoded().as_slice()) - }, - (SymmetricCryptoKey::XChaCha20Poly1305Key(_), SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_)) => { + ( + SymmetricCryptoKey::Aes256CbcHmacKey(_), + SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_), + ) => self.encrypt_data_with_symmetric_key( + wrapping_key, + key_to_wrap_instance.to_encoded().as_slice(), + ), + ( + SymmetricCryptoKey::XChaCha20Poly1305Key(_), + SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_), + ) => { // These keys should be represented as octet stream payloads in cose todo!() - }, - (SymmetricCryptoKey::XChaCha20Poly1305Key(_), SymmetricCryptoKey::XChaCha20Poly1305Key(_)) => { + } + ( + SymmetricCryptoKey::XChaCha20Poly1305Key(_), + SymmetricCryptoKey::XChaCha20Poly1305Key(_), + ) => { // These keys should be represented as CoseKey payloads in cose todo!() - }, - _ => Err(CryptoError::OperationNotSupported(UnsupportedOperation::EncryptionNotImplementedForKey)), + } + _ => Err(CryptoError::OperationNotSupported( + UnsupportedOperation::EncryptionNotImplementedForKey, + )), } } From 2107c967ef4f38deb7751927f799bb601551a8ba Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 18:46:15 +0200 Subject: [PATCH 082/152] Cleanup --- .../src/keys/symmetric_crypto_key.rs | 78 ++++++++----------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 93fb21323..ef7f610a1 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -13,8 +13,8 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; use super::{key_encryptable::CryptoKey, key_id::KeyId}; use crate::{cose, CryptoError}; -/// Aes256CbcKey is a symmetric encryption key, consisting of one 256-bit key, -/// used to decrypt legacy type 0 encstrings. The data is not autenticated +/// [Aes256CbcKey] is a symmetric encryption key, consisting of one 256-bit key, +/// used to decrypt legacy type 0 enc strings. The data is not authenticated /// so this should be used with caution, and removed where possible. #[derive(ZeroizeOnDrop, Clone)] pub struct Aes256CbcKey { @@ -34,7 +34,7 @@ impl PartialEq for Aes256CbcKey { } } -/// Aes256CbcHmacKey is a symmetric encryption key consisting +/// [Aes256CbcHmacKey] is a symmetric encryption key consisting /// of two 256-bit keys, one for encryption and one for MAC #[derive(ZeroizeOnDrop, Clone)] pub struct Aes256CbcHmacKey { @@ -95,23 +95,6 @@ impl SymmetricCryptoKey { */ pub fn generate() -> Self { let mut rng = rand::thread_rng(); - Self::generate_internal(&mut rng, false) - } - - /** - * Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] - */ - pub fn generate_xchacha20() -> Self { - let mut rng = rand::thread_rng(); - Self::generate_internal(&mut rng, true) - } - - /// Generate a new random [SymmetricCryptoKey] - /// @param rng: A random number generator - /// @param xchacha20: If true, generate an XChaCha20Poly1305 key, otherwise generate an - /// AES256_CBC_HMAC key - pub(crate) fn generate_internal(mut rng: impl rand::RngCore, xchacha20: bool) -> Self { - if !xchacha20 { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); @@ -119,20 +102,25 @@ impl SymmetricCryptoKey { rng.fill(mac_key.as_mut_slice()); SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) - } else { - let mut enc_key = Box::pin(GenericArray::::default()); - rng.fill(enc_key.as_mut_slice()); - SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { - enc_key, - key_id: *KeyId::generate().as_bytes(), - }) - } + } + + /** + * Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] + */ + pub fn generate_xchacha20() -> Self { + let mut rng = rand::thread_rng(); + let mut enc_key = Box::pin(GenericArray::::default()); + rng.fill(enc_key.as_mut_slice()); + SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { + enc_key, + key_id: *KeyId::generate().as_bytes(), + }) } /// Encodes the key to a byte array representation, that is separated by size. - /// `SymmetricCryptoKey::Aes256CbcHmacKey` and `SymmetricCryptoKey::Aes256CbcKey` are - /// encoded as 64 and 32 bytes respectively. `SymmetricCryptoKey::XChaCha20Poly1305Key` - /// is encoded as at least 65 bytes, by using padding defined in `pad_key`. + /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are + /// encoded as 64 and 32 bytes respectively. [SymmetricCryptoKey::XChaCha20Poly1305Key] + /// is encoded as at least 65 bytes, by using padding defined in [`pad_key`]. /// /// This can be used for storage and transmission in the old byte array format. /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should @@ -327,12 +315,12 @@ impl std::fmt::Debug for SymmetricCryptoKey { /// The last N bytes of the padded bytes all have the value N. /// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. /// -/// Keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable -/// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays +/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable +/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays /// with no additional content format included in the encoding message. For this reason, the /// padding is used to make sure that the byte representation uniquely separates the keys by -/// size of the byte array. The previous key types `SymmetricCryptoKey::Aes256CbcHmacKey` and -/// `SymmetricCryptoKey::Aes256CbcKey` are 64 and 32 bytes long respectively. +/// size of the byte array. The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and +/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively. fn pad_key(key_bytes: &mut Vec, min_length: usize) { // at least 1 byte of padding is required let pad_bytes = min_length.saturating_sub(key_bytes.len()).max(1); @@ -340,16 +328,16 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { key_bytes.resize(padded_length, pad_bytes as u8); } -/// Unpad a key that is padded using the PKCS7-like padding defined by `pad_key`. +/// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key]. /// The last N bytes of the padded bytes all have the value N. /// For example, padded to size 4, the value 0,0 becomes 0,0,2,2. /// -/// Keys that have the type `SymmetricCryptoKey::XChaCha20Poly1305Key` must be distinguishable -/// from `SymmetricCryptoKey::Aes256CbcHmacKey` keys, when both are encoded as byte arrays +/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable +/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays /// with no additional content format included in the encoding message. For this reason, the /// padding is used to make sure that the byte representation uniquely separates the keys by -/// size of the byte array the previous key types `SymmetricCryptoKey::Aes256CbcHmacKey` and -/// `SymmetricCryptoKey::Aes256CbcKey` are 64 and 32 bytes long respectively. +/// size of the byte array the previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and +/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively. fn unpad_key(key_bytes: &[u8]) -> &[u8] { // this unwrap is safe, the input is always at least 1 byte long #[allow(clippy::unwrap_used)] @@ -388,7 +376,7 @@ mod tests { #[test] fn test_encode_decode_old_symmetric_crypto_key() { - let key = SymmetricCryptoKey::generate_internal(rand::thread_rng(), false); + let key = SymmetricCryptoKey::generate(); let encoded = key.to_encoded(); let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); assert_eq!(key, decoded); @@ -406,10 +394,10 @@ mod tests { #[test] fn test_encode_xchacha20_poly1305_key() { - let key = SymmetricCryptoKey::generate_internal(rand::thread_rng(), true); - let key_vec = key.to_encoded(); - let key_vec_utf8_lossy = String::from_utf8_lossy(&key_vec); - println!("key_vec: {:?}", key_vec_utf8_lossy); + let key = SymmetricCryptoKey::generate_xchacha20(); + let encoded = key.to_encoded(); + let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); + assert_eq!(key, decoded); } #[test] From a98d014e7b1424ddc9163ccd59981731c5b1bdd6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 18:58:56 +0200 Subject: [PATCH 083/152] Cleanup of generate key interfaces --- crates/bitwarden-core/src/auth/tde.rs | 2 +- crates/bitwarden-crypto/README.md | 2 +- .../src/keys/asymmetric_crypto_key.rs | 2 +- .../bitwarden-crypto/src/keys/device_key.rs | 2 +- .../bitwarden-crypto/src/keys/master_key.rs | 7 +++- .../src/keys/symmetric_crypto_key.rs | 37 +++++++++++++------ .../src/store/backend/implementation/mod.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 8 ++-- crates/bitwarden-crypto/src/store/mod.rs | 4 +- .../src/traits/encryptable.rs | 2 +- crates/bitwarden-exporters/src/models.rs | 4 +- crates/bitwarden-vault/src/cipher/cipher.rs | 28 +++++++------- .../src/pure_crypto.rs | 2 +- 13 files changed, 60 insertions(+), 42 deletions(-) diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index e402c9013..cb9441127 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -17,7 +17,7 @@ pub(super) fn make_register_tde_keys( ) -> Result { let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(org_public_key)?)?; - let user_key = UserKey::new(SymmetricCryptoKey::generate()); + let user_key = UserKey::new(SymmetricCryptoKey::generate_aes256_cbc_hmac()); let key_pair = user_key.make_key_pair()?; let admin_reset = UnauthenticatedSharedKey::encapsulate_key_unsigned(&user_key.0, &public_key)?; diff --git a/crates/bitwarden-crypto/README.md b/crates/bitwarden-crypto/README.md index 4abf78329..868a2dbbb 100644 --- a/crates/bitwarden-crypto/README.md +++ b/crates/bitwarden-crypto/README.md @@ -16,7 +16,7 @@ secure. use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; async fn example() -> Result<(), CryptoError> { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let data = "Hello, World!".to_owned(); let encrypted = data.clone().encrypt_with_key(&key)?; diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 5072a2de4..6938f9a9d 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -216,7 +216,7 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); - let raw_key = SymmetricCryptoKey::generate(); + let raw_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let encrypted = UnauthenticatedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index adc557ce3..1813be020 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -30,7 +30,7 @@ impl DeviceKey { /// from EncSettings. pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result { let mut rng = rand::thread_rng(); - let device_key = DeviceKey(SymmetricCryptoKey::generate()); + let device_key = DeviceKey(SymmetricCryptoKey::generate_aes256_cbc_hmac()); let device_private_key = AsymmetricCryptoKey::generate(&mut rng); diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 0c00fae5f..039e54b8a 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -160,8 +160,11 @@ pub(super) fn decrypt_user_key( } /// Generate a new random user key and encrypt it with the master key. -fn make_user_key(rng: impl rand::RngCore, master_key: &MasterKey) -> Result<(UserKey, EncString)> { - let user_key = SymmetricCryptoKey::generate_internal(rng, false); +/// +/// WARNING: This function should only be used with a proper cryptographic random number generator. +/// If you do not have a good reason for using this, use [MasterKey::make_user_key] instead. +fn make_user_key(mut rng: impl rand::RngCore, master_key: &MasterKey) -> Result<(UserKey, EncString)> { + let user_key = SymmetricCryptoKey::generate_aes256_cbc_hmac_internal(&mut rng); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index ef7f610a1..1888f5a2c 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -90,18 +90,26 @@ impl SymmetricCryptoKey { // enc type 2 old static format const AES256_CBC_HMAC_KEY_LEN: usize = 64; + /// Generate a new random AES256_CBC [SymmetricCryptoKey] + /// + /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not have + /// a good reason for using this function, use [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. + pub(crate) fn generate_aes256_cbc_hmac_internal(rng: &mut impl Rng) -> Self { + let mut enc_key = Box::pin(GenericArray::::default()); + let mut mac_key = Box::pin(GenericArray::::default()); + + rng.fill(enc_key.as_mut_slice()); + rng.fill(mac_key.as_mut_slice()); + + SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) + } + /** * Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey] */ - pub fn generate() -> Self { + pub fn generate_aes256_cbc_hmac() -> Self { let mut rng = rand::thread_rng(); - let mut enc_key = Box::pin(GenericArray::::default()); - let mut mac_key = Box::pin(GenericArray::::default()); - - rng.fill(enc_key.as_mut_slice()); - rng.fill(mac_key.as_mut_slice()); - - SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) + Self::generate_aes256_cbc_hmac_internal(&mut rng) } /** @@ -141,8 +149,15 @@ impl SymmetricCryptoKey { /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS /// IN PRODUCTION CODE. pub fn generate_seeded_for_unit_tests(seed: &str) -> Self { - let seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into()); - Self::generate_internal(seeded_rng, false) + // Keep this separate from the other generate function to not break test vectors. + let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into()); + let mut enc_key = Box::pin(GenericArray::::default()); + let mut mac_key = Box::pin(GenericArray::::default()); + + seeded_rng.fill(enc_key.as_mut_slice()); + seeded_rng.fill(mac_key.as_mut_slice()); + + SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) } pub(crate) fn to_encoded_raw(&self) -> Vec { @@ -376,7 +391,7 @@ mod tests { #[test] fn test_encode_decode_old_symmetric_crypto_key() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let encoded = key.to_encoded(); let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); assert_eq!(key, decoded); diff --git a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs index 69d4c69f5..fc6eb4fef 100644 --- a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs +++ b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs @@ -17,7 +17,7 @@ mod tests { fn test_creates_a_valid_store() { let mut store = create_store::(); - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); store.upsert(TestSymmKey::A(0), key.clone()); assert_eq!( diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index f2952bcab..92b89998a 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -257,7 +257,7 @@ impl KeyStoreContext<'_, Ids> { /// Generate a new random symmetric key and store it in the context pub fn generate_symmetric_key(&mut self, key_id: Ids::Symmetric) -> Result { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); #[allow(deprecated)] self.set_symmetric_key(key_id, key)?; Ok(key_id) @@ -402,7 +402,7 @@ mod tests { // Generate and insert a key let key_a0_id = TestSymmKey::A(0); - let key_a0 = SymmetricCryptoKey::generate(); + let key_a0 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); store .context_mut() @@ -424,7 +424,7 @@ mod tests { // Generate and insert a key let key_1_id = TestSymmKey::C(1); - let key_1 = SymmetricCryptoKey::generate(); + let key_1 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); ctx.set_symmetric_key(key_1_id, key_1.clone()).unwrap(); @@ -432,7 +432,7 @@ mod tests { // Generate and insert a new key let key_2_id = TestSymmKey::C(2); - let key_2 = SymmetricCryptoKey::generate(); + let key_2 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); ctx.set_symmetric_key(key_2_id, key_2.clone()).unwrap(); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 2123967d0..93f97ce18 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -65,7 +65,7 @@ pub use context::KeyStoreContext; /// let store: KeyStore = KeyStore::default(); /// /// #[allow(deprecated)] -/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate()); +/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate_aes256_cbc_hmac()); /// /// // Define some data that needs to be encrypted /// struct Data(String); @@ -353,7 +353,7 @@ pub(crate) mod tests { #[allow(deprecated)] store .context_mut() - .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate()) + .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate_aes256_cbc_hmac()) .unwrap(); } diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/traits/encryptable.rs index 916c13b78..b0395f9c9 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/traits/encryptable.rs @@ -83,7 +83,7 @@ mod tests { fn test_store() -> KeyStore { let store = KeyStore::::default(); - let symm_key = SymmetricCryptoKey::generate(); + let symm_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let asymm_key = AsymmetricCryptoKey::generate(&mut rand::thread_rng()); #[allow(deprecated)] diff --git a/crates/bitwarden-exporters/src/models.rs b/crates/bitwarden-exporters/src/models.rs index ebd6d6e1e..35de4491a 100644 --- a/crates/bitwarden-exporters/src/models.rs +++ b/crates/bitwarden-exporters/src/models.rs @@ -222,7 +222,7 @@ mod tests { #[test] fn test_from_login() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); @@ -273,7 +273,7 @@ mod tests { #[test] fn test_from_cipher_login() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index e8ac13d18..c34330974 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -901,7 +901,7 @@ mod tests { #[test] fn test_generate_cipher_key() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_key(key); let original_cipher = generate_cipher(); @@ -927,7 +927,7 @@ mod tests { #[test] fn test_generate_cipher_key_when_a_cipher_key_already_exists() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_key(key); let mut original_cipher = generate_cipher(); @@ -956,7 +956,7 @@ mod tests { #[test] fn test_generate_cipher_key_ignores_attachments_without_key() { - let key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_key(key); let mut cipher = generate_cipher(); @@ -979,8 +979,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(); - let org_key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -1004,8 +1004,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org_manually() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(); - let org_key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -1024,8 +1024,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_without_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(); - let org_key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let mut cipher = generate_cipher(); @@ -1048,8 +1048,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(); - let org_key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1116,8 +1116,8 @@ mod tests { #[test] fn test_move_user_cipher_with_key_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate(); - let org_key = SymmetricCryptoKey::generate(); + let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1340,7 +1340,7 @@ mod tests { #[test] fn test_decrypt_fido2_private_key() { - let key_store = create_test_crypto_with_user_key(SymmetricCryptoKey::generate()); + let key_store = create_test_crypto_with_user_key(SymmetricCryptoKey::generate_aes256_cbc_hmac()); let mut ctx = key_store.context(); let mut cipher_view = generate_cipher(); diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index ae9b787a3..82512cd62 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -74,7 +74,7 @@ impl PureCrypto { } pub fn generate_user_key() -> Vec { - SymmetricCryptoKey::generate().to_encoded() + SymmetricCryptoKey::generate_aes256_cbc_hmac().to_encoded() } pub fn generate_user_key_xchacha20() -> Vec { From cddf00647ef1eb70230f275872c6431f658e758d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 18:59:10 +0200 Subject: [PATCH 084/152] Cargo fmt --- crates/bitwarden-crypto/src/keys/master_key.rs | 7 +++++-- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 5 +++-- crates/bitwarden-crypto/src/store/mod.rs | 5 ++++- crates/bitwarden-vault/src/cipher/cipher.rs | 3 ++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 039e54b8a..2aaa56125 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -160,10 +160,13 @@ pub(super) fn decrypt_user_key( } /// Generate a new random user key and encrypt it with the master key. -/// +/// /// WARNING: This function should only be used with a proper cryptographic random number generator. /// If you do not have a good reason for using this, use [MasterKey::make_user_key] instead. -fn make_user_key(mut rng: impl rand::RngCore, master_key: &MasterKey) -> Result<(UserKey, EncString)> { +fn make_user_key( + mut rng: impl rand::RngCore, + master_key: &MasterKey, +) -> Result<(UserKey, EncString)> { let user_key = SymmetricCryptoKey::generate_aes256_cbc_hmac_internal(&mut rng); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 1888f5a2c..9044fe6cc 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -92,8 +92,9 @@ impl SymmetricCryptoKey { /// Generate a new random AES256_CBC [SymmetricCryptoKey] /// - /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not have - /// a good reason for using this function, use [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. + /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not + /// have a good reason for using this function, use + /// [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. pub(crate) fn generate_aes256_cbc_hmac_internal(rng: &mut impl Rng) -> Self { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 93f97ce18..7b4c35c79 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -353,7 +353,10 @@ pub(crate) mod tests { #[allow(deprecated)] store .context_mut() - .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate_aes256_cbc_hmac()) + .set_symmetric_key( + TestSymmKey::A(n), + SymmetricCryptoKey::generate_aes256_cbc_hmac(), + ) .unwrap(); } diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index c34330974..e12255c3b 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1340,7 +1340,8 @@ mod tests { #[test] fn test_decrypt_fido2_private_key() { - let key_store = create_test_crypto_with_user_key(SymmetricCryptoKey::generate_aes256_cbc_hmac()); + let key_store = + create_test_crypto_with_user_key(SymmetricCryptoKey::generate_aes256_cbc_hmac()); let mut ctx = key_store.context(); let mut cipher_view = generate_cipher(); From ae9a7da6c1f6f96cd04a526c5d0c31827594cae8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 19:06:21 +0200 Subject: [PATCH 085/152] Remove reference to pad_key --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 9044fe6cc..c16ed85b3 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -129,7 +129,7 @@ impl SymmetricCryptoKey { /// Encodes the key to a byte array representation, that is separated by size. /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are /// encoded as 64 and 32 bytes respectively. [SymmetricCryptoKey::XChaCha20Poly1305Key] - /// is encoded as at least 65 bytes, by using padding defined in [`pad_key`]. + /// is encoded as at least 65 bytes, using padding. /// /// This can be used for storage and transmission in the old byte array format. /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should From 3542d516637567f0147ca61f80afe69f6cc5283d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 17:09:39 +0200 Subject: [PATCH 086/152] Cargo fmt --- crates/bitwarden-core/src/mobile/crypto.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 0217e63e1..93beeb414 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -184,9 +184,11 @@ pub async fn initialize_user_crypto( let master_key = MasterKey::try_from(master_key_bytes.as_mut_slice())?; let user_key: EncString = user_key.parse()?; - client - .internal - .initialize_user_crypto_master_key(master_key, user_key.into(), private_key)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key.into(), + private_key, + )?; } } @@ -269,7 +271,7 @@ pub fn update_password( 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(), + new_password.as_bytes(), bitwarden_crypto::HashPurpose::ServerAuthorization, )?; From df634d7987c98bfc4829498d59669b79e32d0a9d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:10:41 +0200 Subject: [PATCH 087/152] Migrate more code to use keywrap --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 22 ++++++++++++++++ crates/bitwarden-crypto/src/safe/mod.rs | 2 ++ crates/bitwarden-crypto/src/store/context.rs | 19 +++++++------- .../bitwarden-vault/src/cipher/attachment.rs | 8 +++--- crates/bitwarden-vault/src/cipher/cipher.rs | 25 ++++++++++--------- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 41e9b8932..7e0528912 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; +#[derive(Clone, PartialEq)] pub struct WrappedSymmetricKey(EncString); impl Debug for WrappedSymmetricKey { @@ -49,6 +50,7 @@ impl From for EncString { } impl WrappedSymmetricKey { + /// Unwraps a the wrapped symmetric key using the provided wrapping key, returning the contained wrapped key. pub fn unwrap( &self, wrapping_key: &SymmetricCryptoKey, @@ -59,6 +61,9 @@ impl WrappedSymmetricKey { } impl SymmetricCryptoKey { + /// Wraps (encrypts) the key using the provided wrapping key. + /// + /// Use this if you have a symmetric crypto key that should protect another symmetric crypto key. pub fn wrap( &self, wrapping_key: &SymmetricCryptoKey, @@ -68,3 +73,20 @@ impl SymmetricCryptoKey { Ok(enc_string.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wrap_unwrap() { + let mut rng = rand::thread_rng(); + let wrapping_key = SymmetricCryptoKey::generate(&mut rng); + let key = SymmetricCryptoKey::generate(&mut rng); + + let wrapped_key = key.wrap(&wrapping_key).unwrap(); + let unwrapped_key = wrapped_key.unwrap(&wrapping_key).unwrap(); + + assert_eq!(key, unwrapped_key); + } +} \ No newline at end of file diff --git a/crates/bitwarden-crypto/src/safe/mod.rs b/crates/bitwarden-crypto/src/safe/mod.rs index 80731521e..06b46ae4b 100644 --- a/crates/bitwarden-crypto/src/safe/mod.rs +++ b/crates/bitwarden-crypto/src/safe/mod.rs @@ -1,2 +1,4 @@ +//! This module contains safe cryptographic building blocks that should be used +//! first and foremost, before any other functions from the bitwarden-crypto crate. mod key_wrap; pub use key_wrap::*; diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 933562a41..01b484f7f 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -10,7 +10,7 @@ use super::KeyStoreInner; use crate::{ derive_shareable_key, error::UnsupportedOperation, store::backend::StoreBackend, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, KeyId, KeyIds, Result, - SymmetricCryptoKey, + SymmetricCryptoKey, WrappedSymmetricKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -141,15 +141,15 @@ impl KeyStoreContext<'_, Ids> { &mut self, encryption_key: Ids::Symmetric, new_key_id: Ids::Symmetric, - encrypted_key: &EncString, + encrypted_key: &WrappedSymmetricKey, ) -> Result { - let mut new_key_material = - self.decrypt_data_with_symmetric_key(encryption_key, encrypted_key)?; + let encryption_key = self.get_symmetric_key(encryption_key)?; + let unwrapped_key = encrypted_key.unwrap(encryption_key)?; #[allow(deprecated)] self.set_symmetric_key( new_key_id, - SymmetricCryptoKey::try_from(new_key_material.as_mut_slice())?, + unwrapped_key, )?; // Returning the new key identifier for convenience @@ -168,9 +168,10 @@ impl KeyStoreContext<'_, Ids> { &self, encryption_key: Ids::Symmetric, key_to_encrypt: Ids::Symmetric, - ) -> Result { - let key_to_encrypt = self.get_symmetric_key(key_to_encrypt)?; - self.encrypt_data_with_symmetric_key(encryption_key, &key_to_encrypt.to_vec()) + ) -> Result { + let key_to_wrap = self.get_symmetric_key(key_to_encrypt)?; + let wrapping_key = self.get_symmetric_key(encryption_key)?; + key_to_wrap.wrap(wrapping_key) } /// Decrypt a symmetric key into the context by using an already existing asymmetric key @@ -450,7 +451,7 @@ mod tests { // Decrypt the new key with the old key in a different identifier let new_key_id = TestSymmKey::C(3); - ctx.decrypt_symmetric_key_with_symmetric_key(key_1_id, new_key_id, &key_2_enc) + ctx.decrypt_symmetric_key_with_symmetric_key(key_1_id, new_key_id, &key_2_enc.into()) .unwrap(); // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index 895672146..ceb9590b3 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -1,6 +1,6 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -80,7 +80,7 @@ impl Encryptable for Attachment ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; let mut attachment = self.attachment.clone(); @@ -120,14 +120,14 @@ impl Decryptable> for AttachmentFile { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result, CryptoError> { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; // Version 2 or 3, `AttachmentKey` or `CipherKey(AttachmentKey)` if let Some(attachment_key) = &self.attachment.key { let content_key = ctx.decrypt_symmetric_key_with_symmetric_key( ciphers_key, ATTACHMENT_KEY, - attachment_key, + &attachment_key.clone().into(), )?; self.contents.decrypt(ctx, content_key) } else { diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index c42d041d3..8b2fba197 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -4,7 +4,7 @@ use bitwarden_core::{ require, MissingFieldError, VaultLockedError, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, WrappedSymmetricKey, }; use bitwarden_error::bitwarden_error; use chrono::{DateTime, Utc}; @@ -198,7 +198,7 @@ impl CipherListView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let totp = match self.r#type { CipherListViewType::Login(LoginListView { totp, .. }) => { @@ -217,7 +217,7 @@ impl Encryptable for CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let mut cipher_view = self.clone(); @@ -263,7 +263,7 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let mut cipher = CipherView { id: self.id, @@ -321,12 +321,12 @@ impl Cipher { pub(super) fn decrypt_cipher_key( ctx: &mut KeyStoreContext, key: SymmetricKeyId, - ciphers_key: &Option, + ciphers_key: &Option, ) -> Result { const CIPHER_KEY: SymmetricKeyId = SymmetricKeyId::Local("cipher_key"); match ciphers_key { Some(ciphers_key) => { - ctx.decrypt_symmetric_key_with_symmetric_key(key, CIPHER_KEY, ciphers_key) + ctx.decrypt_symmetric_key_with_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone().into()) } None => Ok(key), } @@ -456,7 +456,7 @@ impl CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result<(), CryptoError> { - let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -505,7 +505,7 @@ impl CipherView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; Ok(self .login @@ -566,7 +566,7 @@ impl CipherView { ) -> Result<(), CipherError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; require!(self.login.as_mut()).fido2_credentials = Some(creds.encrypt(ctx, ciphers_key)?); @@ -579,7 +579,7 @@ impl CipherView { ) -> Result, CipherError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let login = require!(self.login.as_ref()); let creds = require!(login.fido2_credentials.as_ref()); @@ -603,7 +603,7 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; Ok(CipherListView { id: self.id, @@ -1350,7 +1350,8 @@ mod tests { .unwrap(); let key_id = cipher_view.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(&mut ctx, key_id, &cipher_view.key).unwrap(); + let ciphers_key = Cipher::decrypt_cipher_key(&mut ctx, key_id, &cipher_view.key.clone().map(|k| k.into())) + .unwrap(); let fido2_credential = generate_fido2(&mut ctx, ciphers_key); From f2998a85ba1ed4c9e7183f1447f7b255ba28a082 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:32:35 +0200 Subject: [PATCH 088/152] Expose to purecrypto and fix build issues --- crates/bitwarden-vault/src/cipher/attachment.rs | 2 +- crates/bitwarden-vault/src/cipher/cipher.rs | 16 +++++++++------- .../bitwarden-wasm-internal/src/pure_crypto.rs | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index ceb9590b3..c4818dd58 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -89,7 +89,7 @@ impl Encryptable for Attachment let attachment_key = ctx.generate_symmetric_key(ATTACHMENT_KEY)?; let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; attachment.key = - Some(ctx.encrypt_symmetric_key_with_symmetric_key(ciphers_key, attachment_key)?); + Some(ctx.encrypt_symmetric_key_with_symmetric_key(ciphers_key, attachment_key)?.into()); let contents = encrypted_contents.to_buffer()?; diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 8b2fba197..d6219fd78 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -465,7 +465,7 @@ impl CipherView { self.reencrypt_attachment_keys(ctx, old_ciphers_key, new_key)?; self.reencrypt_fido2_credentials(ctx, old_ciphers_key, new_key)?; - self.key = Some(ctx.encrypt_symmetric_key_with_symmetric_key(key, new_key)?); + self.key = Some(ctx.encrypt_symmetric_key_with_symmetric_key(key, new_key)?.into()); Ok(()) } @@ -938,7 +938,7 @@ mod tests { original_cipher.key = Some( ctx.encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, cipher_key) - .unwrap(), + .unwrap().into(), ); } @@ -1078,7 +1078,7 @@ mod tests { size: None, size_name: None, file_name: Some("Attachment test name".into()), - key: Some(attachment_key_enc), + key: Some(attachment_key_enc.into()), }; cipher.attachments = Some(vec![attachment]); let cred = generate_fido2(&mut key_store.context(), SymmetricKeyId::User); @@ -1128,15 +1128,17 @@ mod tests { .unwrap(); let cipher_key_enc = ctx .encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, cipher_key) - .unwrap(); + .unwrap() + .into(); // Attachment has a key that is encrypted with the cipher key let attachment_key = ctx .generate_symmetric_key(SymmetricKeyId::Local("test_attachment_key")) .unwrap(); - let attachment_key_enc = ctx + let attachment_key_enc: EncString = ctx .encrypt_symmetric_key_with_symmetric_key(cipher_key, attachment_key) - .unwrap(); + .unwrap() + .into(); let mut cipher = generate_cipher(); cipher.key = Some(cipher_key_enc); @@ -1147,7 +1149,7 @@ mod tests { size: None, size_name: None, file_name: Some("Attachment test name".into()), - key: Some(attachment_key_enc.clone()), + key: Some(attachment_key_enc.clone()) }; cipher.attachments = Some(vec![attachment]); diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 0238c909b..1cfb8673c 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, WrappedSymmetricKey, }; use wasm_bindgen::prelude::*; @@ -32,6 +32,20 @@ impl PureCrypto { EncString::from_buffer(&enc_bytes)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } + pub fn wrap_symmetric_key(key_to_be_wrapped: Vec, wrapping_key: Vec) -> Result { + let key_to_be_wrapped: SymmetricCryptoKey = key_to_be_wrapped.try_into()?; + let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; + let wrapped_key: EncString = key_to_be_wrapped.wrap(&wrapping_key)?.into(); + Ok(wrapped_key.to_string()) + } + + pub fn unwrap_symmetric_key(wrapped_key: String, wrapping_key: Vec) -> Result, CryptoError> { + let wrapped_key: WrappedSymmetricKey = EncString::from_str(&wrapped_key)?.into(); + let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; + let unwrapped_key: SymmetricCryptoKey = wrapped_key.unwrap(&wrapping_key)?; + Ok(unwrapped_key.to_vec()) + } + pub fn symmetric_encrypt(plain: String, key: Vec) -> Result { plain .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) From 16dc1e4c494f49d97028d47b0d5849fe4ba7dc76 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:35:18 +0200 Subject: [PATCH 089/152] Cleanup --- crates/bitwarden-vault/src/cipher/cipher.rs | 2 +- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index d6219fd78..ffa921d04 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -326,7 +326,7 @@ impl Cipher { const CIPHER_KEY: SymmetricKeyId = SymmetricKeyId::Local("cipher_key"); match ciphers_key { Some(ciphers_key) => { - ctx.decrypt_symmetric_key_with_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone().into()) + ctx.decrypt_symmetric_key_with_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone()) } None => Ok(key), } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 1cfb8673c..a519ec230 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -32,14 +32,20 @@ impl PureCrypto { EncString::from_buffer(&enc_bytes)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } - pub fn wrap_symmetric_key(key_to_be_wrapped: Vec, wrapping_key: Vec) -> Result { + pub fn wrap_symmetric_key( + key_to_be_wrapped: Vec, + wrapping_key: Vec, + ) -> Result { let key_to_be_wrapped: SymmetricCryptoKey = key_to_be_wrapped.try_into()?; let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; let wrapped_key: EncString = key_to_be_wrapped.wrap(&wrapping_key)?.into(); Ok(wrapped_key.to_string()) } - pub fn unwrap_symmetric_key(wrapped_key: String, wrapping_key: Vec) -> Result, CryptoError> { + pub fn unwrap_symmetric_key( + wrapped_key: String, + wrapping_key: Vec, + ) -> Result, CryptoError> { let wrapped_key: WrappedSymmetricKey = EncString::from_str(&wrapped_key)?.into(); let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; let unwrapped_key: SymmetricCryptoKey = wrapped_key.unwrap(&wrapping_key)?; From e53ef3508fdc02c59f6ba2aa163b82062b947fdd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:36:54 +0200 Subject: [PATCH 090/152] Cargo fmt --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 10 +++-- crates/bitwarden-crypto/src/store/context.rs | 5 +-- .../bitwarden-vault/src/cipher/attachment.rs | 14 +++--- crates/bitwarden-vault/src/cipher/cipher.rs | 45 +++++++++++++------ 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 7e0528912..dbf49d732 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -50,7 +50,8 @@ impl From for EncString { } impl WrappedSymmetricKey { - /// Unwraps a the wrapped symmetric key using the provided wrapping key, returning the contained wrapped key. + /// Unwraps a the wrapped symmetric key using the provided wrapping key, returning the contained + /// wrapped key. pub fn unwrap( &self, wrapping_key: &SymmetricCryptoKey, @@ -62,8 +63,9 @@ impl WrappedSymmetricKey { impl SymmetricCryptoKey { /// Wraps (encrypts) the key using the provided wrapping key. - /// - /// Use this if you have a symmetric crypto key that should protect another symmetric crypto key. + /// + /// Use this if you have a symmetric crypto key that should protect another symmetric crypto + /// key. pub fn wrap( &self, wrapping_key: &SymmetricCryptoKey, @@ -89,4 +91,4 @@ mod tests { assert_eq!(key, unwrapped_key); } -} \ No newline at end of file +} diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 01b484f7f..4fde806cd 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -147,10 +147,7 @@ impl KeyStoreContext<'_, Ids> { let unwrapped_key = encrypted_key.unwrap(encryption_key)?; #[allow(deprecated)] - self.set_symmetric_key( - new_key_id, - unwrapped_key, - )?; + self.set_symmetric_key(new_key_id, unwrapped_key)?; // Returning the new key identifier for convenience Ok(new_key_id) diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index c4818dd58..40063bcf7 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -1,6 +1,6 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -80,7 +80,8 @@ impl Encryptable for Attachment ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; let mut attachment = self.attachment.clone(); @@ -88,8 +89,10 @@ impl Encryptable for Attachment // with it, and then encrypt the key with the cipher key let attachment_key = ctx.generate_symmetric_key(ATTACHMENT_KEY)?; let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; - attachment.key = - Some(ctx.encrypt_symmetric_key_with_symmetric_key(ciphers_key, attachment_key)?.into()); + attachment.key = Some( + ctx.encrypt_symmetric_key_with_symmetric_key(ciphers_key, attachment_key)? + .into(), + ); let contents = encrypted_contents.to_buffer()?; @@ -120,7 +123,8 @@ impl Decryptable> for AttachmentFile { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result, CryptoError> { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; // Version 2 or 3, `AttachmentKey` or `CipherKey(AttachmentKey)` if let Some(attachment_key) = &self.attachment.key { diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index ffa921d04..e6fe62634 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -4,7 +4,8 @@ use bitwarden_core::{ require, MissingFieldError, VaultLockedError, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, WrappedSymmetricKey, + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + WrappedSymmetricKey, }; use bitwarden_error::bitwarden_error; use chrono::{DateTime, Utc}; @@ -198,7 +199,8 @@ impl CipherListView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let totp = match self.r#type { CipherListViewType::Login(LoginListView { totp, .. }) => { @@ -217,7 +219,8 @@ impl Encryptable for CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let mut cipher_view = self.clone(); @@ -263,7 +266,8 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let mut cipher = CipherView { id: self.id, @@ -456,7 +460,8 @@ impl CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result<(), CryptoError> { - let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let old_ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -465,7 +470,10 @@ impl CipherView { self.reencrypt_attachment_keys(ctx, old_ciphers_key, new_key)?; self.reencrypt_fido2_credentials(ctx, old_ciphers_key, new_key)?; - self.key = Some(ctx.encrypt_symmetric_key_with_symmetric_key(key, new_key)?.into()); + self.key = Some( + ctx.encrypt_symmetric_key_with_symmetric_key(key, new_key)? + .into(), + ); Ok(()) } @@ -505,7 +513,8 @@ impl CipherView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; Ok(self .login @@ -566,7 +575,8 @@ impl CipherView { ) -> Result<(), CipherError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; require!(self.login.as_mut()).fido2_credentials = Some(creds.encrypt(ctx, ciphers_key)?); @@ -579,7 +589,8 @@ impl CipherView { ) -> Result, CipherError> { let key = self.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; let login = require!(self.login.as_ref()); let creds = require!(login.fido2_credentials.as_ref()); @@ -603,7 +614,8 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + let ciphers_key = + Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; Ok(CipherListView { id: self.id, @@ -938,7 +950,8 @@ mod tests { original_cipher.key = Some( ctx.encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, cipher_key) - .unwrap().into(), + .unwrap() + .into(), ); } @@ -1149,7 +1162,7 @@ mod tests { size: None, size_name: None, file_name: Some("Attachment test name".into()), - key: Some(attachment_key_enc.clone()) + key: Some(attachment_key_enc.clone()), }; cipher.attachments = Some(vec![attachment]); @@ -1352,8 +1365,12 @@ mod tests { .unwrap(); let key_id = cipher_view.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key(&mut ctx, key_id, &cipher_view.key.clone().map(|k| k.into())) - .unwrap(); + let ciphers_key = Cipher::decrypt_cipher_key( + &mut ctx, + key_id, + &cipher_view.key.clone().map(|k| k.into()), + ) + .unwrap(); let fido2_credential = generate_fido2(&mut ctx, ciphers_key); From f416613e777cb0b9af98062cbd2d0332e3891487 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:37:19 +0200 Subject: [PATCH 091/152] Fix clippy error --- crates/bitwarden-crypto/src/store/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 4fde806cd..5d04f8b79 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -448,7 +448,7 @@ mod tests { // Decrypt the new key with the old key in a different identifier let new_key_id = TestSymmKey::C(3); - ctx.decrypt_symmetric_key_with_symmetric_key(key_1_id, new_key_id, &key_2_enc.into()) + ctx.decrypt_symmetric_key_with_symmetric_key(key_1_id, new_key_id, &key_2_enc) .unwrap(); // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt From 69e8ce20eb03100a6ce5acfde6f7f86b66aa5725 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 22 Apr 2025 18:49:12 +0200 Subject: [PATCH 092/152] Fix docs --- crates/bitwarden-crypto/src/store/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 5d04f8b79..be63fd576 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -43,7 +43,7 @@ use crate::{ /// # pub Ids => SymmKeyId, AsymmKeyId; /// # } /// struct Data { -/// key: EncString, +/// key: WrappedSymmetricKey, /// name: String, /// } /// # impl IdentifyKey for Data { From aa064f1159718b78ecef05f140d4615a580636d7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 13:11:37 +0200 Subject: [PATCH 093/152] Cleanup --- .../bitwarden-crypto/src/keys/master_key.rs | 9 +++-- crates/bitwarden-crypto/src/safe/key_wrap.rs | 36 +++---------------- crates/bitwarden-crypto/src/store/context.rs | 4 +-- .../src/pure_crypto.rs | 4 +-- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 6b1277f4d..dcafd14e9 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -112,9 +112,8 @@ pub(super) fn encrypt_user_key( master_key: &Pin>>, user_key: &SymmetricCryptoKey, ) -> Result { - let stretched_master_key = stretch_key(master_key)?; - let user_key_bytes = Zeroizing::new(user_key.to_vec()); - EncString::encrypt_aes256_hmac(&user_key_bytes, &stretched_master_key).map(Into::into) + let stretched_master_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(master_key)?); + user_key.wrap_with(&stretched_master_key) } /// Helper function to decrypt a user key with a master or pin key or key-connector-key. @@ -130,11 +129,11 @@ pub(super) fn decrypt_user_key( let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey { enc_key: Box::pin(GenericArray::clone_from_slice(key)), }); - user_key.unwrap(&legacy_key) + user_key.unwrap_with(&legacy_key) } _ => { let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); - user_key.unwrap(&stretched_key) + user_key.unwrap_with(&stretched_key) } } } diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index dbf49d732..b490f0600 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -1,36 +1,8 @@ -use std::fmt::{Debug, Formatter}; - -use serde::{Deserialize, Serialize}; - use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; #[derive(Clone, PartialEq)] pub struct WrappedSymmetricKey(EncString); -impl Debug for WrappedSymmetricKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl Serialize for WrappedSymmetricKey { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for WrappedSymmetricKey { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - EncString::deserialize(deserializer).map(WrappedSymmetricKey) - } -} - impl From for WrappedSymmetricKey { fn from(enc_string: EncString) -> Self { WrappedSymmetricKey(enc_string) @@ -52,7 +24,7 @@ impl From for EncString { impl WrappedSymmetricKey { /// Unwraps a the wrapped symmetric key using the provided wrapping key, returning the contained /// wrapped key. - pub fn unwrap( + pub fn unwrap_with( &self, wrapping_key: &SymmetricCryptoKey, ) -> Result { @@ -66,7 +38,7 @@ impl SymmetricCryptoKey { /// /// Use this if you have a symmetric crypto key that should protect another symmetric crypto /// key. - pub fn wrap( + pub fn wrap_with( &self, wrapping_key: &SymmetricCryptoKey, ) -> Result { @@ -86,8 +58,8 @@ mod tests { let wrapping_key = SymmetricCryptoKey::generate(&mut rng); let key = SymmetricCryptoKey::generate(&mut rng); - let wrapped_key = key.wrap(&wrapping_key).unwrap(); - let unwrapped_key = wrapped_key.unwrap(&wrapping_key).unwrap(); + let wrapped_key = key.wrap_with(&wrapping_key).unwrap(); + let unwrapped_key = wrapped_key.unwrap_with(&wrapping_key).unwrap(); assert_eq!(key, unwrapped_key); } diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index be63fd576..814feb4e1 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -144,7 +144,7 @@ impl KeyStoreContext<'_, Ids> { encrypted_key: &WrappedSymmetricKey, ) -> Result { let encryption_key = self.get_symmetric_key(encryption_key)?; - let unwrapped_key = encrypted_key.unwrap(encryption_key)?; + let unwrapped_key = encrypted_key.unwrap_with(encryption_key)?; #[allow(deprecated)] self.set_symmetric_key(new_key_id, unwrapped_key)?; @@ -168,7 +168,7 @@ impl KeyStoreContext<'_, Ids> { ) -> Result { let key_to_wrap = self.get_symmetric_key(key_to_encrypt)?; let wrapping_key = self.get_symmetric_key(encryption_key)?; - key_to_wrap.wrap(wrapping_key) + key_to_wrap.wrap_with(wrapping_key) } /// Decrypt a symmetric key into the context by using an already existing asymmetric key diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index a519ec230..f2acee566 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -38,7 +38,7 @@ impl PureCrypto { ) -> Result { let key_to_be_wrapped: SymmetricCryptoKey = key_to_be_wrapped.try_into()?; let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; - let wrapped_key: EncString = key_to_be_wrapped.wrap(&wrapping_key)?.into(); + let wrapped_key: EncString = key_to_be_wrapped.wrap_with(&wrapping_key)?.into(); Ok(wrapped_key.to_string()) } @@ -48,7 +48,7 @@ impl PureCrypto { ) -> Result, CryptoError> { let wrapped_key: WrappedSymmetricKey = EncString::from_str(&wrapped_key)?.into(); let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; - let unwrapped_key: SymmetricCryptoKey = wrapped_key.unwrap(&wrapping_key)?; + let unwrapped_key: SymmetricCryptoKey = wrapped_key.unwrap_with(&wrapping_key)?; Ok(unwrapped_key.to_vec()) } From d148b6aade5919c6ab548d667175bc90441fccc3 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 13:16:43 +0200 Subject: [PATCH 094/152] Add comment --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index b490f0600..29b3c4705 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -1,5 +1,10 @@ use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; +/// A wrapped symmetric key is an an [EncString], created where a wrapping key is used to encrypt a +/// key_to_wrap. +/// +/// Wrapped keys such as cipher keys, or attachment keys, are used to create a layer of indirection, +/// so that keys can be shared mor granularly, and so that data can be rotated more easily. #[derive(Clone, PartialEq)] pub struct WrappedSymmetricKey(EncString); From 5b4b5a3f6f49dfbef26f32313c196967297e5ba4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 13:19:48 +0200 Subject: [PATCH 095/152] Fix build --- crates/bitwarden-crypto/src/keys/master_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index dcafd14e9..4158e909f 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -4,7 +4,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use generic_array::{typenum::U32, GenericArray}; use rand::Rng; use schemars::JsonSchema; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroize; use super::{ kdf::{Kdf, KdfDerivedKeyMaterial}, From b04717e319d6db565103fdd06df2706b8c7a0b1c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 18:53:31 +0200 Subject: [PATCH 096/152] Cargo fmt --- crates/bitwarden-core/src/auth/auth_client.rs | 4 +--- crates/bitwarden-core/src/client/internal.rs | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_client.rs b/crates/bitwarden-core/src/auth/auth_client.rs index d6ae98b55..b9cd668a7 100644 --- a/crates/bitwarden-core/src/auth/auth_client.rs +++ b/crates/bitwarden-core/src/auth/auth_client.rs @@ -1,8 +1,6 @@ use bitwarden_crypto::WrappedSymmetricKey; #[cfg(feature = "internal")] -use bitwarden_crypto::{ - CryptoError, DeviceKey, Kdf, TrustDeviceResponse, UnsignedSharedKey, -}; +use bitwarden_crypto::{CryptoError, DeviceKey, Kdf, TrustDeviceResponse, UnsignedSharedKey}; #[cfg(feature = "secrets")] use crate::auth::login::{login_access_token, AccessTokenLoginRequest, AccessTokenLoginResponse}; diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs index 53091561a..7c51040c7 100644 --- a/crates/bitwarden-core/src/client/internal.rs +++ b/crates/bitwarden-core/src/client/internal.rs @@ -179,7 +179,6 @@ impl InternalClient { user_key: WrappedSymmetricKey, private_key: EncString, ) -> Result<(), EncryptionSettingsError> { - let user_key = master_key.decrypt_user_key(user_key)?; EncryptionSettings::new_decrypted_key(user_key, private_key, &self.key_store)?; From df7b62e8926a264e30e01d278ed037358e56e82c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 18:57:15 +0200 Subject: [PATCH 097/152] Fix clippy warn --- crates/bitwarden-core/src/auth/auth_request.rs | 2 +- crates/bitwarden-core/src/mobile/crypto.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 8d4c57277..330e39bfd 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -200,7 +200,7 @@ mod tests { let dec = auth_request_decrypt_master_key( private_key.to_owned(), enc_master_key, - enc_user_key.into(), + enc_user_key, ) .unwrap(); diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 159618281..f5dacb050 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -173,7 +173,7 @@ pub async fn initialize_user_crypto( } => auth_request_decrypt_master_key( request_private_key, protected_master_key, - auth_request_key.into(), + auth_request_key, )?, }; client From 2661f93ff75254bc213badd357db7f8766e694de Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 19:00:31 +0200 Subject: [PATCH 098/152] Cargo fmt --- crates/bitwarden-core/src/auth/auth_request.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 330e39bfd..18068f5f2 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -197,12 +197,9 @@ mod tests { let enc_master_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap(); let enc_user_key: EncString = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); - let dec = auth_request_decrypt_master_key( - private_key.to_owned(), - enc_master_key, - enc_user_key, - ) - .unwrap(); + let dec = + auth_request_decrypt_master_key(private_key.to_owned(), enc_master_key, enc_user_key) + .unwrap(); assert_eq!( &dec.to_vec(), From 3501b71a0420e9a3a03a97405d411e095f0d7b86 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 19:06:49 +0200 Subject: [PATCH 099/152] Cleanup --- .../bitwarden-wasm-internal/src/pure_crypto.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 82512cd62..2aa230e66 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -12,6 +12,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct PureCrypto {} +// Encryption #[wasm_bindgen] impl PureCrypto { pub fn symmetric_decrypt(enc_string: String, key: Vec) -> Result { @@ -47,6 +48,13 @@ impl PureCrypto { .to_buffer() } + + +} + +// Userkey encryption with password +#[wasm_bindgen] +impl PureCrypto { pub fn decrypt_user_key_with_master_password( encrypted_user_key: String, master_password: String, @@ -72,12 +80,16 @@ impl PureCrypto { let result = master_key.encrypt_user_key(&user_key)?; Ok(result.to_string()) } +} - pub fn generate_user_key() -> Vec { +// Generate userkey +#[wasm_bindgen] +impl PureCrypto { + pub fn generate_user_key_aes256_cbc_hmac() -> Vec { SymmetricCryptoKey::generate_aes256_cbc_hmac().to_encoded() } - pub fn generate_user_key_xchacha20() -> Vec { + pub fn generate_user_key_xchacha20_poly1305() -> Vec { SymmetricCryptoKey::generate_xchacha20().to_encoded() } } From d03ddd0a7e961e9dadd2d90cc9ddac9895b5f38e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Apr 2025 19:21:29 +0200 Subject: [PATCH 100/152] Cargo fmt --- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 2aa230e66..d85ee5306 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -47,9 +47,6 @@ impl PureCrypto { .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)? .to_buffer() } - - - } // Userkey encryption with password From 7ab5b15e7340328bb8404651eb150f97416361e1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 11:48:14 +0200 Subject: [PATCH 101/152] Tmp --- .../src/enc_string/symmetric.rs | 25 ++++++++++++- .../src/pure_crypto.rs | 37 ++++++++++++------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 804a7d920..8d2ac6966 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -182,7 +182,8 @@ impl EncString { } impl Display for EncString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_> +) -> std::fmt::Result { fn fmt_parts( f: &mut std::fmt::Formatter<'_>, enc_type: u8, @@ -212,6 +213,26 @@ impl Display for EncString { } } +impl EncString { + pub fn to_serialized_string(&self) -> String { + fn fmt_parts( + enc_type: u8, + parts: &[&[u8]], + ) -> String { + let encoded_parts: Vec = + parts.iter().map(|part| STANDARD.encode(part)).collect(); + format!("{}.{}", enc_type, encoded_parts.join("|")) + } + + let enc_type = self.enc_type(); + match &self { + EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]), + EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => fmt_parts(enc_type, &[iv, data, mac]), + EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]), + } + } +} + impl<'de> Deserialize<'de> for EncString { fn deserialize(deserializer: D) -> Result where @@ -226,7 +247,7 @@ impl serde::Serialize for EncString { where S: serde::Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_str(&self.to_serialized_string()) } } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index d85ee5306..65b05c252 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -15,7 +15,7 @@ pub struct PureCrypto {} // Encryption #[wasm_bindgen] impl PureCrypto { - pub fn symmetric_decrypt(enc_string: String, key: Vec) -> Result { + pub fn symmetric_decrypt_string(enc_string: String, key: Vec) -> Result { EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } @@ -26,20 +26,29 @@ impl PureCrypto { EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } - pub fn symmetric_decrypt_array_buffer( + pub fn symmetric_decrypt_filedata( enc_bytes: Vec, key: Vec, ) -> Result, CryptoError> { EncString::from_buffer(&enc_bytes)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } - pub fn symmetric_encrypt(plain: String, key: Vec) -> Result { + pub fn symmetric_encrypt_string(plain: String, key: Vec) -> Result { plain .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) - .map(|enc| enc.to_string()) + .map(|enc| enc.to_serialized_string()) } - pub fn symmetric_encrypt_to_array_buffer( + pub fn symmetric_encrypt_bytes( + plain: Vec, + key: Vec, + ) -> Result { + plain + .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) + .map(|enc| enc.to_serialized_string()) + } + + pub fn symmetric_encrypt_filedata( plain: Vec, key: Vec, ) -> Result, CryptoError> { @@ -75,7 +84,7 @@ impl PureCrypto { let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; let user_key = SymmetricCryptoKey::try_from(user_key)?; let result = master_key.encrypt_user_key(&user_key)?; - Ok(result.to_string()) + Ok(result.to_serialized_string()) } } @@ -120,29 +129,29 @@ mod tests { fn test_symmetric_decrypt() { let enc_string = EncString::from_str(ENCRYPTED).unwrap(); - let result = PureCrypto::symmetric_decrypt(enc_string.to_string(), KEY.to_vec()); + let result = PureCrypto::symmetric_decrypt_string(enc_string.to_string(), KEY.to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED); } #[test] fn test_symmetric_encrypt() { - let result = PureCrypto::symmetric_encrypt(DECRYPTED.to_string(), KEY.to_vec()); + let result = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption } #[test] fn test_symmetric_round_trip() { - let encrypted = PureCrypto::symmetric_encrypt(DECRYPTED.to_string(), KEY.to_vec()).unwrap(); - let decrypted = PureCrypto::symmetric_decrypt(encrypted.clone(), KEY.to_vec()).unwrap(); + let encrypted = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap(); + let decrypted = PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED); } #[test] fn test_symmetric_decrypt_array_buffer() { let result = - PureCrypto::symmetric_decrypt_array_buffer(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); + PureCrypto::symmetric_encrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED_BYTES); } @@ -150,7 +159,7 @@ mod tests { #[test] fn test_symmetric_encrypt_to_array_buffer() { let result = - PureCrypto::symmetric_encrypt_to_array_buffer(DECRYPTED_BYTES.to_vec(), KEY.to_vec()); + PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption } @@ -158,10 +167,10 @@ mod tests { #[test] fn test_symmetric_buffer_round_trip() { let encrypted = - PureCrypto::symmetric_encrypt_to_array_buffer(DECRYPTED_BYTES.to_vec(), KEY.to_vec()) + PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()) .unwrap(); let decrypted = - PureCrypto::symmetric_decrypt_array_buffer(encrypted.clone(), KEY.to_vec()).unwrap(); + PureCrypto::symmetric_encrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED_BYTES); } } From 623587fc836449ec691bcd01ba67f6e0381e2506 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 11:50:47 +0200 Subject: [PATCH 102/152] Cleanup naming --- crates/bitwarden-crypto/src/store/context.rs | 34 +++++++++---------- .../bitwarden-vault/src/cipher/attachment.rs | 4 +-- crates/bitwarden-vault/src/cipher/cipher.rs | 12 +++---- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 5d47312c3..0854a8833 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -127,23 +127,23 @@ impl KeyStoreContext<'_, Ids> { // TODO: All these encrypt x key with x key look like they need to be made generic, // but I haven't found the best way to do that yet. - /// Decrypt a symmetric key into the context by using an already existing symmetric key + /// Unwrap a symmetric key into the context by using an already existing symmetric key /// /// # Arguments /// - /// * `encryption_key` - The key id used to decrypt the `encrypted_key`. It must already exist + /// * `wrapping_key` - The key id used to unwrap the `key_to_unwrap`. It must already exist /// in the context /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten - /// * `encrypted_key` - The key to decrypt - pub fn decrypt_symmetric_key_with_symmetric_key( + /// * `key_to_unwrap` - The key to unwrap + pub fn unwrap_symmetric_key( &mut self, - encryption_key: Ids::Symmetric, + wrapping_key: Ids::Symmetric, new_key_id: Ids::Symmetric, - encrypted_key: &WrappedSymmetricKey, + key_to_unwrap: &WrappedSymmetricKey, ) -> Result { - let encryption_key = self.get_symmetric_key(encryption_key)?; - let unwrapped_key = encrypted_key.unwrap_with(encryption_key)?; + let wrapping_key = self.get_symmetric_key(wrapping_key)?; + let unwrapped_key = key_to_unwrap.unwrap_with(wrapping_key)?; #[allow(deprecated)] self.set_symmetric_key(new_key_id, unwrapped_key)?; @@ -157,16 +157,16 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `encryption_key` - The key id used to encrypt the `key_to_encrypt`. It must already exist + /// * `wrapping_key` - The key id used to encrypt the `key_to_wrap`. It must already exist /// in the context - /// * `key_to_encrypt` - The key id to encrypt. It must already exist in the context - pub fn encrypt_symmetric_key_with_symmetric_key( + /// * `key_to_wrap` - The key id to encrypt. It must already exist in the context + pub fn wrap_symmetric_key( &self, - encryption_key: Ids::Symmetric, - key_to_encrypt: Ids::Symmetric, + wrapping_key: Ids::Symmetric, + key_to_wrap: Ids::Symmetric, ) -> Result { - let key_to_wrap = self.get_symmetric_key(key_to_encrypt)?; - let wrapping_key = self.get_symmetric_key(encryption_key)?; + let key_to_wrap = self.get_symmetric_key(key_to_wrap)?; + let wrapping_key = self.get_symmetric_key(wrapping_key)?; key_to_wrap.wrap_with(wrapping_key) } @@ -409,13 +409,13 @@ mod tests { // Encrypt the new key with the old key let key_2_enc = ctx - .encrypt_symmetric_key_with_symmetric_key(key_1_id, key_2_id) + .wrap_symmetric_key(key_1_id, key_2_id) .unwrap(); // Decrypt the new key with the old key in a different identifier let new_key_id = TestSymmKey::C(3); - ctx.decrypt_symmetric_key_with_symmetric_key(key_1_id, new_key_id, &key_2_enc) + ctx.unwrap_symmetric_key(key_1_id, new_key_id, &key_2_enc) .unwrap(); // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index 40063bcf7..bd96469b4 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -90,7 +90,7 @@ impl Encryptable for Attachment let attachment_key = ctx.generate_symmetric_key(ATTACHMENT_KEY)?; let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; attachment.key = Some( - ctx.encrypt_symmetric_key_with_symmetric_key(ciphers_key, attachment_key)? + ctx.wrap_symmetric_key(ciphers_key, attachment_key)? .into(), ); @@ -128,7 +128,7 @@ impl Decryptable> for AttachmentFile { // Version 2 or 3, `AttachmentKey` or `CipherKey(AttachmentKey)` if let Some(attachment_key) = &self.attachment.key { - let content_key = ctx.decrypt_symmetric_key_with_symmetric_key( + let content_key = ctx.unwrap_symmetric_key( ciphers_key, ATTACHMENT_KEY, &attachment_key.clone().into(), diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index e6fe62634..80d1e617f 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -330,7 +330,7 @@ impl Cipher { const CIPHER_KEY: SymmetricKeyId = SymmetricKeyId::Local("cipher_key"); match ciphers_key { Some(ciphers_key) => { - ctx.decrypt_symmetric_key_with_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone()) + ctx.unwrap_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone()) } None => Ok(key), } @@ -471,7 +471,7 @@ impl CipherView { self.reencrypt_fido2_credentials(ctx, old_ciphers_key, new_key)?; self.key = Some( - ctx.encrypt_symmetric_key_with_symmetric_key(key, new_key)? + ctx.wrap_symmetric_key(key, new_key)? .into(), ); Ok(()) @@ -949,7 +949,7 @@ mod tests { let cipher_key = ctx.generate_symmetric_key(CIPHER_KEY).unwrap(); original_cipher.key = Some( - ctx.encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, cipher_key) + ctx.wrap_symmetric_key(SymmetricKeyId::User, cipher_key) .unwrap() .into(), ); @@ -1073,7 +1073,7 @@ mod tests { .generate_symmetric_key(SymmetricKeyId::Local("test_attachment_key")) .unwrap(); let attachment_key_enc = ctx - .encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, attachment_key) + .wrap_symmetric_key(SymmetricKeyId::User, attachment_key) .unwrap(); #[allow(deprecated)] let attachment_key_val = ctx @@ -1140,7 +1140,7 @@ mod tests { .generate_symmetric_key(SymmetricKeyId::Local("test_cipher_key")) .unwrap(); let cipher_key_enc = ctx - .encrypt_symmetric_key_with_symmetric_key(SymmetricKeyId::User, cipher_key) + .wrap_symmetric_key(SymmetricKeyId::User, cipher_key) .unwrap() .into(); @@ -1149,7 +1149,7 @@ mod tests { .generate_symmetric_key(SymmetricKeyId::Local("test_attachment_key")) .unwrap(); let attachment_key_enc: EncString = ctx - .encrypt_symmetric_key_with_symmetric_key(cipher_key, attachment_key) + .wrap_symmetric_key(cipher_key, attachment_key) .unwrap() .into(); From bdbd6d3861afd6f5239c8b0420225a09b123cb3b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:03:00 +0200 Subject: [PATCH 103/152] Make inner conversion explicit --- crates/bitwarden-core/src/auth/key_connector.rs | 4 ++-- crates/bitwarden-core/src/auth/register.rs | 4 ++-- crates/bitwarden-core/src/mobile/crypto.rs | 4 ++-- crates/bitwarden-crypto/src/keys/master_key.rs | 2 +- crates/bitwarden-crypto/src/safe/key_wrap.rs | 12 +++++------- crates/bitwarden-vault/src/cipher/attachment.rs | 2 +- crates/bitwarden-vault/src/cipher/cipher.rs | 10 +++++----- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 2 +- 8 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden-core/src/auth/key_connector.rs b/crates/bitwarden-core/src/auth/key_connector.rs index c680ebef0..b7eb1b8a2 100644 --- a/crates/bitwarden-core/src/auth/key_connector.rs +++ b/crates/bitwarden-core/src/auth/key_connector.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::{CryptoError, EncString, MasterKey, RsaKeyPair}; +use bitwarden_crypto::{CryptoError, MasterKey, RsaKeyPair}; #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct KeyConnectorResponse { @@ -16,7 +16,7 @@ pub(super) fn make_key_connector_keys( Ok(KeyConnectorResponse { master_key: master_key.to_base64(), - encrypted_user_key: EncString::from(encrypted_user_key).to_string(), + encrypted_user_key: encrypted_user_key.into_inner().to_string(), keys, }) } diff --git a/crates/bitwarden-core/src/auth/register.rs b/crates/bitwarden-core/src/auth/register.rs index ce04190b1..df67b9b59 100644 --- a/crates/bitwarden-core/src/auth/register.rs +++ b/crates/bitwarden-core/src/auth/register.rs @@ -3,7 +3,7 @@ use bitwarden_api_identity::{ models::{KeysRequestModel, RegisterRequestModel}, }; use bitwarden_crypto::{ - default_pbkdf2_iterations, CryptoError, EncString, HashPurpose, Kdf, MasterKey, RsaKeyPair, + default_pbkdf2_iterations, CryptoError, HashPurpose, Kdf, MasterKey, RsaKeyPair, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -77,7 +77,7 @@ pub(super) fn make_register_keys( Ok(RegisterKeyResponse { master_password_hash, - encrypted_user_key: EncString::from(encrypted_user_key).to_string(), + encrypted_user_key: encrypted_user_key.into_inner().to_string(), keys, }) } diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index f5dacb050..2fa1d7447 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -299,7 +299,7 @@ pub(super) fn update_password( Ok(UpdatePasswordResponse { password_hash, - new_key: new_key.into(), + new_key: new_key.into_inner(), }) } @@ -372,7 +372,7 @@ fn derive_pin_protected_user_key( LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?, }; - Ok(derived_key.encrypt_user_key(user_key)?.into()) + Ok(derived_key.encrypt_user_key(user_key)?.into_inner()) } #[allow(missing_docs)] diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 4402778f4..15b54f1aa 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -134,7 +134,7 @@ pub(super) fn decrypt_user_key( key: &Pin>>, user_key: WrappedSymmetricKey, ) -> Result { - match user_key.as_ref() { + match user_key.as_inner() { // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support // decrypting these old keys. diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 29b3c4705..ae418d84c 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -14,15 +14,13 @@ impl From for WrappedSymmetricKey { } } -impl AsRef for WrappedSymmetricKey { - fn as_ref(&self) -> &EncString { - &self.0 +impl WrappedSymmetricKey { + pub fn into_inner(self) -> EncString { + self.0 } -} -impl From for EncString { - fn from(wrapped: WrappedSymmetricKey) -> Self { - wrapped.0 + pub fn as_inner(&self) -> &EncString { + &self.0 } } diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index bd96469b4..f4bab1445 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -91,7 +91,7 @@ impl Encryptable for Attachment let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; attachment.key = Some( ctx.wrap_symmetric_key(ciphers_key, attachment_key)? - .into(), + .into_inner(), ); let contents = encrypted_contents.to_buffer()?; diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 80d1e617f..83b80761a 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -472,7 +472,7 @@ impl CipherView { self.key = Some( ctx.wrap_symmetric_key(key, new_key)? - .into(), + .into_inner(), ); Ok(()) } @@ -951,7 +951,7 @@ mod tests { original_cipher.key = Some( ctx.wrap_symmetric_key(SymmetricKeyId::User, cipher_key) .unwrap() - .into(), + .into_inner(), ); } @@ -1091,7 +1091,7 @@ mod tests { size: None, size_name: None, file_name: Some("Attachment test name".into()), - key: Some(attachment_key_enc.into()), + key: Some(attachment_key_enc.into_inner()), }; cipher.attachments = Some(vec![attachment]); let cred = generate_fido2(&mut key_store.context(), SymmetricKeyId::User); @@ -1142,7 +1142,7 @@ mod tests { let cipher_key_enc = ctx .wrap_symmetric_key(SymmetricKeyId::User, cipher_key) .unwrap() - .into(); + .into_inner(); // Attachment has a key that is encrypted with the cipher key let attachment_key = ctx @@ -1151,7 +1151,7 @@ mod tests { let attachment_key_enc: EncString = ctx .wrap_symmetric_key(cipher_key, attachment_key) .unwrap() - .into(); + .into_inner(); let mut cipher = generate_cipher(); cipher.key = Some(cipher_key_enc); diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index f2acee566..5b481e421 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -38,7 +38,7 @@ impl PureCrypto { ) -> Result { let key_to_be_wrapped: SymmetricCryptoKey = key_to_be_wrapped.try_into()?; let wrapping_key: SymmetricCryptoKey = wrapping_key.try_into()?; - let wrapped_key: EncString = key_to_be_wrapped.wrap_with(&wrapping_key)?.into(); + let wrapped_key: EncString = key_to_be_wrapped.wrap_with(&wrapping_key)?.into_inner(); Ok(wrapped_key.to_string()) } From f8a205360852a08cde36c492e448e71b3c641ede Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:09:50 +0200 Subject: [PATCH 104/152] Cleanup --- crates/bitwarden-vault/src/cipher/cipher.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 83b80761a..8f573b051 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -4,8 +4,7 @@ use bitwarden_core::{ require, MissingFieldError, VaultLockedError, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, - WrappedSymmetricKey, + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, WrappedSymmetricKey }; use bitwarden_error::bitwarden_error; use chrono::{DateTime, Utc}; @@ -461,7 +460,7 @@ impl CipherView { key: SymmetricKeyId, ) -> Result<(), CryptoError> { let old_ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key.take().map(Into::::into))?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); From 7c3511d65b7a0290c57070edb2dbe7d762a8ec3b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:13:44 +0200 Subject: [PATCH 105/152] Cargo fmt --- crates/bitwarden-crypto/src/store/context.rs | 12 +++++------- crates/bitwarden-vault/src/cipher/cipher.rs | 19 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 0854a8833..2504a0ea8 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -131,8 +131,8 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `wrapping_key` - The key id used to unwrap the `key_to_unwrap`. It must already exist - /// in the context + /// * `wrapping_key` - The key id used to unwrap the `key_to_unwrap`. It must already exist in + /// the context /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it /// will be overwritten /// * `key_to_unwrap` - The key to unwrap @@ -157,8 +157,8 @@ impl KeyStoreContext<'_, Ids> { /// /// # Arguments /// - /// * `wrapping_key` - The key id used to encrypt the `key_to_wrap`. It must already exist - /// in the context + /// * `wrapping_key` - The key id used to encrypt the `key_to_wrap`. It must already exist in + /// the context /// * `key_to_wrap` - The key id to encrypt. It must already exist in the context pub fn wrap_symmetric_key( &self, @@ -408,9 +408,7 @@ mod tests { assert!(ctx.has_symmetric_key(key_2_id)); // Encrypt the new key with the old key - let key_2_enc = ctx - .wrap_symmetric_key(key_1_id, key_2_id) - .unwrap(); + let key_2_enc = ctx.wrap_symmetric_key(key_1_id, key_2_id).unwrap(); // Decrypt the new key with the old key in a different identifier let new_key_id = TestSymmKey::C(3); diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 8f573b051..82df79d4e 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -4,7 +4,8 @@ use bitwarden_core::{ require, MissingFieldError, VaultLockedError, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, WrappedSymmetricKey + CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + WrappedSymmetricKey, }; use bitwarden_error::bitwarden_error; use chrono::{DateTime, Utc}; @@ -328,9 +329,7 @@ impl Cipher { ) -> Result { const CIPHER_KEY: SymmetricKeyId = SymmetricKeyId::Local("cipher_key"); match ciphers_key { - Some(ciphers_key) => { - ctx.unwrap_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone()) - } + Some(ciphers_key) => ctx.unwrap_symmetric_key(key, CIPHER_KEY, &ciphers_key.clone()), None => Ok(key), } } @@ -459,8 +458,11 @@ impl CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result<(), CryptoError> { - let old_ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.take().map(Into::::into))?; + let old_ciphers_key = Cipher::decrypt_cipher_key( + ctx, + key, + &self.key.take().map(Into::::into), + )?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -469,10 +471,7 @@ impl CipherView { self.reencrypt_attachment_keys(ctx, old_ciphers_key, new_key)?; self.reencrypt_fido2_credentials(ctx, old_ciphers_key, new_key)?; - self.key = Some( - ctx.wrap_symmetric_key(key, new_key)? - .into_inner(), - ); + self.key = Some(ctx.wrap_symmetric_key(key, new_key)?.into_inner()); Ok(()) } From e46b982254c8b721a0be68ef5d636f5313691727 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:16:58 +0200 Subject: [PATCH 106/152] Cargo fmt --- .../src/enc_string/symmetric.rs | 14 +++++------ .../src/pure_crypto.rs | 25 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 8d2ac6966..69a5bd27a 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -182,8 +182,7 @@ impl EncString { } impl Display for EncString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_> -) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_parts( f: &mut std::fmt::Formatter<'_>, enc_type: u8, @@ -215,10 +214,7 @@ impl Display for EncString { impl EncString { pub fn to_serialized_string(&self) -> String { - fn fmt_parts( - enc_type: u8, - parts: &[&[u8]], - ) -> String { + fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String { let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); format!("{}.{}", enc_type, encoded_parts.join("|")) @@ -227,8 +223,10 @@ impl EncString { let enc_type = self.enc_type(); match &self { EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]), - EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => fmt_parts(enc_type, &[iv, data, mac]), - EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]), + EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => { + fmt_parts(enc_type, &[iv, data, mac]) + } + EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]), } } } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 65b05c252..921f51b01 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -15,7 +15,10 @@ pub struct PureCrypto {} // Encryption #[wasm_bindgen] impl PureCrypto { - pub fn symmetric_decrypt_string(enc_string: String, key: Vec) -> Result { + pub fn symmetric_decrypt_string( + enc_string: String, + key: Vec, + ) -> Result { EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } @@ -39,10 +42,7 @@ impl PureCrypto { .map(|enc| enc.to_serialized_string()) } - pub fn symmetric_encrypt_bytes( - plain: Vec, - key: Vec, - ) -> Result { + pub fn symmetric_encrypt_bytes(plain: Vec, key: Vec) -> Result { plain .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) .map(|enc| enc.to_serialized_string()) @@ -143,23 +143,23 @@ mod tests { #[test] fn test_symmetric_round_trip() { - let encrypted = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap(); - let decrypted = PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap(); + let encrypted = + PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap(); + let decrypted = + PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED); } #[test] fn test_symmetric_decrypt_array_buffer() { - let result = - PureCrypto::symmetric_encrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); + let result = PureCrypto::symmetric_encrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED_BYTES); } #[test] fn test_symmetric_encrypt_to_array_buffer() { - let result = - PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()); + let result = PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()); assert!(result.is_ok()); // Cannot test encrypted string content because IV is unique per encryption } @@ -167,8 +167,7 @@ mod tests { #[test] fn test_symmetric_buffer_round_trip() { let encrypted = - PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()) - .unwrap(); + PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()).unwrap(); let decrypted = PureCrypto::symmetric_encrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED_BYTES); From d91477df788129b6002694f44fb11584091e2879 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:18:48 +0200 Subject: [PATCH 107/152] Fix docs --- crates/bitwarden-crypto/src/store/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 2504a0ea8..2cfe3bf5b 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -55,7 +55,7 @@ use crate::{ /// /// impl Encryptable for Data { /// fn encrypt(&self, ctx: &mut KeyStoreContext, key: SymmKeyId) -> Result { -/// let local_key_id = ctx.decrypt_symmetric_key_with_symmetric_key(key, LOCAL_KEY, &self.key)?; +/// let local_key_id = ctx.unwrap_symmetric_key(key, LOCAL_KEY, &self.key)?; /// self.name.encrypt(ctx, local_key_id) /// } /// } From db41105e5d541d77da19cb47f9a492899ff1f71c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:36:21 +0200 Subject: [PATCH 108/152] Fix docs --- crates/bitwarden-crypto/src/store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 62b3650df..575a6d108 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -152,7 +152,7 @@ impl KeyStore { /// lead to key material being leaked, but we need to support it for backwards compatibility. /// If you want to access the key material to encrypt it or derive a new key from it, we /// provide functions for that: - /// - [KeyStoreContext::encrypt_symmetric_key_with_symmetric_key] + /// - [KeyStoreContext::wrap_symmetric_key] /// - [KeyStoreContext::encapsulate_key_unsigned] /// - [KeyStoreContext::derive_shareable_key] pub fn context(&'_ self) -> KeyStoreContext<'_, Ids> { From 252a097b1ed6feb7c7cb50eab04cd4f455a61755 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:46:40 +0200 Subject: [PATCH 109/152] Add tests --- .../src/pure_crypto.rs | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 921f51b01..2af97d32a 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -22,7 +22,7 @@ impl PureCrypto { EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?) } - pub fn symmetric_decrypt_to_bytes( + pub fn symmetric_decrypt_bytes( enc_string: String, key: Vec, ) -> Result, CryptoError> { @@ -102,7 +102,7 @@ impl PureCrypto { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{num::NonZero, str::FromStr}; use bitwarden_crypto::EncString; @@ -142,7 +142,7 @@ mod tests { } #[test] - fn test_symmetric_round_trip() { + fn test_symmetric_string_round_trip() { let encrypted = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap(); let decrypted = @@ -150,9 +150,18 @@ mod tests { assert_eq!(decrypted, DECRYPTED); } + #[test] + fn test_symmetric_bytes_round_trip() { + let encrypted = + PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec()).unwrap(); + let decrypted = + PureCrypto::symmetric_decrypt_bytes(encrypted.clone(), KEY.to_vec()).unwrap(); + assert_eq!(decrypted, DECRYPTED.as_bytes().to_vec()); + } + #[test] fn test_symmetric_decrypt_array_buffer() { - let result = PureCrypto::symmetric_encrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); + let result = PureCrypto::symmetric_decrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec()); assert!(result.is_ok()); assert_eq!(result.unwrap(), DECRYPTED_BYTES); } @@ -165,11 +174,46 @@ mod tests { } #[test] - fn test_symmetric_buffer_round_trip() { + fn test_symmetric_filedata_round_trip() { let encrypted = PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()).unwrap(); let decrypted = - PureCrypto::symmetric_encrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap(); + PureCrypto::symmetric_decrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED_BYTES); } + + #[test] + fn test_generate_aes256_cbc_hmac() { + let key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + assert_eq!(key.len(), 64); + } + + #[test] + fn test_generate_xchacha20_poly1305() { + let key = PureCrypto::generate_user_key_xchacha20_poly1305(); + assert!(key.len() > 64); + } + + #[test] + fn roundtrip_encrypt_user_key_with_master_password() { + let master_password = "test"; + let email = "test@example.com"; + let kdf = Kdf::PBKDF2 { iterations: NonZero::try_from(600000).unwrap() }; + let user_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); + let encrypted_user_key = PureCrypto::encrypt_user_key_with_master_password( + user_key.clone(), + master_password.to_string(), + email.to_string(), + kdf.clone(), + ) + .unwrap(); + let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_password( + encrypted_user_key, + master_password.to_string(), + email.to_string(), + kdf, + ) + .unwrap(); + assert_eq!(user_key, decrypted_user_key); + } } From 3d3d00aca4bbbb60318ecfb915e738fbbf9ec46c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 25 Apr 2025 12:47:56 +0200 Subject: [PATCH 110/152] Cargo fmt --- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 2af97d32a..75e03c513 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -153,7 +153,8 @@ mod tests { #[test] fn test_symmetric_bytes_round_trip() { let encrypted = - PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec()).unwrap(); + PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec()) + .unwrap(); let decrypted = PureCrypto::symmetric_decrypt_bytes(encrypted.clone(), KEY.to_vec()).unwrap(); assert_eq!(decrypted, DECRYPTED.as_bytes().to_vec()); @@ -198,7 +199,9 @@ mod tests { fn roundtrip_encrypt_user_key_with_master_password() { let master_password = "test"; let email = "test@example.com"; - let kdf = Kdf::PBKDF2 { iterations: NonZero::try_from(600000).unwrap() }; + let kdf = Kdf::PBKDF2 { + iterations: NonZero::try_from(600000).unwrap(), + }; let user_key = PureCrypto::generate_user_key_aes256_cbc_hmac(); let encrypted_user_key = PureCrypto::encrypt_user_key_with_master_password( user_key.clone(), From 7278d09777b9e114c67e58dd36860eb822a99832 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 13:38:29 +0200 Subject: [PATCH 111/152] Use cryptorng trait in make_user_key --- crates/bitwarden-crypto/src/keys/master_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 2aaa56125..f2a76d3b0 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -164,7 +164,7 @@ pub(super) fn decrypt_user_key( /// WARNING: This function should only be used with a proper cryptographic random number generator. /// If you do not have a good reason for using this, use [MasterKey::make_user_key] instead. fn make_user_key( - mut rng: impl rand::RngCore, + mut rng: impl rand::RngCore + rand::CryptoRng, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { let user_key = SymmetricCryptoKey::generate_aes256_cbc_hmac_internal(&mut rng); From 6214a50cc66be04a8d5fd534817f1f600b6c3e79 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 13:39:45 +0200 Subject: [PATCH 112/152] Cleanup nonce in XChaChaPoly1305Ciphertext --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- crates/bitwarden-crypto/src/xchacha20.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 69a5bd27a..0e6254d89 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -272,7 +272,7 @@ impl EncString { .create_ciphertext(data_dec, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce = *ciphertext.nonce(); + nonce = ciphertext.nonce(); ciphertext.encrypted_bytes() }) .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 59a800658..277d96852 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -26,11 +26,9 @@ pub(crate) struct XChaCha20Poly1305Ciphertext { } impl XChaCha20Poly1305Ciphertext { - pub(crate) fn nonce(&self) -> &[u8; 24] { + pub(crate) fn nonce(&self) -> [u8; 24] { self.nonce - .as_slice() - .try_into() - .expect("Nonce size is 24 bytes") + .into() } pub(crate) fn encrypted_bytes(&self) -> Vec { From 9da00d97911e9b5028b9a3279085db6aa601e667 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 13:45:11 +0200 Subject: [PATCH 113/152] Cleanup SymmetricCryptoKey matching --- crates/bitwarden-crypto/src/store/context.rs | 24 ++++++++------------ crates/bitwarden-crypto/src/xchacha20.rs | 3 +-- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index e609e50f3..77f2e97a5 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -168,29 +168,23 @@ impl KeyStoreContext<'_, Ids> { wrapping_key: Ids::Symmetric, key_to_wrap: Ids::Symmetric, ) -> Result { + use SymmetricCryptoKey::*; + let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; match (wrapping_key_instance, key_to_wrap_instance) { // These keys wrap directly by encrypting the key bytes of the inner key, with padding // applied in case it is needed - ( - SymmetricCryptoKey::Aes256CbcHmacKey(_), - SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_), - ) => self.encrypt_data_with_symmetric_key( - wrapping_key, - key_to_wrap_instance.to_encoded().as_slice(), - ), - ( - SymmetricCryptoKey::XChaCha20Poly1305Key(_), - SymmetricCryptoKey::Aes256CbcHmacKey(_) | SymmetricCryptoKey::Aes256CbcKey(_), - ) => { + (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => self + .encrypt_data_with_symmetric_key( + wrapping_key, + key_to_wrap_instance.to_encoded().as_slice(), + ), + (XChaCha20Poly1305Key(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => { // These keys should be represented as octet stream payloads in cose todo!() } - ( - SymmetricCryptoKey::XChaCha20Poly1305Key(_), - SymmetricCryptoKey::XChaCha20Poly1305Key(_), - ) => { + (XChaCha20Poly1305Key(_), XChaCha20Poly1305Key(_)) => { // These keys should be represented as CoseKey payloads in cose todo!() } diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/xchacha20.rs index 277d96852..6552a4969 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/xchacha20.rs @@ -27,8 +27,7 @@ pub(crate) struct XChaCha20Poly1305Ciphertext { impl XChaCha20Poly1305Ciphertext { pub(crate) fn nonce(&self) -> [u8; 24] { - self.nonce - .into() + self.nonce.into() } pub(crate) fn encrypted_bytes(&self) -> Vec { From ef799b18b3c918c500f79d9033943ed2010202db Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 13:50:26 +0200 Subject: [PATCH 114/152] Cleanup SymmetricCryptoKey --- .../src/keys/symmetric_crypto_key.rs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 52f1e82bd..f8c68c164 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -2,7 +2,10 @@ use std::{cmp::max, pin::Pin}; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; -use coset::{iana, CborSerializable, Label, RegisteredLabelWithPrivate}; +use coset::{ + iana::{self, KeyOperation}, + CborSerializable, Label, RegisteredLabelWithPrivate, +}; use generic_array::GenericArray; use rand::Rng; #[cfg(test)] @@ -106,7 +109,7 @@ impl SymmetricCryptoKey { rng.fill(enc_key.as_mut_slice()); rng.fill(mac_key.as_mut_slice()); - SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) + Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) } /** @@ -124,7 +127,7 @@ impl SymmetricCryptoKey { let mut rng = rand::thread_rng(); let mut enc_key = Box::pin(GenericArray::::default()); rng.fill(enc_key.as_mut_slice()); - SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key { + Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key { enc_key, key_id: *KeyId::generate().as_bytes(), }) @@ -141,10 +144,8 @@ impl SymmetricCryptoKey { pub fn to_encoded(&self) -> Vec { let mut encoded_key = self.to_encoded_raw(); match self { - SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => { - encoded_key - } - SymmetricCryptoKey::XChaCha20Poly1305Key(_) => { + Self::Aes256CbcKey(_) | Self::Aes256CbcHmacKey(_) => encoded_key, + Self::XChaCha20Poly1305Key(_) => { pad_key(&mut encoded_key, Self::AES256_CBC_HMAC_KEY_LEN + 1); encoded_key } @@ -168,21 +169,21 @@ impl SymmetricCryptoKey { pub(crate) fn to_encoded_raw(&self) -> Vec { match self { - SymmetricCryptoKey::Aes256CbcKey(key) => key.enc_key.to_vec(), - SymmetricCryptoKey::Aes256CbcHmacKey(key) => { + Self::Aes256CbcKey(key) => key.enc_key.to_vec(), + Self::Aes256CbcHmacKey(key) => { let mut buf = Vec::with_capacity(64); buf.extend_from_slice(&key.enc_key); buf.extend_from_slice(&key.mac_key); buf } - SymmetricCryptoKey::XChaCha20Poly1305Key(key) => { + Self::XChaCha20Poly1305Key(key) => { let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec()); let mut cose_key = builder .key_id(key.key_id.to_vec()) - .add_key_op(iana::KeyOperation::Decrypt) - .add_key_op(iana::KeyOperation::Encrypt) - .add_key_op(iana::KeyOperation::WrapKey) - .add_key_op(iana::KeyOperation::UnwrapKey) + .add_key_op(KeyOperation::Decrypt) + .add_key_op(KeyOperation::Encrypt) + .add_key_op(KeyOperation::WrapKey) + .add_key_op(KeyOperation::UnwrapKey) .build(); cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse( cose::XCHACHA20_POLY1305, @@ -229,7 +230,7 @@ impl TryFrom for SymmetricCryptoKey { let b = STANDARD .decode(value) .map_err(|_| CryptoError::InvalidKey)?; - SymmetricCryptoKey::try_from(b) + Self::try_from(b) } } @@ -237,7 +238,7 @@ impl TryFrom> for SymmetricCryptoKey { type Error = CryptoError; fn try_from(mut value: Vec) -> Result { - SymmetricCryptoKey::try_from(value.as_mut_slice()) + Self::try_from(value.as_mut_slice()) } } @@ -258,7 +259,7 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { enc_key.copy_from_slice(&value[..32]); mac_key.copy_from_slice(&value[32..]); - Ok(SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { + Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key, })) @@ -267,7 +268,7 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { enc_key.copy_from_slice(&value[..Self::AES256_CBC_KEY_LEN]); - Ok(SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey { enc_key })) + Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key })) } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN { let unpadded_value = unpad_key(value); let cose_key = @@ -319,7 +320,7 @@ fn parse_cose_key(cose_key: &coset::CoseKey) -> Result for SymmetricCryptoKey { fn from(key: Aes256CbcHmacKey) -> Self { - SymmetricCryptoKey::Aes256CbcHmacKey(key) + Self::Aes256CbcHmacKey(key) } } From 3e1b7351835d91e7f9b50f63642fe48810e30422 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 13:51:39 +0200 Subject: [PATCH 115/152] Remove unused From impl --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index f8c68c164..335e10c52 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -318,12 +318,6 @@ fn parse_cose_key(cose_key: &coset::CoseKey) -> Result for SymmetricCryptoKey { - fn from(key: Aes256CbcHmacKey) -> Self { - Self::Aes256CbcHmacKey(key) - } -} - impl CryptoKey for SymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data From 97401c55d035c27229ee19bdba2dfabfb24f5641 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:13:15 +0200 Subject: [PATCH 116/152] Add cose to dictionary --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index f75a2e3f5..b93a7dbd3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "Cdecl", "chrono", "cloc", + "COSE", "dealloc", "decryptable", "dylib", From 65a90a6430991f9224b75334f79d84f0aafe04f3 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:13:35 +0200 Subject: [PATCH 117/152] Add comment to XChaCha20Poly1305Key --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 335e10c52..ccd35775d 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -87,7 +87,7 @@ impl PartialEq for XChaCha20Poly1305Key { pub enum SymmetricCryptoKey { Aes256CbcKey(Aes256CbcKey), Aes256CbcHmacKey(Aes256CbcHmacKey), - // always encode with cose + /// Data encrypted by XChaCha20Poly1305Key keys has type [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64) XChaCha20Poly1305Key(XChaCha20Poly1305Key), } From 556ef552d587b2de1c7133161546f47b954b3032 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:13:44 +0200 Subject: [PATCH 118/152] Add comment explaining key wrap --- crates/bitwarden-crypto/src/store/context.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 77f2e97a5..d19ba21dc 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -172,20 +172,21 @@ impl KeyStoreContext<'_, Ids> { let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; + // Aes256CbcHmacKey can wrap keys by encrypting their byte serialization obtained using SymmetricCryptoKey::to_encoded(). + // XChaCha20Poly1305Key need to specify the content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey or Aes256CbcKey, or by specifying + // the content format to be CoseKey, in case the wrapped key is a XChaCha20Poly1305Key. match (wrapping_key_instance, key_to_wrap_instance) { - // These keys wrap directly by encrypting the key bytes of the inner key, with padding - // applied in case it is needed (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => self .encrypt_data_with_symmetric_key( wrapping_key, key_to_wrap_instance.to_encoded().as_slice(), ), (XChaCha20Poly1305Key(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => { - // These keys should be represented as octet stream payloads in cose + // These keys should be represented as octet stream payloads in COSE todo!() } (XChaCha20Poly1305Key(_), XChaCha20Poly1305Key(_)) => { - // These keys should be represented as CoseKey payloads in cose + // These keys should be represented as CoseKey payloads in COSE todo!() } _ => Err(CryptoError::OperationNotSupported( From 36a1b63a3b5dc801e4098d539b2c27f799b73f6c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:13:56 +0200 Subject: [PATCH 119/152] Cargo fmt --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 3 ++- crates/bitwarden-crypto/src/store/context.rs | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index ccd35775d..341d71ea6 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -87,7 +87,8 @@ impl PartialEq for XChaCha20Poly1305Key { pub enum SymmetricCryptoKey { Aes256CbcKey(Aes256CbcKey), Aes256CbcHmacKey(Aes256CbcHmacKey), - /// Data encrypted by XChaCha20Poly1305Key keys has type [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64) + /// Data encrypted by XChaCha20Poly1305Key keys has type + /// [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64) XChaCha20Poly1305Key(XChaCha20Poly1305Key), } diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index d19ba21dc..94cc0fc8c 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -172,9 +172,11 @@ impl KeyStoreContext<'_, Ids> { let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; - // Aes256CbcHmacKey can wrap keys by encrypting their byte serialization obtained using SymmetricCryptoKey::to_encoded(). - // XChaCha20Poly1305Key need to specify the content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey or Aes256CbcKey, or by specifying - // the content format to be CoseKey, in case the wrapped key is a XChaCha20Poly1305Key. + // Aes256CbcHmacKey can wrap keys by encrypting their byte serialization obtained using + // SymmetricCryptoKey::to_encoded(). XChaCha20Poly1305Key need to specify the + // content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey + // or Aes256CbcKey, or by specifying the content format to be CoseKey, in case the + // wrapped key is a XChaCha20Poly1305Key. match (wrapping_key_instance, key_to_wrap_instance) { (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => self .encrypt_data_with_symmetric_key( From bfa90d09e326b32b8f93a909104fba29e9df7638 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:16:50 +0200 Subject: [PATCH 120/152] Fix clippy error --- crates/bitwarden-crypto/src/keys/master_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index f2a76d3b0..927d37ec1 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -67,7 +67,7 @@ impl MasterKey { /// Generate a new random user key and encrypt it with the master key. pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { - make_user_key(&mut rand::thread_rng(), self) + make_user_key(rand::thread_rng(), self) } /// Encrypt the users user key From c9a5c4a27470a3ca465d2b47808537eb1cb78da4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:24:13 +0200 Subject: [PATCH 121/152] Undo changes to rng --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 341d71ea6..67139a14d 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -103,7 +103,7 @@ impl SymmetricCryptoKey { /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not /// have a good reason for using this function, use /// [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. - pub(crate) fn generate_aes256_cbc_hmac_internal(rng: &mut impl Rng) -> Self { + pub(crate) fn generate_aes256_cbc_hmac_internal(mut rng: impl rand::RngCore + rand::CryptoRng) -> Self { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); From 1ed2549edadfe9dbe986d36b94a18aa8c41e8100 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:25:49 +0200 Subject: [PATCH 122/152] Fix formatting --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 67139a14d..992fd55ab 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -103,7 +103,9 @@ impl SymmetricCryptoKey { /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not /// have a good reason for using this function, use /// [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. - pub(crate) fn generate_aes256_cbc_hmac_internal(mut rng: impl rand::RngCore + rand::CryptoRng) -> Self { + pub(crate) fn generate_aes256_cbc_hmac_internal( + mut rng: impl rand::RngCore + rand::CryptoRng, + ) -> Self { let mut enc_key = Box::pin(GenericArray::::default()); let mut mac_key = Box::pin(GenericArray::::default()); From 19fef2f088b82e1209fad20c4883335c96bfe79f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:33:33 +0200 Subject: [PATCH 123/152] Fix comment style --- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 992fd55ab..5a5a7cf05 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -115,17 +115,13 @@ impl SymmetricCryptoKey { Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key }) } - /** - * Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey] - */ + /// Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey] pub fn generate_aes256_cbc_hmac() -> Self { let mut rng = rand::thread_rng(); Self::generate_aes256_cbc_hmac_internal(&mut rng) } - /** - * Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] - */ + /// Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] pub fn generate_xchacha20() -> Self { let mut rng = rand::thread_rng(); let mut enc_key = Box::pin(GenericArray::::default()); From 00975bf3c019be83cb039fac151d1db9474ddcce Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:51:45 +0200 Subject: [PATCH 124/152] Add XChaCha to workspace spelling --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index b93a7dbd3..90ee9dc41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "totp", "uniffi", "wordlist", + "XCHACHA", "Zeroize", "Zeroizing", "zxcvbn" From 5175757f866513afdece3c2a256e352dff5a1214 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 14:53:49 +0200 Subject: [PATCH 125/152] Clean up parse_cose_key match --- .../src/keys/symmetric_crypto_key.rs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 5a5a7cf05..7f1dfde58 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -297,21 +297,19 @@ fn parse_cose_key(cose_key: &coset::CoseKey) -> Result { - if key_bytes.len() == 32 { - let mut enc_key = Box::pin(GenericArray::::default()); - enc_key.copy_from_slice(key_bytes); - let key_id = cose_key - .key_id - .clone() - .try_into() - .map_err(|_| CryptoError::InvalidKey)?; - Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( - XChaCha20Poly1305Key { enc_key, key_id }, - )) - } else { - Err(CryptoError::InvalidKey) - } + coset::RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305) + if key_bytes.len() == 32 => + { + let mut enc_key = Box::pin(GenericArray::::default()); + enc_key.copy_from_slice(key_bytes); + let key_id = cose_key + .key_id + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( + XChaCha20Poly1305Key { enc_key, key_id }, + )) } _ => Err(CryptoError::InvalidKey), } From 8477bb2db8cc39b0a49513ef048f01b08914b465 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:09:16 +0200 Subject: [PATCH 126/152] Remove EncString display impl and move to ToString / Debug --- .../src/enc_string/symmetric.rs | 52 ++++++++----------- .../src/pure_crypto.rs | 6 +-- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 0e6254d89..df7bab2b2 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, str::FromStr}; +use std::str::FromStr; use base64::{engine::general_purpose::STANDARD, Engine}; use coset::CborSerializable; @@ -73,13 +73,6 @@ pub enum EncString { }, } -/// To avoid printing sensitive information, [EncString] debug prints to `EncString`. -impl std::fmt::Debug for EncString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EncString").finish() - } -} - /// Deserializes an [EncString] from a string. impl FromStr for EncString { type Err = CryptoError; @@ -181,7 +174,27 @@ impl EncString { } } -impl Display for EncString { +impl ToString for EncString { + fn to_string(&self) -> String { + fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String { + let encoded_parts: Vec = + parts.iter().map(|part| STANDARD.encode(part)).collect(); + format!("{}.{}", enc_type, encoded_parts.join("|")) + } + + let enc_type = self.enc_type(); + match &self { + EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]), + EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => { + fmt_parts(enc_type, &[iv, data, mac]) + } + EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]), + } + } +} + +/// To avoid printing sensitive information, [EncString] debug prints to `EncString`. +impl std::fmt::Debug for EncString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_parts( f: &mut std::fmt::Formatter<'_>, @@ -212,25 +225,6 @@ impl Display for EncString { } } -impl EncString { - pub fn to_serialized_string(&self) -> String { - fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String { - let encoded_parts: Vec = - parts.iter().map(|part| STANDARD.encode(part)).collect(); - format!("{}.{}", enc_type, encoded_parts.join("|")) - } - - let enc_type = self.enc_type(); - match &self { - EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]), - EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => { - fmt_parts(enc_type, &[iv, data, mac]) - } - EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]), - } - } -} - impl<'de> Deserialize<'de> for EncString { fn deserialize(deserializer: D) -> Result where @@ -245,7 +239,7 @@ impl serde::Serialize for EncString { where S: serde::Serializer, { - serializer.serialize_str(&self.to_serialized_string()) + serializer.serialize_str(&self.to_string()) } } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 75e03c513..5452b93d2 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -39,13 +39,13 @@ impl PureCrypto { pub fn symmetric_encrypt_string(plain: String, key: Vec) -> Result { plain .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) - .map(|enc| enc.to_serialized_string()) + .map(|enc| enc.to_string()) } pub fn symmetric_encrypt_bytes(plain: Vec, key: Vec) -> Result { plain .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) - .map(|enc| enc.to_serialized_string()) + .map(|enc| enc.to_string()) } pub fn symmetric_encrypt_filedata( @@ -84,7 +84,7 @@ impl PureCrypto { let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?; let user_key = SymmetricCryptoKey::try_from(user_key)?; let result = master_key.encrypt_user_key(&user_key)?; - Ok(result.to_serialized_string()) + Ok(result.to_string()) } } From 515aa31b254b1169f4003c392df6f7decd38332c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:15:24 +0200 Subject: [PATCH 127/152] Fix clippy warning --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index df7bab2b2..c463bf3f7 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -174,6 +174,7 @@ impl EncString { } } +#[allow(clippy::to_string_trait_impl)] impl ToString for EncString { fn to_string(&self) -> String { fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String { From 0f8298ef5ee48499ec9aa04fc50afff133e0caaf Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:20:09 +0200 Subject: [PATCH 128/152] Fix debug format test --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index c463bf3f7..76b545912 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -523,7 +523,7 @@ mod tests { let enc_string: EncString = enc_str.parse().unwrap(); let debug_string = format!("{:?}", enc_string); - assert_eq!(debug_string, "EncString"); + assert_eq!(debug_string, enc_str); } #[test] From 17eceb11f0d55e5396ca9bfc7d0852cea05d604f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:30:08 +0200 Subject: [PATCH 129/152] Return result from unpad_key --- .../src/keys/symmetric_crypto_key.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 7f1dfde58..815fdd30d 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -269,7 +269,7 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key })) } else if value.len() > Self::AES256_CBC_HMAC_KEY_LEN { - let unpadded_value = unpad_key(value); + let unpadded_value = unpad_key(value)?; let cose_key = coset::CoseKey::from_slice(unpadded_value).map_err(|_| CryptoError::InvalidKey)?; parse_cose_key(&cose_key) @@ -351,11 +351,12 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { /// padding is used to make sure that the byte representation uniquely separates the keys by /// size of the byte array the previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and /// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively. -fn unpad_key(key_bytes: &[u8]) -> &[u8] { - // this unwrap is safe, the input is always at least 1 byte long - #[allow(clippy::unwrap_used)] - let pad_len = *key_bytes.last().unwrap() as usize; - key_bytes[..key_bytes.len() - pad_len].as_ref() +fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> { + let pad_len = *key_bytes.last().ok_or(CryptoError::InvalidKey)? as usize; + if pad_len >= key_bytes.len() { + return Err(CryptoError::InvalidKey); + } + Ok(key_bytes[..key_bytes.len() - pad_len].as_ref()) } #[cfg(test)] @@ -422,7 +423,7 @@ mod tests { encoded_bytes[64] = 2; pad_key(&mut key_bytes, 65); assert_eq!(encoded_bytes, key_bytes); - let unpadded_key = unpad_key(&key_bytes); + let unpadded_key = unpad_key(&key_bytes).unwrap(); assert_eq!(original_key, unpadded_key); } @@ -434,7 +435,7 @@ mod tests { encoded_bytes[64] = 1; pad_key(&mut key_bytes, 65); assert_eq!(encoded_bytes, key_bytes); - let unpadded_key = unpad_key(&key_bytes); + let unpadded_key = unpad_key(&key_bytes).unwrap(); assert_eq!(original_key, unpadded_key); } @@ -446,7 +447,7 @@ mod tests { encoded_bytes[65] = 1; pad_key(&mut key_bytes, 65); assert_eq!(encoded_bytes, key_bytes); - let unpadded_key = unpad_key(&key_bytes); + let unpadded_key = unpad_key(&key_bytes).unwrap(); assert_eq!(original_key, unpadded_key); } } From 1c7cdcf09e2267bc2644762d77b6d34881202539 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:48:33 +0200 Subject: [PATCH 130/152] Move encrypt xchacha20 to cose module --- crates/bitwarden-crypto/src/cose.rs | 28 +++++++++++++++++++ .../src/enc_string/symmetric.rs | 23 ++------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 5e05f0a29..f41613bec 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -2,7 +2,35 @@ //! Standardized values from should always be preferred //! unless there is a specific reason to use a private-use value. +use coset::CborSerializable; + +use crate::{error::EncStringParseError, CryptoError}; + // XChaCha20 is used over ChaCha20 // to be able to randomly generate nonces, and to not have to worry about key wearout. Since // the draft was never published as an RFC, we use a private-use value for the algorithm. pub(crate) const XCHACHA20_POLY1305: i64 = -70000; + +pub(crate) fn encrypt_xchacha20_poly1305( + plaintext: &[u8], + key: &crate::XChaCha20Poly1305Key, +) -> Result, CryptoError> { + let mut protected_header = coset::HeaderBuilder::new().build(); + protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305)); + + let mut nonce = [0u8; 24]; + let cose_encrypt0 = coset::CoseEncrypt0Builder::new() + .protected(protected_header) + .create_ciphertext(plaintext, &[], |data, aad| { + let ciphertext = + crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); + nonce = ciphertext.nonce(); + ciphertext.encrypted_bytes() + }) + .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) + .build(); + + cose_encrypt0 + .to_vec() + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) +} diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 76b545912..ee85bb5e1 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -6,7 +6,6 @@ use serde::Deserialize; use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ - cose, error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key, }; @@ -258,26 +257,8 @@ impl EncString { data_dec: &[u8], key: &XChaCha20Poly1305Key, ) -> Result { - let mut protected_header = coset::HeaderBuilder::new().build(); - protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305)); - - let mut nonce = [0u8; 24]; - let cose_encrypt0 = coset::CoseEncrypt0Builder::new() - .protected(protected_header) - .create_ciphertext(data_dec, &[], |data, aad| { - let ciphertext = - crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce = ciphertext.nonce(); - ciphertext.encrypted_bytes() - }) - .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) - .build(); - - Ok(EncString::Cose_Encrypt0_B64 { - data: cose_encrypt0.to_vec().map_err(|err| { - CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) - })?, - }) + let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, &key)?; + Ok(EncString::Cose_Encrypt0_B64 { data }) } /// The numerical representation of the encryption type of the [EncString]. From 809a69770cb4c16efc0bf9124c26efea1009b873 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:54:04 +0200 Subject: [PATCH 131/152] Move parse_cose_key to cose mod and make it a TryFrom impl --- crates/bitwarden-crypto/src/cose.rs | 42 ++++++++++++++++++- .../src/keys/symmetric_crypto_key.rs | 39 ++--------------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index f41613bec..1cbb2fc37 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -2,9 +2,10 @@ //! Standardized values from should always be preferred //! unless there is a specific reason to use a private-use value. -use coset::CborSerializable; +use coset::{iana, CborSerializable, Label}; +use generic_array::{typenum::U32, GenericArray}; -use crate::{error::EncStringParseError, CryptoError}; +use crate::{error::EncStringParseError, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key}; // XChaCha20 is used over ChaCha20 // to be able to randomly generate nonces, and to not have to worry about key wearout. Since @@ -34,3 +35,40 @@ pub(crate) fn encrypt_xchacha20_poly1305( .to_vec() .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) } + +impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { + type Error = CryptoError; + + fn try_from(cose_key: &coset::CoseKey) -> Result { + let key_bytes = cose_key + .params + .iter() + .find_map(|(label, value)| { + const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; + if let (Label::Int(SYMMETRIC_KEY), ciborium::Value::Bytes(bytes)) = (label, value) { + Some(bytes) + } else { + None + } + }) + .ok_or(CryptoError::InvalidKey)?; + + match cose_key.alg.clone().ok_or(CryptoError::InvalidKey)? { + coset::RegisteredLabelWithPrivate::PrivateUse(XCHACHA20_POLY1305) + if key_bytes.len() == 32 => + { + let mut enc_key = Box::pin(GenericArray::::default()); + enc_key.copy_from_slice(key_bytes); + let key_id = cose_key + .key_id + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( + XChaCha20Poly1305Key { enc_key, key_id }, + )) + } + _ => Err(CryptoError::InvalidKey), + } + } +} \ No newline at end of file diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 815fdd30d..7c07610d2 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -3,8 +3,8 @@ use std::{cmp::max, pin::Pin}; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; use coset::{ - iana::{self, KeyOperation}, - CborSerializable, Label, RegisteredLabelWithPrivate, + iana::KeyOperation, + CborSerializable, RegisteredLabelWithPrivate, }; use generic_array::GenericArray; use rand::Rng; @@ -272,7 +272,7 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { let unpadded_value = unpad_key(value)?; let cose_key = coset::CoseKey::from_slice(unpadded_value).map_err(|_| CryptoError::InvalidKey)?; - parse_cose_key(&cose_key) + SymmetricCryptoKey::try_from(&cose_key) } else { Err(CryptoError::InvalidKeyLen) }; @@ -282,39 +282,6 @@ impl TryFrom<&mut [u8]> for SymmetricCryptoKey { } } -fn parse_cose_key(cose_key: &coset::CoseKey) -> Result { - let key_bytes = cose_key - .params - .iter() - .find_map(|(label, value)| { - const SYMMETRIC_KEY: i64 = iana::SymmetricKeyParameter::K as i64; - if let (Label::Int(SYMMETRIC_KEY), ciborium::Value::Bytes(bytes)) = (label, value) { - Some(bytes) - } else { - None - } - }) - .ok_or(CryptoError::InvalidKey)?; - - match cose_key.alg.clone().ok_or(CryptoError::InvalidKey)? { - coset::RegisteredLabelWithPrivate::PrivateUse(cose::XCHACHA20_POLY1305) - if key_bytes.len() == 32 => - { - let mut enc_key = Box::pin(GenericArray::::default()); - enc_key.copy_from_slice(key_bytes); - let key_id = cose_key - .key_id - .as_slice() - .try_into() - .map_err(|_| CryptoError::InvalidKey)?; - Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( - XChaCha20Poly1305Key { enc_key, key_id }, - )) - } - _ => Err(CryptoError::InvalidKey), - } -} - impl CryptoKey for SymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data From 6119a48bd94b4fa0d70b0db8918c8812249b3d77 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:57:42 +0200 Subject: [PATCH 132/152] Move decrypt_xchacha20_poly1305 to cose module --- crates/bitwarden-crypto/src/cose.rs | 21 +++++++++++++++++++ .../src/enc_string/symmetric.rs | 15 +------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 1cbb2fc37..748b93b66 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -36,6 +36,27 @@ pub(crate) fn encrypt_xchacha20_poly1305( .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) } +pub(crate) fn decrypt_xchacha20_poly1305( + cose_encrypt0_message: &[u8], + key: &crate::XChaCha20Poly1305Key, +) -> Result, CryptoError> { + let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message).map_err(|err| { + CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) + })?; + let decrypted_message = msg.decrypt(&[], |data, aad| { + let nonce = msg.unprotected.iv.as_slice(); + crate::xchacha20::decrypt_xchacha20_poly1305( + nonce + .try_into() + .map_err(|_| CryptoError::InvalidNonceLength)?, + &(*key.enc_key).into(), + data, + aad, + ) + })?; + Ok(decrypted_message) +} + impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { type Error = CryptoError; diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index ee85bb5e1..f8f8db866 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -299,20 +299,7 @@ impl KeyDecryptable> for EncString { EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { - let msg = coset::CoseEncrypt0::from_slice(data.as_slice()).map_err(|err| { - CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) - })?; - let decrypted_message = msg.decrypt(&[], |data, aad| { - let nonce = msg.unprotected.iv.as_slice(); - crate::xchacha20::decrypt_xchacha20_poly1305( - nonce - .try_into() - .map_err(|_| CryptoError::InvalidNonceLength)?, - &(*key.enc_key).into(), - data, - aad, - ) - })?; + let decrypted_message = crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?; Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), From 89a6a6252eefbe8584e011d1c6a121903172046b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:59:04 +0200 Subject: [PATCH 133/152] Add comments --- crates/bitwarden-crypto/src/cose.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 748b93b66..0c56ec2d6 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -12,6 +12,7 @@ use crate::{error::EncStringParseError, CryptoError, SymmetricCryptoKey, XChaCha // the draft was never published as an RFC, we use a private-use value for the algorithm. pub(crate) const XCHACHA20_POLY1305: i64 = -70000; +/// Encrypts a plaintext message using XChaCha20Poly1305 and returns a COSE Encrypt0 message pub(crate) fn encrypt_xchacha20_poly1305( plaintext: &[u8], key: &crate::XChaCha20Poly1305Key, @@ -36,6 +37,7 @@ pub(crate) fn encrypt_xchacha20_poly1305( .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) } +/// Decrypts a COSE Encrypt0 message, using a XChaCha20Poly1305 key pub(crate) fn decrypt_xchacha20_poly1305( cose_encrypt0_message: &[u8], key: &crate::XChaCha20Poly1305Key, From ffe306e5af9b979f6968fe6e62f0448e9e27bebc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 15:59:49 +0200 Subject: [PATCH 134/152] Add ciphertext to workspace settings --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 90ee9dc41..697a9c0a5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "Bitwarden", "Cdecl", "chrono", + "ciphertext", "cloc", "COSE", "dealloc", From 6b5c28839b5ef6c2a2b1762e47e8c871078de189 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 16:00:10 +0200 Subject: [PATCH 135/152] Run cargo fmt --- crates/bitwarden-crypto/src/cose.rs | 9 ++++----- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 3 ++- crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs | 5 +---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 0c56ec2d6..42aa7a04f 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -42,9 +42,8 @@ pub(crate) fn decrypt_xchacha20_poly1305( cose_encrypt0_message: &[u8], key: &crate::XChaCha20Poly1305Key, ) -> Result, CryptoError> { - let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message).map_err(|err| { - CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)) - })?; + let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message) + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?; let decrypted_message = msg.decrypt(&[], |data, aad| { let nonce = msg.unprotected.iv.as_slice(); crate::xchacha20::decrypt_xchacha20_poly1305( @@ -62,7 +61,7 @@ pub(crate) fn decrypt_xchacha20_poly1305( impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { type Error = CryptoError; - fn try_from(cose_key: &coset::CoseKey) -> Result { + fn try_from(cose_key: &coset::CoseKey) -> Result { let key_bytes = cose_key .params .iter() @@ -94,4 +93,4 @@ impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { _ => Err(CryptoError::InvalidKey), } } -} \ No newline at end of file +} diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index f8f8db866..ffaf904a3 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -299,7 +299,8 @@ impl KeyDecryptable> for EncString { EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { - let decrypted_message = crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?; + let decrypted_message = + crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?; Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 7c07610d2..013192566 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -2,10 +2,7 @@ use std::{cmp::max, pin::Pin}; use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; -use coset::{ - iana::KeyOperation, - CborSerializable, RegisteredLabelWithPrivate, -}; +use coset::{iana::KeyOperation, CborSerializable, RegisteredLabelWithPrivate}; use generic_array::GenericArray; use rand::Rng; #[cfg(test)] From a1a5a02c165ecdfd4622b3cbe35feb6549a43424 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 16:04:25 +0200 Subject: [PATCH 136/152] Remove unneeded ref --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index ffaf904a3..7866eb278 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -257,7 +257,7 @@ impl EncString { data_dec: &[u8], key: &XChaCha20Poly1305Key, ) -> Result { - let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, &key)?; + let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key)?; Ok(EncString::Cose_Encrypt0_B64 { data }) } From b21df8bdb35ec5b4124eed7b76e8b9baebe04e6d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 16:17:41 +0200 Subject: [PATCH 137/152] Fix comment --- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 7866eb278..6a617e7cb 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -38,7 +38,7 @@ export type EncString = string; /// /// ## Serialization /// -/// [EncString] implements [Display] and [FromStr] to allow for easy serialization and uses a +/// [EncString] implements [ToString] and [FromStr] to allow for easy serialization and uses a /// custom scheme to represent the different variants. /// /// The scheme is one of the following schemes: From 91b2465b644365c5ed0a139e5bff93a402b08935 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 28 Apr 2025 16:21:41 +0200 Subject: [PATCH 138/152] Rename SymmetricCryptoKey::generate_* to ::make_* --- crates/bitwarden-core/src/auth/tde.rs | 2 +- crates/bitwarden-crypto/README.md | 2 +- .../src/keys/asymmetric_crypto_key.rs | 2 +- .../bitwarden-crypto/src/keys/device_key.rs | 2 +- .../bitwarden-crypto/src/keys/master_key.rs | 2 +- .../src/keys/symmetric_crypto_key.rs | 14 +++++----- .../src/store/backend/implementation/mod.rs | 2 +- crates/bitwarden-crypto/src/store/context.rs | 8 +++--- crates/bitwarden-crypto/src/store/mod.rs | 4 +-- .../src/traits/encryptable.rs | 2 +- crates/bitwarden-exporters/src/models.rs | 4 +-- crates/bitwarden-vault/src/cipher/cipher.rs | 28 +++++++++---------- .../src/pure_crypto.rs | 8 +++--- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index b8f52b530..e8bc8470e 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -17,7 +17,7 @@ pub(super) fn make_register_tde_keys( ) -> Result { let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(org_public_key)?)?; - let user_key = UserKey::new(SymmetricCryptoKey::generate_aes256_cbc_hmac()); + let user_key = UserKey::new(SymmetricCryptoKey::make_aes256_cbc_hmac_key()); let key_pair = user_key.make_key_pair()?; let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(&user_key.0, &public_key)?; diff --git a/crates/bitwarden-crypto/README.md b/crates/bitwarden-crypto/README.md index 868a2dbbb..c568f475c 100644 --- a/crates/bitwarden-crypto/README.md +++ b/crates/bitwarden-crypto/README.md @@ -16,7 +16,7 @@ secure. use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; async fn example() -> Result<(), CryptoError> { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let data = "Hello, World!".to_owned(); let encrypted = data.clone().encrypt_with_key(&key)?; diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 67f2af587..284c4ce25 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -215,7 +215,7 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); - let raw_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index 68b7dad3a..6574ea91b 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -30,7 +30,7 @@ impl DeviceKey { /// from EncSettings. pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result { let mut rng = rand::thread_rng(); - let device_key = DeviceKey(SymmetricCryptoKey::generate_aes256_cbc_hmac()); + let device_key = DeviceKey(SymmetricCryptoKey::make_aes256_cbc_hmac_key()); let device_private_key = AsymmetricCryptoKey::generate(&mut rng); diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 927d37ec1..39049bf7d 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -167,7 +167,7 @@ fn make_user_key( mut rng: impl rand::RngCore + rand::CryptoRng, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { - let user_key = SymmetricCryptoKey::generate_aes256_cbc_hmac_internal(&mut rng); + let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(&mut rng); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 013192566..8d0f973e9 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -99,8 +99,8 @@ impl SymmetricCryptoKey { /// /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not /// have a good reason for using this function, use - /// [SymmetricCryptoKey::generate_aes256_cbc_hmac] instead. - pub(crate) fn generate_aes256_cbc_hmac_internal( + /// [SymmetricCryptoKey::make_aes256_cbc_hmac_key] instead. + pub(crate) fn make_aes256_cbc_hmac_key_internal( mut rng: impl rand::RngCore + rand::CryptoRng, ) -> Self { let mut enc_key = Box::pin(GenericArray::::default()); @@ -113,13 +113,13 @@ impl SymmetricCryptoKey { } /// Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey] - pub fn generate_aes256_cbc_hmac() -> Self { + pub fn make_aes256_cbc_hmac_key() -> Self { let mut rng = rand::thread_rng(); - Self::generate_aes256_cbc_hmac_internal(&mut rng) + Self::make_aes256_cbc_hmac_key_internal(&mut rng) } /// Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey] - pub fn generate_xchacha20() -> Self { + pub fn make_xchacha20_poly1305_key() -> Self { let mut rng = rand::thread_rng(); let mut enc_key = Box::pin(GenericArray::::default()); rng.fill(enc_key.as_mut_slice()); @@ -354,7 +354,7 @@ mod tests { #[test] fn test_encode_decode_old_symmetric_crypto_key() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let encoded = key.to_encoded(); let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); assert_eq!(key, decoded); @@ -372,7 +372,7 @@ mod tests { #[test] fn test_encode_xchacha20_poly1305_key() { - let key = SymmetricCryptoKey::generate_xchacha20(); + let key = SymmetricCryptoKey::make_xchacha20_poly1305_key(); let encoded = key.to_encoded(); let decoded = SymmetricCryptoKey::try_from(encoded).unwrap(); assert_eq!(key, decoded); diff --git a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs index fc6eb4fef..141d73c0b 100644 --- a/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs +++ b/crates/bitwarden-crypto/src/store/backend/implementation/mod.rs @@ -17,7 +17,7 @@ mod tests { fn test_creates_a_valid_store() { let mut store = create_store::(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); store.upsert(TestSymmKey::A(0), key.clone()); assert_eq!( diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 94cc0fc8c..9c2ea0c29 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -254,7 +254,7 @@ impl KeyStoreContext<'_, Ids> { /// Generate a new random symmetric key and store it in the context pub fn generate_symmetric_key(&mut self, key_id: Ids::Symmetric) -> Result { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); #[allow(deprecated)] self.set_symmetric_key(key_id, key)?; Ok(key_id) @@ -399,7 +399,7 @@ mod tests { // Generate and insert a key let key_a0_id = TestSymmKey::A(0); - let key_a0 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key_a0 = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); store .context_mut() @@ -421,7 +421,7 @@ mod tests { // Generate and insert a key let key_1_id = TestSymmKey::C(1); - let key_1 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key_1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); ctx.set_symmetric_key(key_1_id, key_1.clone()).unwrap(); @@ -429,7 +429,7 @@ mod tests { // Generate and insert a new key let key_2_id = TestSymmKey::C(2); - let key_2 = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key_2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); ctx.set_symmetric_key(key_2_id, key_2.clone()).unwrap(); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 7b4c35c79..4f75edeea 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -65,7 +65,7 @@ pub use context::KeyStoreContext; /// let store: KeyStore = KeyStore::default(); /// /// #[allow(deprecated)] -/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate_aes256_cbc_hmac()); +/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::make_aes256_cbc_hmac_key()); /// /// // Define some data that needs to be encrypted /// struct Data(String); @@ -355,7 +355,7 @@ pub(crate) mod tests { .context_mut() .set_symmetric_key( TestSymmKey::A(n), - SymmetricCryptoKey::generate_aes256_cbc_hmac(), + SymmetricCryptoKey::make_aes256_cbc_hmac_key(), ) .unwrap(); } diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/traits/encryptable.rs index b0395f9c9..84d4b7bdc 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/traits/encryptable.rs @@ -83,7 +83,7 @@ mod tests { fn test_store() -> KeyStore { let store = KeyStore::::default(); - let symm_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let symm_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let asymm_key = AsymmetricCryptoKey::generate(&mut rand::thread_rng()); #[allow(deprecated)] diff --git a/crates/bitwarden-exporters/src/models.rs b/crates/bitwarden-exporters/src/models.rs index 35de4491a..97e19c059 100644 --- a/crates/bitwarden-exporters/src/models.rs +++ b/crates/bitwarden-exporters/src/models.rs @@ -222,7 +222,7 @@ mod tests { #[test] fn test_from_login() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); @@ -273,7 +273,7 @@ mod tests { #[test] fn test_from_cipher_login() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_key(key); let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap(); diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index e12255c3b..b0b062b9e 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -901,7 +901,7 @@ mod tests { #[test] fn test_generate_cipher_key() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_key(key); let original_cipher = generate_cipher(); @@ -927,7 +927,7 @@ mod tests { #[test] fn test_generate_cipher_key_when_a_cipher_key_already_exists() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_key(key); let mut original_cipher = generate_cipher(); @@ -956,7 +956,7 @@ mod tests { #[test] fn test_generate_cipher_key_ignores_attachments_without_key() { - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_key(key); let mut cipher = generate_cipher(); @@ -979,8 +979,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); - let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let org_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -1004,8 +1004,8 @@ mod tests { #[test] fn test_move_user_cipher_to_org_manually() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); - let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let org_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); // Create a cipher with a user key @@ -1024,8 +1024,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_without_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); - let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let org_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let mut cipher = generate_cipher(); @@ -1048,8 +1048,8 @@ mod tests { #[test] fn test_move_user_cipher_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); - let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let org_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1116,8 +1116,8 @@ mod tests { #[test] fn test_move_user_cipher_with_key_with_attachment_with_key_to_org() { let org = uuid::Uuid::new_v4(); - let key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); - let org_key = SymmetricCryptoKey::generate_aes256_cbc_hmac(); + let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let org_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let key_store = create_test_crypto_with_user_and_org_key(key, org, org_key); let org_key = SymmetricKeyId::Organization(org); @@ -1341,7 +1341,7 @@ mod tests { #[test] fn test_decrypt_fido2_private_key() { let key_store = - create_test_crypto_with_user_key(SymmetricCryptoKey::generate_aes256_cbc_hmac()); + create_test_crypto_with_user_key(SymmetricCryptoKey::make_aes256_cbc_hmac_key()); let mut ctx = key_store.context(); let mut cipher_view = generate_cipher(); diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 5452b93d2..d4fc0ee52 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -92,11 +92,11 @@ impl PureCrypto { #[wasm_bindgen] impl PureCrypto { pub fn generate_user_key_aes256_cbc_hmac() -> Vec { - SymmetricCryptoKey::generate_aes256_cbc_hmac().to_encoded() + SymmetricCryptoKey::make_aes256_cbc_hmac_key().to_encoded() } pub fn generate_user_key_xchacha20_poly1305() -> Vec { - SymmetricCryptoKey::generate_xchacha20().to_encoded() + SymmetricCryptoKey::make_xchacha20_poly1305_key().to_encoded() } } @@ -184,13 +184,13 @@ mod tests { } #[test] - fn test_generate_aes256_cbc_hmac() { + fn test_make_aes256_cbc_hmac_key() { let key = PureCrypto::generate_user_key_aes256_cbc_hmac(); assert_eq!(key.len(), 64); } #[test] - fn test_generate_xchacha20_poly1305() { + fn test_make_xchacha20_poly1305_key() { let key = PureCrypto::generate_user_key_xchacha20_poly1305(); assert!(key.len() > 64); } From 7643a54adab1820aa1f2e969183646beddacc652 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 29 Apr 2025 15:08:37 +0200 Subject: [PATCH 139/152] Use wrapped key in more places --- crates/bitwarden-core/src/uniffi_support.rs | 7 +- crates/bitwarden-crypto/src/safe/key_wrap.rs | 52 ++++++++++++ crates/bitwarden-crypto/src/uniffi_support.rs | 18 +++- .../bitwarden-vault/src/cipher/attachment.rs | 12 ++- crates/bitwarden-vault/src/cipher/cipher.rs | 84 +++++++++++-------- .../src/cipher/cipher_client.rs | 8 +- crates/bitwarden-vault/src/uniffi_support.rs | 7 +- 7 files changed, 137 insertions(+), 51 deletions(-) diff --git a/crates/bitwarden-core/src/uniffi_support.rs b/crates/bitwarden-core/src/uniffi_support.rs index 87676449e..00682408b 100644 --- a/crates/bitwarden-core/src/uniffi_support.rs +++ b/crates/bitwarden-core/src/uniffi_support.rs @@ -2,13 +2,18 @@ use std::num::NonZeroU32; -use bitwarden_crypto::{EncString, UnsignedSharedKey}; +use bitwarden_crypto::{EncString, UnsignedSharedKey, WrappedSymmetricKey}; use uuid::Uuid; use crate::UniffiCustomTypeConverter; uniffi::ffi_converter_forward!(NonZeroU32, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!( + WrappedSymmetricKey, + bitwarden_crypto::UniFfiTag, + crate::UniFfiTag +); uniffi::ffi_converter_forward!( UnsignedSharedKey, bitwarden_crypto::UniFfiTag, diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 17f145a76..d5775f049 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -1,3 +1,7 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; /// A wrapped symmetric key is an an [EncString], created where a wrapping key is used to encrypt a @@ -14,6 +18,42 @@ impl From for WrappedSymmetricKey { } } +impl Serialize for WrappedSymmetricKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for WrappedSymmetricKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + EncString::deserialize(deserializer).map(WrappedSymmetricKey) + } +} + +impl std::fmt::Debug for WrappedSymmetricKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WrappedSymmetricKey") + .field("inner_enc_string", &self.as_inner()) + .finish() + } +} + +impl schemars::JsonSchema for WrappedSymmetricKey { + fn schema_name() -> String { + "WrappedSymmetricKey".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + EncString::json_schema(gen) + } +} + impl WrappedSymmetricKey { pub fn into_inner(self) -> EncString { self.0 @@ -22,6 +62,18 @@ impl WrappedSymmetricKey { pub fn as_inner(&self) -> &EncString { &self.0 } + + pub fn try_from_optional(s: Option) -> Result, CryptoError> { + EncString::try_from_optional(s).map(|enc_string| enc_string.map(WrappedSymmetricKey)) + } +} + +impl FromStr for WrappedSymmetricKey { + type Err = CryptoError; + + fn from_str(s: &str) -> Result { + EncString::from_str(s).map(WrappedSymmetricKey) + } } impl WrappedSymmetricKey { diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs index 3e9869cb9..bdc46fffc 100644 --- a/crates/bitwarden-crypto/src/uniffi_support.rs +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -1,6 +1,8 @@ use std::{num::NonZeroU32, str::FromStr}; -use crate::{CryptoError, EncString, UniffiCustomTypeConverter, UnsignedSharedKey}; +use crate::{ + CryptoError, EncString, UniffiCustomTypeConverter, UnsignedSharedKey, WrappedSymmetricKey, +}; uniffi::custom_type!(NonZeroU32, u32); @@ -30,6 +32,20 @@ impl UniffiCustomTypeConverter for EncString { } } +uniffi::custom_type!(WrappedSymmetricKey, String); +impl UniffiCustomTypeConverter for WrappedSymmetricKey { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + let enc_string: EncString = val.parse()?; + Ok(WrappedSymmetricKey::from(enc_string)) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.as_inner().to_string() + } +} + uniffi::custom_type!(UnsignedSharedKey, String); impl UniffiCustomTypeConverter for UnsignedSharedKey { diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index f4bab1445..c5019e0fb 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -1,6 +1,7 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + WrappedSymmetricKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -21,7 +22,7 @@ pub struct Attachment { /// Readable size, ex: "4.2 KB" or "1.43 GB" pub size_name: Option, pub file_name: Option, - pub key: Option, + pub key: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] @@ -34,7 +35,7 @@ pub struct AttachmentView { pub size: Option, pub size_name: Option, pub file_name: Option, - pub key: Option, + pub key: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -89,10 +90,7 @@ impl Encryptable for Attachment // with it, and then encrypt the key with the cipher key let attachment_key = ctx.generate_symmetric_key(ATTACHMENT_KEY)?; let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; - attachment.key = Some( - ctx.wrap_symmetric_key(ciphers_key, attachment_key)? - .into_inner(), - ); + attachment.key = Some(ctx.wrap_symmetric_key(ciphers_key, attachment_key)?); let contents = encrypted_contents.to_buffer()?; @@ -187,7 +185,7 @@ impl TryFrom for Attachment size: attachment.size, size_name: attachment.size_name, file_name: EncString::try_from_optional(attachment.file_name)?, - key: EncString::try_from_optional(attachment.key)?, + key: WrappedSymmetricKey::try_from_optional(attachment.key)?, }) } } diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 2b95f7294..afa9307c1 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -78,7 +78,7 @@ pub struct Cipher { /// More recent ciphers uses individual encryption keys to encrypt the other fields of the /// Cipher. - pub key: Option, + pub key: Option, pub name: EncString, pub notes: Option, @@ -118,7 +118,7 @@ pub struct CipherView { pub collection_ids: Vec, /// Temporary, required to support re-encrypting existing items. - pub key: Option, + pub key: Option, pub name: String, pub notes: Option, @@ -170,7 +170,7 @@ pub struct CipherListView { pub collection_ids: Vec, /// Temporary, required to support calculating TOTP from CipherListView. - pub key: Option, + pub key: Option, pub name: String, pub subtitle: String, @@ -471,7 +471,7 @@ impl CipherView { self.reencrypt_attachment_keys(ctx, old_ciphers_key, new_key)?; self.reencrypt_fido2_credentials(ctx, old_ciphers_key, new_key)?; - self.key = Some(ctx.wrap_symmetric_key(key, new_key)?.into_inner()); + self.key = Some(ctx.wrap_symmetric_key(key, new_key)?); Ok(()) } @@ -498,8 +498,9 @@ impl CipherView { if let Some(attachments) = &mut self.attachments { for attachment in attachments { if let Some(attachment_key) = &mut attachment.key { - let dec_attachment_key: Vec = attachment_key.decrypt(ctx, old_key)?; - *attachment_key = dec_attachment_key.encrypt(ctx, new_key)?; + let tmp_attachment_key_id = SymmetricKeyId::Local("attachment_key"); + ctx.unwrap_symmetric_key(old_key, tmp_attachment_key_id, attachment_key)?; + *attachment_key = ctx.wrap_symmetric_key(new_key, tmp_attachment_key_id)?; } } } @@ -554,8 +555,9 @@ impl CipherView { // If the cipher has a key, we need to re-encrypt it with the new organization key if let Some(cipher_key) = &mut self.key { - let dec_cipher_key: Vec = cipher_key.decrypt(ctx, old_key)?; - *cipher_key = dec_cipher_key.encrypt(ctx, new_key)?; + let tmp_cipher_key_id = SymmetricKeyId::Local("cipher_key"); + ctx.unwrap_symmetric_key(old_key, tmp_cipher_key_id, cipher_key)?; + *cipher_key = ctx.wrap_symmetric_key(new_key, tmp_cipher_key_id)?; } else { // If the cipher does not have a key, we need to reencrypt all attachment keys self.reencrypt_attachment_keys(ctx, old_key, new_key)?; @@ -728,7 +730,7 @@ impl TryFrom for Cipher { creation_date: require!(cipher.creation_date).parse()?, deleted_date: cipher.deleted_date.map(|d| d.parse()).transpose()?, revision_date: require!(cipher.revision_date).parse()?, - key: EncString::try_from_optional(cipher.key)?, + key: WrappedSymmetricKey::try_from_optional(cipher.key)?, }) } } @@ -948,8 +950,7 @@ mod tests { original_cipher.key = Some( ctx.wrap_symmetric_key(SymmetricKeyId::User, cipher_key) - .unwrap() - .into_inner(), + .unwrap(), ); } @@ -958,11 +959,14 @@ mod tests { .unwrap(); // Make sure that the cipher key is decryptable - let _: Vec = original_cipher - .key - .unwrap() - .decrypt(&mut key_store.context(), SymmetricKeyId::User) - .unwrap(); + let wrapped_key = original_cipher.key.unwrap(); + let mut ctx = key_store.context(); + ctx.unwrap_symmetric_key( + SymmetricKeyId::User, + SymmetricKeyId::Local("test_cipher_key"), + &wrapped_key, + ) + .unwrap(); } #[test] @@ -1089,7 +1093,7 @@ mod tests { size: None, size_name: None, file_name: Some("Attachment test name".into()), - key: Some(attachment_key_enc.into_inner()), + key: Some(attachment_key_enc), }; cipher.attachments = Some(vec![attachment]); let cred = generate_fido2(&mut key_store.context(), SymmetricKeyId::User); @@ -1104,12 +1108,20 @@ mod tests { // Check that the attachment key has been re-encrypted with the org key, // and the value matches with the original attachment key let new_attachment_key = cipher.attachments.unwrap()[0].key.clone().unwrap(); - let new_attachment_key_dec: Vec<_> = new_attachment_key - .decrypt(&mut key_store.context(), org_key) + let mut ctx = key_store.context(); + let new_attachment_key_id = ctx + .unwrap_symmetric_key( + org_key, + SymmetricKeyId::Local("test_attachment_key"), + &new_attachment_key, + ) + .unwrap(); + #[allow(deprecated)] + let new_attachment_key_dec = ctx + .dangerous_get_symmetric_key(new_attachment_key_id) .unwrap(); - let new_attachment_key_dec: SymmetricCryptoKey = new_attachment_key_dec.try_into().unwrap(); - assert_eq!(new_attachment_key_dec, attachment_key_val); + assert_eq!(*new_attachment_key_dec, attachment_key_val); let cred2: Fido2CredentialFullView = cipher .login @@ -1139,17 +1151,13 @@ mod tests { .unwrap(); let cipher_key_enc = ctx .wrap_symmetric_key(SymmetricKeyId::User, cipher_key) - .unwrap() - .into_inner(); + .unwrap(); // Attachment has a key that is encrypted with the cipher key let attachment_key = ctx .generate_symmetric_key(SymmetricKeyId::Local("test_attachment_key")) .unwrap(); - let attachment_key_enc: EncString = ctx - .wrap_symmetric_key(cipher_key, attachment_key) - .unwrap() - .into_inner(); + let attachment_key_enc = ctx.wrap_symmetric_key(cipher_key, attachment_key).unwrap(); let mut cipher = generate_cipher(); cipher.key = Some(cipher_key_enc); @@ -1170,19 +1178,20 @@ mod tests { cipher.move_to_organization(&mut ctx, org).unwrap(); // Check that the cipher key has been re-encrypted with the org key, - let new_cipher_key_dec: Vec<_> = cipher - .key - .clone() - .unwrap() - .decrypt(&mut ctx, org_key) + let wrapped_new_cipher_key = cipher.key.clone().unwrap(); + let new_cipher_key_dec = ctx + .unwrap_symmetric_key( + org_key, + SymmetricKeyId::Local("test_cipher_key"), + &wrapped_new_cipher_key, + ) .unwrap(); - - let new_cipher_key_dec: SymmetricCryptoKey = new_cipher_key_dec.try_into().unwrap(); - + #[allow(deprecated)] + let new_cipher_key_dec = ctx.dangerous_get_symmetric_key(new_cipher_key_dec).unwrap(); #[allow(deprecated)] let cipher_key_val = ctx.dangerous_get_symmetric_key(cipher_key).unwrap(); - assert_eq!(new_cipher_key_dec, *cipher_key_val); + assert_eq!(*new_cipher_key_dec, *cipher_key_val); // Check that the attachment key hasn't changed assert_eq!( @@ -1190,8 +1199,9 @@ mod tests { .key .as_ref() .unwrap() + .as_inner() .to_string(), - attachment_key_enc.to_string() + attachment_key_enc.as_inner().to_string() ); let cred2: Fido2Credential = cipher diff --git a/crates/bitwarden-vault/src/cipher/cipher_client.rs b/crates/bitwarden-vault/src/cipher/cipher_client.rs index 9143a2d72..3c3262e7c 100644 --- a/crates/bitwarden-vault/src/cipher/cipher_client.rs +++ b/crates/bitwarden-vault/src/cipher/cipher_client.rs @@ -274,8 +274,8 @@ mod tests { // Ensure attachment key is updated since it's now protected by the cipher key assert_ne!( - attachment.clone().key.unwrap().to_string(), - attachment_view.clone().key.unwrap().to_string() + attachment.clone().key.unwrap().as_inner().to_string(), + attachment_view.clone().key.unwrap().as_inner().to_string() ); assert_eq!(attachment_view.file_name.unwrap(), "h.txt"); @@ -316,8 +316,8 @@ mod tests { // Ensure attachment key is still the same since it's protected by the cipher key assert_eq!( - attachment.clone().key.unwrap().to_string(), - attachment_view.key.unwrap().to_string() + attachment.clone().key.unwrap().as_inner().to_string(), + attachment_view.key.unwrap().as_inner().to_string() ); let content = client diff --git a/crates/bitwarden-vault/src/uniffi_support.rs b/crates/bitwarden-vault/src/uniffi_support.rs index 932f0de7a..9fb051a2b 100644 --- a/crates/bitwarden-vault/src/uniffi_support.rs +++ b/crates/bitwarden-vault/src/uniffi_support.rs @@ -1,7 +1,12 @@ -use bitwarden_crypto::EncString; +use bitwarden_crypto::{EncString, WrappedSymmetricKey}; use uuid::Uuid; uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!( + WrappedSymmetricKey, + bitwarden_crypto::UniFfiTag, + crate::UniFfiTag +); type DateTime = chrono::DateTime; uniffi::ffi_converter_forward!(DateTime, bitwarden_core::UniFfiTag, crate::UniFfiTag); From 3b4e445e2e513f22f390a24d87441e6d732f514f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 29 Apr 2025 15:11:48 +0200 Subject: [PATCH 140/152] Wasm custom type for wrappedSymmetricKey --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index d5775f049..8e632a364 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -4,6 +4,12 @@ use serde::{Deserialize, Serialize}; use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; +#[cfg(feature = "wasm")] +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export type WrappedSymmetricKey = string; +"#; + /// A wrapped symmetric key is an an [EncString], created where a wrapping key is used to encrypt a /// key_to_wrap. /// From 1b7dd692ab5c20ed900307221ea0f6fe2f589a10 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 11:03:00 +0200 Subject: [PATCH 141/152] Cleanup --- crates/bitwarden-crypto/src/keys/master_key.rs | 4 ++-- .../bitwarden-vault/src/cipher/attachment.rs | 6 +++--- crates/bitwarden-vault/src/cipher/cipher.rs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index d938c70ef..206562912 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -149,9 +149,9 @@ pub(super) fn decrypt_user_key( user_key.unwrap_with(&stretched_key) } EncString::Cose_Encrypt0_B64 { .. } => { - return Err(CryptoError::OperationNotSupported( + Err(CryptoError::OperationNotSupported( crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, - )); + )) } } } diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index c5019e0fb..2d4c95468 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -82,7 +82,7 @@ impl Encryptable for Attachment key: SymmetricKeyId, ) -> Result { let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; let mut attachment = self.attachment.clone(); @@ -122,14 +122,14 @@ impl Decryptable> for AttachmentFile { key: SymmetricKeyId, ) -> Result, CryptoError> { let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; // Version 2 or 3, `AttachmentKey` or `CipherKey(AttachmentKey)` if let Some(attachment_key) = &self.attachment.key { let content_key = ctx.unwrap_symmetric_key( ciphers_key, ATTACHMENT_KEY, - &attachment_key.clone().into(), + attachment_key, )?; self.contents.decrypt(ctx, content_key) } else { diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index afa9307c1..9471e36fb 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -200,7 +200,7 @@ impl CipherListView { ) -> Result, CryptoError> { let key = self.key_identifier(); let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let totp = match self.r#type { CipherListViewType::Login(LoginListView { totp, .. }) => { @@ -220,7 +220,7 @@ impl Encryptable for CipherView { key: SymmetricKeyId, ) -> Result { let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let mut cipher_view = self.clone(); @@ -267,7 +267,7 @@ impl Decryptable for Cipher { key: SymmetricKeyId, ) -> Result { let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let mut cipher = CipherView { id: self.id, @@ -461,7 +461,7 @@ impl CipherView { let old_ciphers_key = Cipher::decrypt_cipher_key( ctx, key, - &self.key.take().map(Into::::into), + &self.key.take(), )?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -513,7 +513,7 @@ impl CipherView { ) -> Result, CryptoError> { let key = self.key_identifier(); let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; Ok(self .login @@ -576,7 +576,7 @@ impl CipherView { let key = self.key_identifier(); let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; require!(self.login.as_mut()).fido2_credentials = Some(creds.encrypt(ctx, ciphers_key)?); @@ -590,7 +590,7 @@ impl CipherView { let key = self.key_identifier(); let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let login = require!(self.login.as_ref()); let creds = require!(login.fido2_credentials.as_ref()); @@ -615,7 +615,7 @@ impl Decryptable for Cipher { key: SymmetricKeyId, ) -> Result { let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key.clone().map(|k| k.into()))?; + Cipher::decrypt_cipher_key(ctx, key, &self.key)?; Ok(CipherListView { id: self.id, @@ -1376,7 +1376,7 @@ mod tests { let ciphers_key = Cipher::decrypt_cipher_key( &mut ctx, key_id, - &cipher_view.key.clone().map(|k| k.into()), + &cipher_view.key, ) .unwrap(); From 64d2d50a43c6de87979e9a380395603d0aab870f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 11:04:50 +0200 Subject: [PATCH 142/152] Cargo fmt --- .../bitwarden-crypto/src/keys/master_key.rs | 8 ++--- .../bitwarden-vault/src/cipher/attachment.rs | 13 +++---- crates/bitwarden-vault/src/cipher/cipher.rs | 34 +++++-------------- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 206562912..fd5d410ff 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -148,11 +148,9 @@ pub(super) fn decrypt_user_key( let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.unwrap_with(&stretched_key) } - EncString::Cose_Encrypt0_B64 { .. } => { - Err(CryptoError::OperationNotSupported( - crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, - )) - } + EncString::Cose_Encrypt0_B64 { .. } => Err(CryptoError::OperationNotSupported( + crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, + )), } } diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index 2d4c95468..46aa8f272 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -81,8 +81,7 @@ impl Encryptable for Attachment ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; let mut attachment = self.attachment.clone(); @@ -121,16 +120,12 @@ impl Decryptable> for AttachmentFile { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result, CryptoError> { - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; // Version 2 or 3, `AttachmentKey` or `CipherKey(AttachmentKey)` if let Some(attachment_key) = &self.attachment.key { - let content_key = ctx.unwrap_symmetric_key( - ciphers_key, - ATTACHMENT_KEY, - attachment_key, - )?; + let content_key = + ctx.unwrap_symmetric_key(ciphers_key, ATTACHMENT_KEY, attachment_key)?; self.contents.decrypt(ctx, content_key) } else { // Legacy attachment version 1, use user/org key diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 9471e36fb..a69615691 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -199,8 +199,7 @@ impl CipherListView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let totp = match self.r#type { CipherListViewType::Login(LoginListView { totp, .. }) => { @@ -219,8 +218,7 @@ impl Encryptable for CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let mut cipher_view = self.clone(); @@ -266,8 +264,7 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let mut cipher = CipherView { id: self.id, @@ -458,11 +455,7 @@ impl CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result<(), CryptoError> { - let old_ciphers_key = Cipher::decrypt_cipher_key( - ctx, - key, - &self.key.take(), - )?; + let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.take())?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -512,8 +505,7 @@ impl CipherView { ctx: &mut KeyStoreContext, ) -> Result, CryptoError> { let key = self.key_identifier(); - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; Ok(self .login @@ -575,8 +567,7 @@ impl CipherView { ) -> Result<(), CipherError> { let key = self.key_identifier(); - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; require!(self.login.as_mut()).fido2_credentials = Some(creds.encrypt(ctx, ciphers_key)?); @@ -589,8 +580,7 @@ impl CipherView { ) -> Result, CipherError> { let key = self.key_identifier(); - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; let login = require!(self.login.as_ref()); let creds = require!(login.fido2_credentials.as_ref()); @@ -614,8 +604,7 @@ impl Decryptable for Cipher { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result { - let ciphers_key = - Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; Ok(CipherListView { id: self.id, @@ -1373,12 +1362,7 @@ mod tests { .unwrap(); let key_id = cipher_view.key_identifier(); - let ciphers_key = Cipher::decrypt_cipher_key( - &mut ctx, - key_id, - &cipher_view.key, - ) - .unwrap(); + let ciphers_key = Cipher::decrypt_cipher_key(&mut ctx, key_id, &cipher_view.key).unwrap(); let fido2_credential = generate_fido2(&mut ctx, ciphers_key); From efcc1949c5148d2667c1d7ba65286b55ae304c3b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 6 May 2025 20:07:44 +0200 Subject: [PATCH 143/152] Cleanup --- crates/bitwarden-wasm-internal/src/pure_crypto.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index 2f00ff2be..35b4b8f74 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -108,11 +108,8 @@ impl PureCrypto { pub fn generate_user_key_xchacha20_poly1305() -> Vec { SymmetricCryptoKey::make_xchacha20_poly1305_key().to_encoded() } -} -// Key Wrap -#[wasm_bindgen] -impl PureCrypto { + // Key Wrap pub fn wrap_symmetric_key( key_to_be_wrapped: Vec, wrapping_key: Vec, From 3c20a6c21877c6643dc410a28047a698331318cb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 6 May 2025 20:08:23 +0200 Subject: [PATCH 144/152] Cargo fmt --- crates/bitwarden-crypto/src/keys/pin_key.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 3d3e62182..eea23acd8 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -4,7 +4,8 @@ use super::{ utils::stretch_key, }; use crate::{ - keys::key_encryptable::CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey, WrappedSymmetricKey, + keys::key_encryptable::CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey, + WrappedSymmetricKey, }; /// Pin Key. From f04fb2d66853e69120107a75bc9fcc429983efe4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 6 May 2025 20:15:43 +0200 Subject: [PATCH 145/152] Fix formatting --- crates/bitwarden-crypto/src/keys/master_key.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index b97900512..5e90f248c 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -151,9 +151,9 @@ pub(super) fn decrypt_user_key( user_key.unwrap_with(&stretched_key) } EncString::Cose_Encrypt0_B64 { .. } => { - return Err(CryptoError::OperationNotSupported( + Err(CryptoError::OperationNotSupported( crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, - )); + )) } } } From a2dd716e88d9af11981cda8ea071e0ddf4fe6c0c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 6 May 2025 20:17:27 +0200 Subject: [PATCH 146/152] Cargo fmt --- crates/bitwarden-crypto/src/keys/master_key.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 5e90f248c..3f65ce8fa 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -150,11 +150,9 @@ pub(super) fn decrypt_user_key( let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?); user_key.unwrap_with(&stretched_key) } - EncString::Cose_Encrypt0_B64 { .. } => { - Err(CryptoError::OperationNotSupported( - crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, - )) - } + EncString::Cose_Encrypt0_B64 { .. } => Err(CryptoError::OperationNotSupported( + crate::error::UnsupportedOperation::EncryptionNotImplementedForKey, + )), } } From 28d1e6fa3b2d5ee885b1fc66634c52af09966900 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 11:28:06 +0200 Subject: [PATCH 147/152] Add dummy record to force WrappedSymmetricCryptoKey to be emitted in uniffi --- crates/bitwarden-crypto/src/uniffi_support.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs index bdc46fffc..61dd3bccb 100644 --- a/crates/bitwarden-crypto/src/uniffi_support.rs +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -59,3 +59,12 @@ impl UniffiCustomTypeConverter for UnsignedSharedKey { obj.to_string() } } + + +// Uniffi doesn't emit unused types, this is a dummy record to ensure that the custom type +// converters are emitted +#[allow(dead_code)] +#[derive(uniffi::Record)] +struct UniffiConverterDummyRecord { + wrapped_symmetric_key: WrappedSymmetricKey, +} \ No newline at end of file From 10904f35a1b8c4f759a819a3aa6f4fe0b74cc6f0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 11:39:49 +0200 Subject: [PATCH 148/152] Cargo fmt --- crates/bitwarden-crypto/src/uniffi_support.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs index 61dd3bccb..ef63717bb 100644 --- a/crates/bitwarden-crypto/src/uniffi_support.rs +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -60,11 +60,10 @@ impl UniffiCustomTypeConverter for UnsignedSharedKey { } } - // Uniffi doesn't emit unused types, this is a dummy record to ensure that the custom type // converters are emitted #[allow(dead_code)] #[derive(uniffi::Record)] struct UniffiConverterDummyRecord { wrapped_symmetric_key: WrappedSymmetricKey, -} \ No newline at end of file +} From 66c9ae6c03d59ef3d52dc4adda30bd68b3c921f4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 12:20:58 +0200 Subject: [PATCH 149/152] Give Uniffi dummy record unique name --- crates/bitwarden-crypto/src/uniffi_support.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs index ef63717bb..27f4c4b99 100644 --- a/crates/bitwarden-crypto/src/uniffi_support.rs +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -64,6 +64,6 @@ impl UniffiCustomTypeConverter for UnsignedSharedKey { // converters are emitted #[allow(dead_code)] #[derive(uniffi::Record)] -struct UniffiConverterDummyRecord { +struct CryptoUniffiConverterDummyRecord { wrapped_symmetric_key: WrappedSymmetricKey, } From 6eb359d3e368542f094b495de6f08931c310ee66 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 12:32:27 +0200 Subject: [PATCH 150/152] Move keywrap out of context --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 25 ++++++++++++++++---- crates/bitwarden-crypto/src/store/context.rs | 20 +--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 8e632a364..291450077 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -2,7 +2,10 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; -use crate::{CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; +use crate::{ + error::UnsupportedOperation, CryptoError, EncString, KeyDecryptable, KeyEncryptable, + SymmetricCryptoKey, +}; #[cfg(feature = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] @@ -103,9 +106,23 @@ impl SymmetricCryptoKey { &self, wrapping_key: &SymmetricCryptoKey, ) -> Result { - let encoded = self.to_encoded(); - let enc_string = encoded.encrypt_with_key(wrapping_key)?; - Ok(enc_string.into()) + use crate::SymmetricCryptoKey::*; + + // `Aes256CbcHmacKey` can wrap keys by encrypting their byte serialization obtained using + // `SymmetricCryptoKey::to_encoded()`. `XChaCha20Poly1305Key` need to specify the + // content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey + // or `Aes256CbcKey`, or by specifying the content format to be CoseKey, in case the + // wrapped key is a `XChaCha20Poly1305Key`. + match (&wrapping_key, self) { + (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => { + let encoded = self.to_encoded(); + let enc_string = encoded.encrypt_with_key(wrapping_key)?; + return Ok(WrappedSymmetricKey(enc_string)); + } + _ => Err(CryptoError::OperationNotSupported( + UnsupportedOperation::EncryptionNotImplementedForKey, + )), + } } } diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 1f3fbcd6e..f28b3c735 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -165,27 +165,9 @@ impl KeyStoreContext<'_, Ids> { wrapping_key: Ids::Symmetric, key_to_wrap: Ids::Symmetric, ) -> Result { - use SymmetricCryptoKey::*; - let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?; let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?; - // `Aes256CbcHmacKey` can wrap keys by encrypting their byte serialization obtained using - // `SymmetricCryptoKey::to_encoded()`. `XChaCha20Poly1305Key` need to specify the - // content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey - // or `Aes256CbcKey`, or by specifying the content format to be CoseKey, in case the - // wrapped key is a `XChaCha20Poly1305Key`. - match (wrapping_key_instance, key_to_wrap_instance) { - (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => self - .encrypt_data_with_symmetric_key( - wrapping_key, - key_to_wrap_instance.to_encoded().as_slice(), - ) - .map_err(|_| CryptoError::InvalidKey) - .map(WrappedSymmetricKey::from), - _ => Err(CryptoError::OperationNotSupported( - UnsupportedOperation::EncryptionNotImplementedForKey, - )), - } + key_to_wrap_instance.wrap_with(wrapping_key_instance) } /// Decapsulate a symmetric key into the context by using an already existing asymmetric key From efa1310f9fff24e68bd58536e0b4e322e80bd681 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 12:34:31 +0200 Subject: [PATCH 151/152] Fix clippy warn --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 291450077..9d20e4c0d 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -117,7 +117,7 @@ impl SymmetricCryptoKey { (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => { let encoded = self.to_encoded(); let enc_string = encoded.encrypt_with_key(wrapping_key)?; - return Ok(WrappedSymmetricKey(enc_string)); + Ok(WrappedSymmetricKey(enc_string)) } _ => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, From 34cf0829dc1d2e401ea3fb9866807aaab8fcfb95 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 7 May 2025 12:34:41 +0200 Subject: [PATCH 152/152] Cleanup --- crates/bitwarden-crypto/src/safe/key_wrap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/safe/key_wrap.rs b/crates/bitwarden-crypto/src/safe/key_wrap.rs index 9d20e4c0d..d4f13a00c 100644 --- a/crates/bitwarden-crypto/src/safe/key_wrap.rs +++ b/crates/bitwarden-crypto/src/safe/key_wrap.rs @@ -113,7 +113,7 @@ impl SymmetricCryptoKey { // content format to be either octet stream, in case the wrapped key is a Aes256CbcHmacKey // or `Aes256CbcKey`, or by specifying the content format to be CoseKey, in case the // wrapped key is a `XChaCha20Poly1305Key`. - match (&wrapping_key, self) { + match (wrapping_key, self) { (Aes256CbcHmacKey(_), Aes256CbcHmacKey(_) | Aes256CbcKey(_)) => { let encoded = self.to_encoded(); let enc_string = encoded.encrypt_with_key(wrapping_key)?;