From 72bcf39e3472d7de2513f17648d20e8726700953 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 7 Mar 2025 11:44:47 +0100 Subject: [PATCH 001/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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/108] 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 9cd79c6669dc0d2ce8749c0558c3d7ac563d3d73 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 4 Apr 2025 18:01:50 +0200 Subject: [PATCH 037/108] Initial signature keys --- Cargo.lock | 6 + crates/bitwarden-crypto/Cargo.toml | 2 + crates/bitwarden-crypto/src/error.rs | 3 + crates/bitwarden-crypto/src/keys/mod.rs | 1 + .../src/keys/signing_crypto_key.rs | 124 ++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 crates/bitwarden-crypto/src/keys/signing_crypto_key.rs diff --git a/Cargo.lock b/Cargo.lock index 7ffefb1e2..251e5bfc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,6 +427,8 @@ dependencies = [ "ciborium", "coset", "criterion", + "curve25519-dalek", + "ed25519-dalek", "generic-array", "hkdf", "hmac", @@ -1240,6 +1242,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -1438,8 +1441,11 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core", + "serde", "sha2", "subtle", + "zeroize", ] [[package]] diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index dfa30b824..94364c519 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -33,6 +33,8 @@ cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } chacha20poly1305 = { version = "0.10.1" } ciborium = "0.2.2" coset = "0.3.8" +curve25519-dalek = "4.1.3" +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index c5257c4d6..5ca45fb49 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -58,6 +58,9 @@ pub enum CryptoError { #[error("Encoding error")] EncodingError, + + #[error("Invalid signature")] + InvalidSignature, } #[derive(Debug, Error)] diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index ac3792806..3c7fcbac0 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -22,6 +22,7 @@ mod pin_key; pub use pin_key::PinKey; mod kdf; mod key_id; +mod signing_crypto_key; pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs new file mode 100644 index 000000000..aa9d5f612 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -0,0 +1,124 @@ + +use coset::{iana, CborSerializable}; +use ed25519_dalek::{Signature, Signer, SigningKey}; +use rand::rngs::OsRng; + +use crate::error::Result; + +struct Ed25519SigningKey { + key: ed25519_dalek::SigningKey, +} + +struct Ed25519VerifyingKey { + key: ed25519_dalek::VerifyingKey, +} + +pub enum SigningCryptoKey { + Ed25519(Ed25519SigningKey), +} + +impl SigningCryptoKey { + pub fn generate() -> Result { + let mut csprng = OsRng; + let signing_key: SigningKey = SigningKey::generate(&mut csprng); + Ok(SigningCryptoKey::Ed25519(Ed25519SigningKey { + key: signing_key, + })) + } + + pub fn to_cose(&self) -> Result> { + match self { + SigningCryptoKey::Ed25519(key) => { + let cose = coset::CoseKeyBuilder::new_okp_key() + .add_key_op(iana::KeyOperation::Sign) + .add_key_op(iana::KeyOperation::Verify) + .build(); + cose.to_vec().map_err(|_| crate::error::CryptoError::InvalidKey) + } + } + } + + pub(crate) fn sign(&self, namespace: &[u8], data: &[u8]) -> Result> { + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::EdDSA) + .text_value("namespace".to_string(), ciborium::Value::Bytes(namespace.to_vec())) + .build(); + let sign1 = coset::CoseSign1Builder::new() + .protected(protected) + .create_detached_signature(data, &[], |pt| self.sign_raw(pt).unwrap()) + .build(); + let sign1_data = sign1.to_vec().unwrap(); + + // At the receiving end, deserialize the bytes back to a `CoseSign1` object. + let sign1 = coset::CoseSign1::from_slice(&sign1_data).unwrap(); + Ok(sign1.to_vec().unwrap()) + } + + fn sign_raw(&self, data: &[u8]) -> Result> { + match self { + SigningCryptoKey::Ed25519(key) => { + Ok(key.key.sign(data).to_vec()) + } + } + } + + fn to_verifying_key(&self) -> VerifyingKey { + match self { + SigningCryptoKey::Ed25519(key) => { + VerifyingKey::Ed25519(Ed25519VerifyingKey { + key: key.key.verifying_key(), + }) + } + } + } +} + +pub enum VerifyingKey { + Ed25519(Ed25519VerifyingKey), +} + +impl VerifyingKey { + pub fn to_cose(&self) -> Result> { + match self { + VerifyingKey::Ed25519(key) => { + let cose = coset::CoseKeyBuilder::new_okp_key() + .add_key_op(iana::KeyOperation::Sign) + .add_key_op(iana::KeyOperation::Verify) + .build(); + cose.to_vec().map_err(|_| crate::error::CryptoError::InvalidKey) + } + } + } + + fn verify(&self, namespace: &[u8], signature: &[u8], data: &[u8]) -> bool { + let sign1 = coset::CoseSign1::from_slice(&signature).unwrap(); + let result = sign1.verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)); + return result.is_ok(); + } + + fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<()> { + match self { + VerifyingKey::Ed25519(key) => { + let sig = Signature::from_bytes(signature.try_into().map_err(|_| crate::error::CryptoError::InvalidSignature)?); + key.key.verify_strict(data, &sig) + .map_err(|_| crate::error::CryptoError::InvalidSignature) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_signing_key() { + let signing_key = SigningCryptoKey::generate().unwrap(); + let verifying_key = signing_key.to_verifying_key(); + let data = b"Hello, world!"; + let namespace = b"namespace"; + + let signature = signing_key.sign(namespace, data).unwrap(); + assert!(verifying_key.verify(namespace, &signature, data)); + } +} \ No newline at end of file From dd3d23ef73137150293f6ed03393f5e5caed64d4 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 5 Apr 2025 12:05:43 +0200 Subject: [PATCH 038/108] Cose --- Cargo.toml | 2 +- .../src/keys/signing_crypto_key.rs | 97 ++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7e66758c6..642ec36f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ version = "1.0.0" authors = ["Bitwarden Inc"] edition = "2021" # Important: Changing rust-version should be considered a breaking change -rust-version = "1.75" +rust-version = "1.81" homepage = "https://bitwarden.com" repository = "https://github.com/bitwarden/sdk-internal" license-file = "LICENSE" diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index aa9d5f612..7534a6d2b 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -1,5 +1,6 @@ -use coset::{iana, CborSerializable}; +use ciborium::value::Integer; +use coset::{iana::{self, EnumI64}, CborSerializable}; use ed25519_dalek::{Signature, Signer, SigningKey}; use rand::rngs::OsRng; @@ -30,6 +31,9 @@ impl SigningCryptoKey { match self { SigningCryptoKey::Ed25519(key) => { let cose = coset::CoseKeyBuilder::new_okp_key() + .param(iana::OkpKeyParameter::Crv.to_i64(), ciborium::Value::Integer(Integer::from(iana::EllipticCurve::Ed25519.to_i64()))) + .param(iana::OkpKeyParameter::X.to_i64(), ciborium::Value::Bytes(key.key.verifying_key().to_bytes().to_vec())) + .param(iana::OkpKeyParameter::D.to_i64(), ciborium::Value::Bytes(key.key.to_bytes().to_vec())) .add_key_op(iana::KeyOperation::Sign) .add_key_op(iana::KeyOperation::Verify) .build(); @@ -38,6 +42,43 @@ impl SigningCryptoKey { } } + pub fn from_cose(bytes: &[u8]) -> Result { + let cose_key = coset::CoseKey::from_slice(bytes).map_err(|_| crate::error::CryptoError::InvalidKey)?; + + let (mut crv, mut x, mut d) = (None, None, None); + for (key, value) in &cose_key.params { + if let coset::Label::Int(i) = key { + let key = iana::OkpKeyParameter::from_i64(*i).ok_or(crate::error::CryptoError::InvalidKey)?; + match key { + iana::OkpKeyParameter::Crv => { + crv.replace(value); + } + iana::OkpKeyParameter::X => { + x.replace(value); + } + iana::OkpKeyParameter::D => { + d.replace(value); + } + _ => (), + } + } + } + + let (Some(x), Some(d), Some(crv)) = (x, d, crv) else { + return Err(crate::error::CryptoError::InvalidKey); + }; + + let crv = crv.as_integer().ok_or(crate::error::CryptoError::InvalidKey)?; + if crv == Integer::from(iana::EllipticCurve::Ed25519.to_i64()) { + let d = d.as_bytes().ok_or(crate::error::CryptoError::InvalidKey)?; + let d: &[u8; 32] = d.as_slice().try_into().map_err(|_| crate::error::CryptoError::InvalidKey)?; + let d = ed25519_dalek::SigningKey::from_bytes(&d); + Ok(SigningCryptoKey::Ed25519(Ed25519SigningKey { key: d })) + } else { + Err(crate::error::CryptoError::InvalidKey) + } + } + pub(crate) fn sign(&self, namespace: &[u8], data: &[u8]) -> Result> { let protected = coset::HeaderBuilder::new() .algorithm(iana::Algorithm::EdDSA) @@ -82,6 +123,8 @@ impl VerifyingKey { match self { VerifyingKey::Ed25519(key) => { let cose = coset::CoseKeyBuilder::new_okp_key() + .param(iana::OkpKeyParameter::Crv.to_i64(), ciborium::Value::Integer(Integer::from(iana::EllipticCurve::Ed25519.to_i64()))) + .param(iana::OkpKeyParameter::X.to_i64(), ciborium::Value::Bytes(key.key.to_bytes().to_vec())) .add_key_op(iana::KeyOperation::Sign) .add_key_op(iana::KeyOperation::Verify) .build(); @@ -90,6 +133,38 @@ impl VerifyingKey { } } + pub fn from_cose(bytes: &[u8]) -> Result { + let cose_key = coset::CoseKey::from_slice(&bytes).map_err(|_| crate::error::CryptoError::InvalidKey)?; + let (mut crv, mut x) = (None, None); + for (key, value) in &cose_key.params { + if let coset::Label::Int(i) = key { + let key = iana::OkpKeyParameter::from_i64(*i).ok_or(crate::error::CryptoError::InvalidKey)?; + match key { + iana::OkpKeyParameter::Crv => { + crv.replace(value); + } + iana::OkpKeyParameter::X => { + x.replace(value); + } + _ => (), + } + } + } + let (Some(x), Some(crv)) = (x, crv) else { + return Err(crate::error::CryptoError::InvalidKey); + }; + + let crv = crv.as_integer().ok_or(crate::error::CryptoError::InvalidKey)?; + if crv == Integer::from(iana::EllipticCurve::Ed25519.to_i64()) { + let x = x.as_bytes().ok_or(crate::error::CryptoError::InvalidKey)?; + let x: &[u8; 32] = x.as_slice().try_into().map_err(|_| crate::error::CryptoError::InvalidKey)?; + let x = ed25519_dalek::VerifyingKey::from_bytes(&x).map_err(|_| crate::error::CryptoError::InvalidKey)?; + Ok(VerifyingKey::Ed25519(Ed25519VerifyingKey { key: x })) + } else { + Err(crate::error::CryptoError::InvalidKey) + } + } + fn verify(&self, namespace: &[u8], signature: &[u8], data: &[u8]) -> bool { let sign1 = coset::CoseSign1::from_slice(&signature).unwrap(); let result = sign1.verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)); @@ -121,4 +196,24 @@ mod tests { let signature = signing_key.sign(namespace, data).unwrap(); assert!(verifying_key.verify(namespace, &signature, data)); } + + #[test] + fn test_cose_rountrip_encode_signing() { + let signing_key = SigningCryptoKey::generate().unwrap(); + let cose = signing_key.to_cose().unwrap(); + println!("{:?}", cose); + let parsed_key = SigningCryptoKey::from_cose(&cose).unwrap(); + + assert_eq!(signing_key.to_cose().unwrap(), parsed_key.to_cose().unwrap()); + } + + #[test] + fn test_cose_rountrip_encode_verifying() { + let signing_key = SigningCryptoKey::generate().unwrap(); + let cose = signing_key.to_verifying_key().to_cose().unwrap(); + println!("{:?}", cose); + let parsed_key = VerifyingKey::from_cose(&cose).unwrap(); + + assert_eq!(signing_key.to_verifying_key().to_cose().unwrap(), parsed_key.to_cose().unwrap()); + } } \ No newline at end of file From 951664b7bdbbff3f719bc112b8ad8a8561fea9b1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 8 Apr 2025 13:56:17 +0200 Subject: [PATCH 039/108] 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 040/108] 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 041/108] 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 042/108] 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 043/108] 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 044/108] 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 045/108] 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 046/108] 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 047/108] 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 048/108] 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 049/108] 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 050/108] 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 051/108] 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 052/108] 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 053/108] 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 054/108] 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 055/108] 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 056/108] 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 057/108] 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 058/108] 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 059/108] 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 060/108] 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 061/108] 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 062/108] 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 063/108] 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 ba89a8c409ef531676d02b49a0dabee29ab7e960 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 14:04:05 +0200 Subject: [PATCH 064/108] Finish signing implementation --- Cargo.lock | 25 +- crates/bitwarden-crypto/Cargo.toml | 2 + crates/bitwarden-crypto/src/keys/key_id.rs | 10 + .../src/keys/signing_crypto_key.rs | 413 ++++++++++++------ crates/bitwarden-crypto/src/lib.rs | 2 + crates/bitwarden-crypto/src/signing/mod.rs | 8 + 6 files changed, 318 insertions(+), 142 deletions(-) create mode 100644 crates/bitwarden-crypto/src/signing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 251e5bfc4..5423eb134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,8 @@ dependencies = [ "serde_json", "sha1", "sha2", + "strum 0.27.1", + "strum_macros 0.27.1", "subtle", "thiserror 2.0.12", "tsify-next", @@ -2707,7 +2709,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "strum", + "strum 0.25.0", ] [[package]] @@ -3761,9 +3763,15 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros", + "strum_macros 0.25.3", ] +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" + [[package]] name = "strum_macros" version = "0.25.3" @@ -3777,6 +3785,19 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 94364c519..ed9bd0714 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -48,6 +48,8 @@ schemars = { workspace = true } serde = { workspace = true } sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" +strum = "0.27.1" +strum_macros = "0.27.1" subtle = ">=2.5.0, <3.0" thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } diff --git a/crates/bitwarden-crypto/src/keys/key_id.rs b/crates/bitwarden-crypto/src/keys/key_id.rs index ca248faa8..1a8879e89 100644 --- a/crates/bitwarden-crypto/src/keys/key_id.rs +++ b/crates/bitwarden-crypto/src/keys/key_id.rs @@ -1,5 +1,6 @@ use rand::RngCore; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct KeyId([u8; 24]); impl KeyId { @@ -18,3 +19,12 @@ impl KeyId { Self::from_bytes(key_id) } } + +impl TryFrom<&[u8]> for KeyId { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + let key_id = <[u8; 24]>::try_from(value)?; + Ok(Self::from_bytes(key_id)) + } +} diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 7534a6d2b..3b57e8289 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -1,181 +1,310 @@ - +///! This file implements creation and verification of detached signatures use ciborium::value::Integer; -use coset::{iana::{self, EnumI64}, CborSerializable}; +use ciborium::Value; +use coset::{ + iana::{ + self, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, + OkpKeyParameter, + }, + CborSerializable, CoseKey, Label, RegisteredLabel, RegisteredLabelWithPrivate, +}; use ed25519_dalek::{Signature, Signer, SigningKey}; use rand::rngs::OsRng; -use crate::error::Result; +use super::key_id::KeyId; +use crate::{error::Result, CryptoError, SigningNamespace}; + +#[allow(unused)] +enum SigningCryptoKeyEnum { + Ed25519(ed25519_dalek::SigningKey), +} -struct Ed25519SigningKey { - key: ed25519_dalek::SigningKey, +#[allow(unused)] +enum VerifyingKeyEnum { + Ed25519(ed25519_dalek::VerifyingKey), } -struct Ed25519VerifyingKey { - key: ed25519_dalek::VerifyingKey, +#[allow(unused)] +struct SigningCryptoKey { + id: KeyId, + inner: SigningCryptoKeyEnum, } -pub enum SigningCryptoKey { - Ed25519(Ed25519SigningKey), +#[allow(unused)] +struct VerifyingKey { + id: KeyId, + inner: VerifyingKeyEnum, } +#[allow(unused)] impl SigningCryptoKey { - pub fn generate() -> Result { - let mut csprng = OsRng; - let signing_key: SigningKey = SigningKey::generate(&mut csprng); - Ok(SigningCryptoKey::Ed25519(Ed25519SigningKey { - key: signing_key, - })) + fn generate() -> Result { + Ok(SigningCryptoKey { + id: KeyId::generate(), + inner: SigningCryptoKeyEnum::Ed25519(SigningKey::generate(&mut OsRng)), + }) } - pub fn to_cose(&self) -> Result> { - match self { - SigningCryptoKey::Ed25519(key) => { - let cose = coset::CoseKeyBuilder::new_okp_key() - .param(iana::OkpKeyParameter::Crv.to_i64(), ciborium::Value::Integer(Integer::from(iana::EllipticCurve::Ed25519.to_i64()))) - .param(iana::OkpKeyParameter::X.to_i64(), ciborium::Value::Bytes(key.key.verifying_key().to_bytes().to_vec())) - .param(iana::OkpKeyParameter::D.to_i64(), ciborium::Value::Bytes(key.key.to_bytes().to_vec())) - .add_key_op(iana::KeyOperation::Sign) - .add_key_op(iana::KeyOperation::Verify) - .build(); - cose.to_vec().map_err(|_| crate::error::CryptoError::InvalidKey) - } + fn cose_algorithm(&self) -> Algorithm { + match &self.inner { + SigningCryptoKeyEnum::Ed25519(_) => Algorithm::EdDSA, } } - pub fn from_cose(bytes: &[u8]) -> Result { - let cose_key = coset::CoseKey::from_slice(bytes).map_err(|_| crate::error::CryptoError::InvalidKey)?; - - let (mut crv, mut x, mut d) = (None, None, None); - for (key, value) in &cose_key.params { - if let coset::Label::Int(i) = key { - let key = iana::OkpKeyParameter::from_i64(*i).ok_or(crate::error::CryptoError::InvalidKey)?; - match key { - iana::OkpKeyParameter::Crv => { - crv.replace(value); - } - iana::OkpKeyParameter::X => { - x.replace(value); - } - iana::OkpKeyParameter::D => { - d.replace(value); - } - _ => (), - } + fn to_cose(&self) -> Result> { + match &self.inner { + SigningCryptoKeyEnum::Ed25519(key) => { + coset::CoseKeyBuilder::new_okp_key() + .key_id(self.id.as_bytes().into()) + .algorithm(Algorithm::EdDSA) + // Note: X does not refer to the X coordinate of the public key curve point, but + // to the verifying key, as represented by the curve. In the + // case of Ed25519, this is the compressed Y coordinate. This was ill-defined in + // earlier drafts of the standard. https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair + // + // Note: By the standard, the public key is optional (but RECOMMENDED) here, and + // can be derived on the fly. + .param( + OkpKeyParameter::X.to_i64(), + Value::Bytes(key.verifying_key().to_bytes().into()), + ) + .param( + OkpKeyParameter::D.to_i64(), + Value::Bytes(key.to_bytes().into()), + ) + .param( + OkpKeyParameter::Crv.to_i64(), + Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())), + ) + .add_key_op(KeyOperation::Sign) + .add_key_op(KeyOperation::Verify) + .build() + .to_vec() + .map_err(|_| CryptoError::InvalidKey) } } - - let (Some(x), Some(d), Some(crv)) = (x, d, crv) else { - return Err(crate::error::CryptoError::InvalidKey); + } + + fn from_cose(bytes: &[u8]) -> Result { + let cose_key = CoseKey::from_slice(bytes).map_err(|_| CryptoError::InvalidKey)?; + let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { + return Err(CryptoError::InvalidKey); }; + let key_id: KeyId = key_id + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; - let crv = crv.as_integer().ok_or(crate::error::CryptoError::InvalidKey)?; - if crv == Integer::from(iana::EllipticCurve::Ed25519.to_i64()) { - let d = d.as_bytes().ok_or(crate::error::CryptoError::InvalidKey)?; - let d: &[u8; 32] = d.as_slice().try_into().map_err(|_| crate::error::CryptoError::InvalidKey)?; - let d = ed25519_dalek::SigningKey::from_bytes(&d); - Ok(SigningCryptoKey::Ed25519(Ed25519SigningKey { key: d })) - } else { - Err(crate::error::CryptoError::InvalidKey) + // Labels for supported combinations + match (key_type, algorithm) { + (kty, alg) if kty == RegisteredLabel::Assigned(KeyType::OKP) && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => { + // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair + let (mut crv, mut x, mut d) = (None, None, None); + for (key, value) in &cose_key.params { + if let Label::Int(i) = key { + let key = OkpKeyParameter::from_i64(*i).ok_or(CryptoError::InvalidKey)?; + match key { + OkpKeyParameter::Crv => { + crv.replace(value); + } + OkpKeyParameter::X => { + x.replace(value); + } + OkpKeyParameter::D => { + d.replace(value); + } + _ => (), + } + } + } + + let (Some(_x), Some(d), Some(crv)) = (x, d, crv) else { + return Err(CryptoError::InvalidKey); + }; + let crv: i128 = crv.as_integer().ok_or(CryptoError::InvalidKey)?.into(); + let crv: i64 = crv as i64; + + if crv == EllipticCurve::Ed25519.to_i64() { + let secret_key_bytes: &[u8; 32] = d + .as_bytes() + .ok_or(CryptoError::InvalidKey)? + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + let key = ed25519_dalek::SigningKey::from_bytes(&secret_key_bytes); + Ok(SigningCryptoKey { + id: key_id, + inner: SigningCryptoKeyEnum::Ed25519(key), + }) + } else { + Err(CryptoError::InvalidKey) + } + } + _ => Err(CryptoError::InvalidKey), } } - pub(crate) fn sign(&self, namespace: &[u8], data: &[u8]) -> Result> { - let protected = coset::HeaderBuilder::new() - .algorithm(iana::Algorithm::EdDSA) - .text_value("namespace".to_string(), ciborium::Value::Bytes(namespace.to_vec())) - .build(); - let sign1 = coset::CoseSign1Builder::new() - .protected(protected) + pub(crate) fn sign(&self, namespace: &SigningNamespace, data: &[u8]) -> Result> { + coset::CoseSign1Builder::new() + .protected( + coset::HeaderBuilder::new() + .algorithm(self.cose_algorithm()) + .key_id(self.id.as_bytes().into()) + .text_value( + "namespace".to_string(), + ciborium::Value::Bytes(namespace.to_string().into()), + ) + .build(), + ) .create_detached_signature(data, &[], |pt| self.sign_raw(pt).unwrap()) - .build(); - let sign1_data = sign1.to_vec().unwrap(); - - // At the receiving end, deserialize the bytes back to a `CoseSign1` object. - let sign1 = coset::CoseSign1::from_slice(&sign1_data).unwrap(); - Ok(sign1.to_vec().unwrap()) + .build() + .to_vec() + .map_err(|_| crate::error::CryptoError::InvalidSignature) } - + + /// Signs the given byte array with the signing key. + /// This should never be used directly, but only through the `sign` method, to enforce + /// strong domain separation of the signatures. fn sign_raw(&self, data: &[u8]) -> Result> { - match self { - SigningCryptoKey::Ed25519(key) => { - Ok(key.key.sign(data).to_vec()) - } + match &self.inner { + SigningCryptoKeyEnum::Ed25519(key) => Ok(key.sign(data).to_bytes().to_vec()), } } fn to_verifying_key(&self) -> VerifyingKey { - match self { - SigningCryptoKey::Ed25519(key) => { - VerifyingKey::Ed25519(Ed25519VerifyingKey { - key: key.key.verifying_key(), - }) - } + match &self.inner { + SigningCryptoKeyEnum::Ed25519(key) => VerifyingKey { + id: self.id, + inner: VerifyingKeyEnum::Ed25519(key.verifying_key().clone()), + }, } } } -pub enum VerifyingKey { - Ed25519(Ed25519VerifyingKey), -} - +#[allow(unused)] impl VerifyingKey { - pub fn to_cose(&self) -> Result> { - match self { - VerifyingKey::Ed25519(key) => { - let cose = coset::CoseKeyBuilder::new_okp_key() - .param(iana::OkpKeyParameter::Crv.to_i64(), ciborium::Value::Integer(Integer::from(iana::EllipticCurve::Ed25519.to_i64()))) - .param(iana::OkpKeyParameter::X.to_i64(), ciborium::Value::Bytes(key.key.to_bytes().to_vec())) - .add_key_op(iana::KeyOperation::Sign) - .add_key_op(iana::KeyOperation::Verify) - .build(); - cose.to_vec().map_err(|_| crate::error::CryptoError::InvalidKey) - } + fn to_cose(&self) -> Result> { + match &self.inner { + VerifyingKeyEnum::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key() + .key_id(self.id.as_bytes().into()) + .algorithm(Algorithm::EdDSA) + + .param( + OkpKeyParameter::Crv.to_i64(), + Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())), + ) + .param( + OkpKeyParameter::X.to_i64(), + Value::Bytes(key.to_bytes().to_vec()), + ) + + .add_key_op(KeyOperation::Sign) + .add_key_op(KeyOperation::Verify) + + .build() + .to_vec() + .map_err(|_| CryptoError::InvalidKey), } } - pub fn from_cose(bytes: &[u8]) -> Result { - let cose_key = coset::CoseKey::from_slice(&bytes).map_err(|_| crate::error::CryptoError::InvalidKey)?; - let (mut crv, mut x) = (None, None); - for (key, value) in &cose_key.params { - if let coset::Label::Int(i) = key { - let key = iana::OkpKeyParameter::from_i64(*i).ok_or(crate::error::CryptoError::InvalidKey)?; - match key { - iana::OkpKeyParameter::Crv => { - crv.replace(value); - } - iana::OkpKeyParameter::X => { - x.replace(value); + fn from_cose(bytes: &[u8]) -> Result { + let cose_key = coset::CoseKey::from_slice(&bytes).map_err(|_| CryptoError::InvalidKey)?; + + let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { + return Err(CryptoError::InvalidKey); + }; + let key_id: KeyId = key_id + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + + match (key_type, algorithm) { + (kty, alg) if kty == RegisteredLabel::Assigned(KeyType::OKP) && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => { + let (mut crv, mut x) = (None, None); + for (key, value) in &cose_key.params { + if let coset::Label::Int(i) = key { + let key = OkpKeyParameter::from_i64(*i).ok_or(CryptoError::InvalidKey)?; + match key { + OkpKeyParameter::Crv => { + crv.replace(value); + } + OkpKeyParameter::X => { + x.replace(value); + } + _ => (), + } } - _ => (), + } + let (Some(x), Some(crv)) = (x, crv) else { + return Err(CryptoError::InvalidKey); + }; + + let crv: i128 = crv.as_integer().ok_or(CryptoError::InvalidKey)?.into(); + if crv as i64 == iana::EllipticCurve::Ed25519.to_i64() { + let verifying_key_bytes: &[u8; 32] = x + .as_bytes() + .ok_or(CryptoError::InvalidKey)? + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&verifying_key_bytes) + .map_err(|_| CryptoError::InvalidKey)?; + Ok(VerifyingKey { + id: key_id, + inner: VerifyingKeyEnum::Ed25519(verifying_key), + }) + } else { + Err(CryptoError::InvalidKey) } } + _ => return Err(CryptoError::InvalidKey), } - let (Some(x), Some(crv)) = (x, crv) else { - return Err(crate::error::CryptoError::InvalidKey); + } + + /// Verifies the signature of the given data, for the given namespace. + /// This should never be used directly, but only through the `verify` method, to enforce + /// strong domain separation of the signatures. + pub fn verify(&self, namespace: &SigningNamespace, signature: &[u8], data: &[u8]) -> bool { + let Ok(sign1) = coset::CoseSign1::from_slice(&signature) else { + return false; + }; + let Some(_alg) = &sign1.protected.header.alg else { + return false; }; - let crv = crv.as_integer().ok_or(crate::error::CryptoError::InvalidKey)?; - if crv == Integer::from(iana::EllipticCurve::Ed25519.to_i64()) { - let x = x.as_bytes().ok_or(crate::error::CryptoError::InvalidKey)?; - let x: &[u8; 32] = x.as_slice().try_into().map_err(|_| crate::error::CryptoError::InvalidKey)?; - let x = ed25519_dalek::VerifyingKey::from_bytes(&x).map_err(|_| crate::error::CryptoError::InvalidKey)?; - Ok(VerifyingKey::Ed25519(Ed25519VerifyingKey { key: x })) - } else { - Err(crate::error::CryptoError::InvalidKey) + let mut signature_namespace = None; + for (key, value) in &sign1.protected.header.rest { + if let Label::Text(key) = key { + if key == "namespace" { + signature_namespace.replace(value); + } + } } + let Some(signature_namespace) = signature_namespace else { + return false; + }; + let Some(signature_namespace) = signature_namespace.as_bytes() else { + return false; + }; + if signature_namespace.to_vec() != namespace.to_string().as_bytes() { + return false; + } + + sign1.verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)).is_ok() } - fn verify(&self, namespace: &[u8], signature: &[u8], data: &[u8]) -> bool { - let sign1 = coset::CoseSign1::from_slice(&signature).unwrap(); - let result = sign1.verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)); - return result.is_ok(); - } - + /// Verifies the signature of the given data, for the given namespace. + /// This should never be used directly, but only through the `verify` method, to enforce + /// strong domain separation of the signatures. fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<()> { - match self { - VerifyingKey::Ed25519(key) => { - let sig = Signature::from_bytes(signature.try_into().map_err(|_| crate::error::CryptoError::InvalidSignature)?); - key.key.verify_strict(data, &sig) + match &self.inner { + VerifyingKeyEnum::Ed25519(key) => { + let sig = Signature::from_bytes( + signature + .try_into() + .map_err(|_| crate::error::CryptoError::InvalidSignature)?, + ); + key.verify_strict(data, &sig) .map_err(|_| crate::error::CryptoError::InvalidSignature) } } @@ -187,33 +316,37 @@ mod tests { use super::*; #[test] - fn test_signing_key() { + fn test_sign_roundtrip() { let signing_key = SigningCryptoKey::generate().unwrap(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; - let namespace = b"namespace"; + let namespace = SigningNamespace::EncryptionMetadata; - let signature = signing_key.sign(namespace, data).unwrap(); - assert!(verifying_key.verify(namespace, &signature, data)); + let signature = signing_key.sign(&namespace, data).unwrap(); + assert!(verifying_key.verify(&namespace, &signature, data)); } #[test] - fn test_cose_rountrip_encode_signing() { + fn test_cose_roundtrip_encode_signing() { let signing_key = SigningCryptoKey::generate().unwrap(); let cose = signing_key.to_cose().unwrap(); - println!("{:?}", cose); let parsed_key = SigningCryptoKey::from_cose(&cose).unwrap(); - assert_eq!(signing_key.to_cose().unwrap(), parsed_key.to_cose().unwrap()); + assert_eq!( + signing_key.to_cose().unwrap(), + parsed_key.to_cose().unwrap() + ); } #[test] - fn test_cose_rountrip_encode_verifying() { + fn test_cose_roundtrip_encode_verifying() { let signing_key = SigningCryptoKey::generate().unwrap(); let cose = signing_key.to_verifying_key().to_cose().unwrap(); - println!("{:?}", cose); let parsed_key = VerifyingKey::from_cose(&cose).unwrap(); - assert_eq!(signing_key.to_verifying_key().to_cose().unwrap(), parsed_key.to_cose().unwrap()); + assert_eq!( + signing_key.to_verifying_key().to_cose().unwrap(), + parsed_key.to_cose().unwrap() + ); } -} \ No newline at end of file +} diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index cefa024cc..72b46bd61 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -31,8 +31,10 @@ pub use wordlist::EFF_LONG_WORD_LIST; mod store; pub use store::{KeyStore, KeyStoreContext}; mod cose; +mod signing; mod traits; mod xchacha20; +pub(crate) use signing::*; pub use traits::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs new file mode 100644 index 000000000..eae854d16 --- /dev/null +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -0,0 +1,8 @@ +/// Signing is domain-separated within bitwarden, to prevent cross protocol attacks. +/// +/// A new signed entity or protocol shall use a new signing namespace. Further, signing +/// namespaces cannot be renamed, since that would invalidate signatures. +#[derive(strum_macros::Display, strum_macros::EnumString)] +pub enum SigningNamespace { + EncryptionMetadata, +} From e4585e5da4cfe438f951065a0b4261219876015b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 14:07:02 +0200 Subject: [PATCH 065/108] Add tests --- .../src/keys/signing_crypto_key.rs | 23 +++++++++++++++++++ crates/bitwarden-crypto/src/signing/mod.rs | 2 ++ 2 files changed, 25 insertions(+) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 3b57e8289..d379588e3 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -326,6 +326,29 @@ mod tests { assert!(verifying_key.verify(&namespace, &signature, data)); } + #[test] + fn test_changed_signature_fails() { + let signing_key = SigningCryptoKey::generate().unwrap(); + let verifying_key = signing_key.to_verifying_key(); + let data = b"Hello, world!"; + let namespace = SigningNamespace::EncryptionMetadata; + + let signature = signing_key.sign(&namespace, data).unwrap(); + assert!(!verifying_key.verify(&namespace, &signature, b"Goodbye, world!")); + } + + #[test] + fn test_changed_namespace_fails() { + let signing_key = SigningCryptoKey::generate().unwrap(); + let verifying_key = signing_key.to_verifying_key(); + let data = b"Hello, world!"; + let namespace = SigningNamespace::EncryptionMetadata; + let other_namespace = SigningNamespace::Test; + + let signature = signing_key.sign(&namespace, data).unwrap(); + assert!(!verifying_key.verify(&other_namespace, &signature, data)); + } + #[test] fn test_cose_roundtrip_encode_signing() { let signing_key = SigningCryptoKey::generate().unwrap(); diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs index eae854d16..f66098a36 100644 --- a/crates/bitwarden-crypto/src/signing/mod.rs +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -5,4 +5,6 @@ #[derive(strum_macros::Display, strum_macros::EnumString)] pub enum SigningNamespace { EncryptionMetadata, + #[cfg(test)] + Test, } From 77afef58c21a7931f9670a3a31a72a91ee3f0e89 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 15:57:01 +0200 Subject: [PATCH 066/108] 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 d58c75fad88e9fa38765eedbd7aac325f109e6f6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:42:37 +0200 Subject: [PATCH 067/108] Cleanup --- Cargo.lock | 25 ++----------------- Cargo.toml | 2 +- crates/bitwarden-crypto/Cargo.toml | 6 ++--- crates/bitwarden-crypto/src/cose.rs | 7 ++++++ .../src/keys/signing_crypto_key.rs | 17 +++++++------ crates/bitwarden-crypto/src/signing/mod.rs | 13 +++++++--- 6 files changed, 31 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5423eb134..251e5bfc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,8 +444,6 @@ dependencies = [ "serde_json", "sha1", "sha2", - "strum 0.27.1", - "strum_macros 0.27.1", "subtle", "thiserror 2.0.12", "tsify-next", @@ -2709,7 +2707,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "strum 0.25.0", + "strum", ] [[package]] @@ -3763,15 +3761,9 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros 0.25.3", + "strum_macros", ] -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" - [[package]] name = "strum_macros" version = "0.25.3" @@ -3785,19 +3777,6 @@ dependencies = [ "syn", ] -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 642ec36f7..7e66758c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ version = "1.0.0" authors = ["Bitwarden Inc"] edition = "2021" # Important: Changing rust-version should be considered a breaking change -rust-version = "1.81" +rust-version = "1.75" homepage = "https://bitwarden.com" repository = "https://github.com/bitwarden/sdk-internal" license-file = "LICENSE" diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index ed9bd0714..80ea26f0c 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -33,8 +33,8 @@ cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } chacha20poly1305 = { version = "0.10.1" } ciborium = "0.2.2" coset = "0.3.8" -curve25519-dalek = "4.1.3" -ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } +curve25519-dalek = ">=4.1.3, <4.2.0" +ed25519-dalek = { version = ">=2.1.1, <2.2.0", features = ["rand_core"] } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" @@ -48,8 +48,6 @@ schemars = { workspace = true } serde = { workspace = true } sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" -strum = "0.27.1" -strum_macros = "0.27.1" subtle = ">=2.5.0, <3.0" thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 5e05f0a29..5130d5e8e 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -2,7 +2,14 @@ //! Standardized values from should always be preferred //! unless there is a specific reason to use a private-use value. +// Algorithms +// // 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; + +// Labels +// +// The label used for the namespace ensuring strong domain separation when using signatures. +pub(crate) const SIGNING_NAMESPACE: i64 = -80000; diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index d379588e3..7c221f95b 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -12,7 +12,7 @@ use ed25519_dalek::{Signature, Signer, SigningKey}; use rand::rngs::OsRng; use super::key_id::KeyId; -use crate::{error::Result, CryptoError, SigningNamespace}; +use crate::{cose::SIGNING_NAMESPACE, error::Result, CryptoError, SigningNamespace}; #[allow(unused)] enum SigningCryptoKeyEnum { @@ -150,9 +150,9 @@ impl SigningCryptoKey { coset::HeaderBuilder::new() .algorithm(self.cose_algorithm()) .key_id(self.id.as_bytes().into()) - .text_value( - "namespace".to_string(), - ciborium::Value::Bytes(namespace.to_string().into()), + .value( + SIGNING_NAMESPACE, + ciborium::Value::Integer(Integer::from(namespace.as_i64())) ) .build(), ) @@ -274,8 +274,8 @@ impl VerifyingKey { let mut signature_namespace = None; for (key, value) in &sign1.protected.header.rest { - if let Label::Text(key) = key { - if key == "namespace" { + if let Label::Int(key) = key { + if *key == SIGNING_NAMESPACE { signature_namespace.replace(value); } } @@ -283,10 +283,11 @@ impl VerifyingKey { let Some(signature_namespace) = signature_namespace else { return false; }; - let Some(signature_namespace) = signature_namespace.as_bytes() else { + let Some(signature_namespace) = signature_namespace.as_integer() else { return false; }; - if signature_namespace.to_vec() != namespace.to_string().as_bytes() { + let signature_namespace: i128 = signature_namespace.into(); + if signature_namespace != namespace.as_i64() as i128 { return false; } diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs index f66098a36..05625daaf 100644 --- a/crates/bitwarden-crypto/src/signing/mod.rs +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -2,9 +2,16 @@ /// /// A new signed entity or protocol shall use a new signing namespace. Further, signing /// namespaces cannot be renamed, since that would invalidate signatures. -#[derive(strum_macros::Display, strum_macros::EnumString)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SigningNamespace { - EncryptionMetadata, + #[allow(dead_code)] + EncryptionMetadata = 1, #[cfg(test)] - Test, + Test = -1, +} + +impl SigningNamespace { + pub fn as_i64(&self) -> i64 { + *self as i64 + } } From 70ad092637a9673847bcda3a2ace97f16aa73b92 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:45:00 +0200 Subject: [PATCH 068/108] Cleanup --- crates/bitwarden-crypto/src/keys/signing_crypto_key.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 7c221f95b..32ec33c91 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -122,9 +122,7 @@ impl SigningCryptoKey { return Err(CryptoError::InvalidKey); }; let crv: i128 = crv.as_integer().ok_or(CryptoError::InvalidKey)?.into(); - let crv: i64 = crv as i64; - - if crv == EllipticCurve::Ed25519.to_i64() { + if crv == EllipticCurve::Ed25519.to_i64().into() { let secret_key_bytes: &[u8; 32] = d .as_bytes() .ok_or(CryptoError::InvalidKey)? @@ -240,7 +238,7 @@ impl VerifyingKey { }; let crv: i128 = crv.as_integer().ok_or(CryptoError::InvalidKey)?.into(); - if crv as i64 == iana::EllipticCurve::Ed25519.to_i64() { + if crv == iana::EllipticCurve::Ed25519.to_i64().into() { let verifying_key_bytes: &[u8; 32] = x .as_bytes() .ok_or(CryptoError::InvalidKey)? From f8a76c87af58776ddf3cd3267a619cc40ca1d5a8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:51:51 +0200 Subject: [PATCH 069/108] Remove wrong key_op --- crates/bitwarden-crypto/src/keys/signing_crypto_key.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 32ec33c91..b7fb57138 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -196,8 +196,7 @@ impl VerifyingKey { Value::Bytes(key.to_bytes().to_vec()), ) - .add_key_op(KeyOperation::Sign) - .add_key_op(KeyOperation::Verify) + .add_key_op(KeyOpereyOperation::Verify) .build() .to_vec() From 3affc1787bd028e1b89178f862aac30e775e6f26 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:55:02 +0200 Subject: [PATCH 070/108] Limit verify visibility --- crates/bitwarden-crypto/src/keys/signing_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index b7fb57138..414e7cc69 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -261,7 +261,7 @@ impl VerifyingKey { /// Verifies the signature of the given data, for the given namespace. /// This should never be used directly, but only through the `verify` method, to enforce /// strong domain separation of the signatures. - pub fn verify(&self, namespace: &SigningNamespace, signature: &[u8], data: &[u8]) -> bool { + pub(crate) fn verify(&self, namespace: &SigningNamespace, signature: &[u8], data: &[u8]) -> bool { let Ok(sign1) = coset::CoseSign1::from_slice(&signature) else { return false; }; From 2db9852c5fc2b29399cd5a09bf15efcda6549526 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:56:18 +0200 Subject: [PATCH 071/108] Fix build --- crates/bitwarden-crypto/src/keys/signing_crypto_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 414e7cc69..a45230a93 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -196,7 +196,7 @@ impl VerifyingKey { Value::Bytes(key.to_bytes().to_vec()), ) - .add_key_op(KeyOpereyOperation::Verify) + .add_key_op(KeyOperation::Verify) .build() .to_vec() From 4c3f84ee2bf8c3c52f2cf97196af85d004ac2243 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 18:57:32 +0200 Subject: [PATCH 072/108] Remove unused error from merge conflict --- crates/bitwarden-crypto/src/error.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index ae4d339a8..a888b0dee 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -54,9 +54,6 @@ pub enum CryptoError { #[error("Key algorithm does not match encrypted data type")] WrongKeyType, - #[error("Encoding error")] - EncodingError, - #[error("Invalid signature")] InvalidSignature, From 9393bab400ea08f2b981cced329f22a5d81f968b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:01:28 +0200 Subject: [PATCH 073/108] Cargo fmt --- crates/bitwarden-crypto/src/cose.rs | 2 +- .../src/keys/signing_crypto_key.rs | 44 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index 5130d5e8e..adc4315b4 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -11,5 +11,5 @@ pub(crate) const XCHACHA20_POLY1305: i64 = -70000; // Labels // -// The label used for the namespace ensuring strong domain separation when using signatures. +// The label used for the namespace ensuring strong domain separation when using signatures. pub(crate) const SIGNING_NAMESPACE: i64 = -80000; diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index a45230a93..12d286920 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -2,10 +2,7 @@ use ciborium::value::Integer; use ciborium::Value; use coset::{ - iana::{ - self, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, - OkpKeyParameter, - }, + iana::{self, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter}, CborSerializable, CoseKey, Label, RegisteredLabel, RegisteredLabelWithPrivate, }; use ed25519_dalek::{Signature, Signer, SigningKey}; @@ -87,7 +84,8 @@ impl SigningCryptoKey { fn from_cose(bytes: &[u8]) -> Result { let cose_key = CoseKey::from_slice(bytes).map_err(|_| CryptoError::InvalidKey)?; - let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { + let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) + else { return Err(CryptoError::InvalidKey); }; let key_id: KeyId = key_id @@ -97,7 +95,10 @@ impl SigningCryptoKey { // Labels for supported combinations match (key_type, algorithm) { - (kty, alg) if kty == RegisteredLabel::Assigned(KeyType::OKP) && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => { + (kty, alg) + if kty == RegisteredLabel::Assigned(KeyType::OKP) + && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => + { // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair let (mut crv, mut x, mut d) = (None, None, None); for (key, value) in &cose_key.params { @@ -150,7 +151,7 @@ impl SigningCryptoKey { .key_id(self.id.as_bytes().into()) .value( SIGNING_NAMESPACE, - ciborium::Value::Integer(Integer::from(namespace.as_i64())) + ciborium::Value::Integer(Integer::from(namespace.as_i64())), ) .build(), ) @@ -186,7 +187,6 @@ impl VerifyingKey { VerifyingKeyEnum::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key() .key_id(self.id.as_bytes().into()) .algorithm(Algorithm::EdDSA) - .param( OkpKeyParameter::Crv.to_i64(), Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())), @@ -195,9 +195,7 @@ impl VerifyingKey { OkpKeyParameter::X.to_i64(), Value::Bytes(key.to_bytes().to_vec()), ) - .add_key_op(KeyOperation::Verify) - .build() .to_vec() .map_err(|_| CryptoError::InvalidKey), @@ -207,7 +205,8 @@ impl VerifyingKey { fn from_cose(bytes: &[u8]) -> Result { let cose_key = coset::CoseKey::from_slice(&bytes).map_err(|_| CryptoError::InvalidKey)?; - let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { + let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) + else { return Err(CryptoError::InvalidKey); }; let key_id: KeyId = key_id @@ -216,7 +215,10 @@ impl VerifyingKey { .map_err(|_| CryptoError::InvalidKey)?; match (key_type, algorithm) { - (kty, alg) if kty == RegisteredLabel::Assigned(KeyType::OKP) && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => { + (kty, alg) + if kty == RegisteredLabel::Assigned(KeyType::OKP) + && alg == RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA) => + { let (mut crv, mut x) = (None, None); for (key, value) in &cose_key.params { if let coset::Label::Int(i) = key { @@ -244,8 +246,9 @@ impl VerifyingKey { .as_slice() .try_into() .map_err(|_| CryptoError::InvalidKey)?; - let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&verifying_key_bytes) - .map_err(|_| CryptoError::InvalidKey)?; + let verifying_key = + ed25519_dalek::VerifyingKey::from_bytes(&verifying_key_bytes) + .map_err(|_| CryptoError::InvalidKey)?; Ok(VerifyingKey { id: key_id, inner: VerifyingKeyEnum::Ed25519(verifying_key), @@ -261,7 +264,12 @@ impl VerifyingKey { /// Verifies the signature of the given data, for the given namespace. /// This should never be used directly, but only through the `verify` method, to enforce /// strong domain separation of the signatures. - pub(crate) fn verify(&self, namespace: &SigningNamespace, signature: &[u8], data: &[u8]) -> bool { + pub(crate) fn verify( + &self, + namespace: &SigningNamespace, + signature: &[u8], + data: &[u8], + ) -> bool { let Ok(sign1) = coset::CoseSign1::from_slice(&signature) else { return false; }; @@ -287,8 +295,10 @@ impl VerifyingKey { if signature_namespace != namespace.as_i64() as i128 { return false; } - - sign1.verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)).is_ok() + + sign1 + .verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)) + .is_ok() } /// Verifies the signature of the given data, for the given namespace. From 1d60dbc1539c911328d281864b13364a16766f87 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:06:36 +0200 Subject: [PATCH 074/108] Cleanup --- .../src/keys/signing_crypto_key.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 12d286920..09a26c540 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -1,4 +1,5 @@ -///! This file implements creation and verification of detached signatures +//! This file implements creation and verification of detached signatures + use ciborium::value::Integer; use ciborium::Value; use coset::{ @@ -130,7 +131,7 @@ impl SigningCryptoKey { .as_slice() .try_into() .map_err(|_| CryptoError::InvalidKey)?; - let key = ed25519_dalek::SigningKey::from_bytes(&secret_key_bytes); + let key = ed25519_dalek::SigningKey::from_bytes(secret_key_bytes); Ok(SigningCryptoKey { id: key_id, inner: SigningCryptoKeyEnum::Ed25519(key), @@ -155,7 +156,7 @@ impl SigningCryptoKey { ) .build(), ) - .create_detached_signature(data, &[], |pt| self.sign_raw(pt).unwrap()) + .create_detached_signature(data, &[], |pt| self.sign_raw(pt)) .build() .to_vec() .map_err(|_| crate::error::CryptoError::InvalidSignature) @@ -164,9 +165,9 @@ impl SigningCryptoKey { /// Signs the given byte array with the signing key. /// This should never be used directly, but only through the `sign` method, to enforce /// strong domain separation of the signatures. - fn sign_raw(&self, data: &[u8]) -> Result> { + fn sign_raw(&self, data: &[u8]) -> Vec { match &self.inner { - SigningCryptoKeyEnum::Ed25519(key) => Ok(key.sign(data).to_bytes().to_vec()), + SigningCryptoKeyEnum::Ed25519(key) => key.sign(data).to_bytes().to_vec(), } } @@ -174,7 +175,7 @@ impl SigningCryptoKey { match &self.inner { SigningCryptoKeyEnum::Ed25519(key) => VerifyingKey { id: self.id, - inner: VerifyingKeyEnum::Ed25519(key.verifying_key().clone()), + inner: VerifyingKeyEnum::Ed25519(key.verifying_key()), }, } } @@ -203,7 +204,7 @@ impl VerifyingKey { } fn from_cose(bytes: &[u8]) -> Result { - let cose_key = coset::CoseKey::from_slice(&bytes).map_err(|_| CryptoError::InvalidKey)?; + let cose_key = coset::CoseKey::from_slice(bytes).map_err(|_| CryptoError::InvalidKey)?; let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { @@ -247,7 +248,7 @@ impl VerifyingKey { .try_into() .map_err(|_| CryptoError::InvalidKey)?; let verifying_key = - ed25519_dalek::VerifyingKey::from_bytes(&verifying_key_bytes) + ed25519_dalek::VerifyingKey::from_bytes(verifying_key_bytes) .map_err(|_| CryptoError::InvalidKey)?; Ok(VerifyingKey { id: key_id, @@ -257,7 +258,7 @@ impl VerifyingKey { Err(CryptoError::InvalidKey) } } - _ => return Err(CryptoError::InvalidKey), + _ => Err(CryptoError::InvalidKey), } } @@ -270,7 +271,7 @@ impl VerifyingKey { signature: &[u8], data: &[u8], ) -> bool { - let Ok(sign1) = coset::CoseSign1::from_slice(&signature) else { + let Ok(sign1) = coset::CoseSign1::from_slice(signature) else { return false; }; let Some(_alg) = &sign1.protected.header.alg else { From a29c131ab4875aed2b212ee90f891995b886bf08 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:08:08 +0200 Subject: [PATCH 075/108] Cargo fmt --- crates/bitwarden-crypto/src/keys/signing_crypto_key.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 09a26c540..a8b421448 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -1,7 +1,6 @@ //! This file implements creation and verification of detached signatures -use ciborium::value::Integer; -use ciborium::Value; +use ciborium::{value::Integer, Value}; use coset::{ iana::{self, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter}, CborSerializable, CoseKey, Label, RegisteredLabel, RegisteredLabelWithPrivate, From b719c952d3499dbe6cba2474e239fe934e0341af Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:12:47 +0200 Subject: [PATCH 076/108] Cleanup --- crates/bitwarden-crypto/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 72b46bd61..0b42a84b8 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -32,9 +32,9 @@ mod store; pub use store::{KeyStore, KeyStoreContext}; mod cose; mod signing; +pub(crate) use signing::SigningNamespace; mod traits; mod xchacha20; -pub(crate) use signing::*; pub use traits::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; From a80c102e89f8c1979c1c91ee2902d7d5567fa61f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:21:23 +0200 Subject: [PATCH 077/108] Simplify comment --- crates/bitwarden-crypto/src/signing/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs index 05625daaf..bc47b5356 100644 --- a/crates/bitwarden-crypto/src/signing/mod.rs +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -1,7 +1,6 @@ /// Signing is domain-separated within bitwarden, to prevent cross protocol attacks. /// -/// A new signed entity or protocol shall use a new signing namespace. Further, signing -/// namespaces cannot be renamed, since that would invalidate signatures. +/// A new signed entity or protocol shall use a new signing namespace. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SigningNamespace { #[allow(dead_code)] From 4f0b9b5303e273b75e185aff33d4d8bc94bd0478 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 17 Apr 2025 19:23:04 +0200 Subject: [PATCH 078/108] Fix formatting --- crates/bitwarden-crypto/src/signing/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs index bc47b5356..7cb35de81 100644 --- a/crates/bitwarden-crypto/src/signing/mod.rs +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -1,6 +1,6 @@ /// Signing is domain-separated within bitwarden, to prevent cross protocol attacks. /// -/// A new signed entity or protocol shall use a new signing namespace. +/// A new signed entity or protocol shall use a new signing namespace. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SigningNamespace { #[allow(dead_code)] From 0ff0e989abe56d193cce201df6c5d1ed8fea4bd7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Apr 2025 12:06:29 +0200 Subject: [PATCH 079/108] 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 080/108] 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 081/108] 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 082/108] 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 083/108] 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 084/108] 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 085/108] 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 086/108] 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 087/108] 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 088/108] 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 089/108] 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 090/108] 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 091/108] 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 092/108] 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 093/108] 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 094/108] 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 095/108] 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 096/108] 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 097/108] 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 098/108] 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 099/108] 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 d13e8fe4acb95a1ff5f8b46fc8546fc9482b69c0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 12:56:21 +0200 Subject: [PATCH 100/108] Expose signing key generation to mobile and wasm clients --- .../src/client/encryption_settings.rs | 8 ++-- crates/bitwarden-core/src/client/internal.rs | 6 +-- crates/bitwarden-core/src/mobile/crypto.rs | 38 ++++++++++++++++++- .../src/mobile/crypto_client.rs | 14 +++++-- crates/bitwarden-crypto/src/keys/mod.rs | 1 + .../src/keys/signing_crypto_key.rs | 25 +++++------- crates/bitwarden-wasm-internal/src/crypto.rs | 11 +++++- 7 files changed, 76 insertions(+), 27 deletions(-) diff --git a/crates/bitwarden-core/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs index 7d3825d26..6a621da10 100644 --- a/crates/bitwarden-core/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -1,12 +1,12 @@ -use bitwarden_crypto::{AsymmetricCryptoKey, KeyStore, SymmetricCryptoKey}; #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, EncString}; +use bitwarden_crypto::{KeyStore, SymmetricCryptoKey}; use bitwarden_error::bitwarden_error; use thiserror::Error; use uuid::Uuid; use crate::{ - key_management::{AsymmetricKeyId, KeyIds, SymmetricKeyId}, + key_management::{KeyIds, SymmetricKeyId}, MissingPrivateKeyError, VaultLockedError, }; @@ -42,7 +42,7 @@ impl EncryptionSettings { private_key: EncString, store: &KeyStore, ) -> Result<(), EncryptionSettingsError> { - use bitwarden_crypto::KeyDecryptable; + use bitwarden_crypto::{AsymmetricCryptoKey, KeyDecryptable}; use log::warn; use crate::key_management::{AsymmetricKeyId, SymmetricKeyId}; @@ -94,6 +94,8 @@ impl EncryptionSettings { org_enc_keys: Vec<(Uuid, AsymmetricEncString)>, store: &KeyStore, ) -> Result<(), EncryptionSettingsError> { + use crate::key_management::AsymmetricKeyId; + let mut ctx = store.context_mut(); // FIXME: [PM-11690] - Early abort to handle private key being corrupt diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs index e365f0250..00cf06c9a 100644 --- a/crates/bitwarden-core/src/client/internal.rs +++ b/crates/bitwarden-core/src/client/internal.rs @@ -8,13 +8,11 @@ use bitwarden_crypto::{AsymmetricEncString, EncString, Kdf, MasterKey, PinKey}; use chrono::Utc; use uuid::Uuid; +use super::encryption_settings::EncryptionSettings; #[cfg(feature = "secrets")] use super::login_method::ServiceAccountLoginMethod; use crate::{ - auth::renew::renew_token, - client::{encryption_settings::EncryptionSettings, login_method::LoginMethod}, - key_management::KeyIds, - DeviceType, + auth::renew::renew_token, client::login_method::LoginMethod, key_management::KeyIds, DeviceType, }; #[cfg(feature = "internal")] use crate::{ diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index fd9c6be0f..bed96bd2f 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, SigningCryptoKey, SymmetricCryptoKey, UserKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -513,6 +513,34 @@ pub fn verify_asymmetric_keys( }) } +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct MakeSigningKeysResponse { + /// The verifying key + verifying_key: String, + /// Signing key, encrypted with a symmetric key (user key, org key) + signing_key: EncString, +} + +pub fn make_signing_keys(wrapping_key: String) -> Result { + let wrapping_key = SymmetricCryptoKey::try_from(wrapping_key)?; + let signature_keypair = SigningCryptoKey::generate()?; + // This needs to be changed to use the correct cose content format before rolling out to real + // accounts + let encrypted_signing_key = signature_keypair + .to_cose()? + .encrypt_with_key(&wrapping_key)?; + let serialized_verifying_key = signature_keypair.to_verifying_key().to_cose()?; + let serialized_verifying_key_b64 = STANDARD.encode(serialized_verifying_key); + + Ok(MakeSigningKeysResponse { + verifying_key: serialized_verifying_key_b64, + signing_key: encrypted_signing_key, + }) +} + #[cfg(test)] mod tests { use std::num::NonZeroU32; @@ -793,6 +821,14 @@ mod tests { assert!(!private_key.is_empty()); } + #[test] + fn test_make_signing_keys() { + let user_key = SymmetricCryptoKey::generate(); + let response = make_signing_keys(user_key.to_base64()).unwrap(); + assert!(!response.verifying_key.is_empty()); + let _: Vec = response.signing_key.decrypt_with_key(&user_key).unwrap(); + } + #[test] fn test_verify_asymmetric_keys_success() { let (user_key, key_pair) = setup_asymmetric_keys_test(); diff --git a/crates/bitwarden-core/src/mobile/crypto_client.rs b/crates/bitwarden-core/src/mobile/crypto_client.rs index 90462a489..1e8e828ac 100644 --- a/crates/bitwarden-core/src/mobile/crypto_client.rs +++ b/crates/bitwarden-core/src/mobile/crypto_client.rs @@ -3,9 +3,10 @@ use bitwarden_crypto::CryptoError; use bitwarden_crypto::{AsymmetricEncString, EncString}; use super::crypto::{ - derive_key_connector, make_key_pair, verify_asymmetric_keys, DeriveKeyConnectorError, - DeriveKeyConnectorRequest, EnrollAdminPasswordResetError, MakeKeyPairResponse, - MobileCryptoError, VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse, + derive_key_connector, make_key_pair, make_signing_keys, verify_asymmetric_keys, + DeriveKeyConnectorError, DeriveKeyConnectorRequest, EnrollAdminPasswordResetError, + MakeKeyPairResponse, MakeSigningKeysResponse, MobileCryptoError, VerifyAsymmetricKeysRequest, + VerifyAsymmetricKeysResponse, }; #[cfg(feature = "internal")] use crate::mobile::crypto::{ @@ -81,6 +82,13 @@ impl CryptoClient<'_> { ) -> Result { verify_asymmetric_keys(request) } + + pub fn make_signing_keys( + &self, + wrapping_key: String, + ) -> Result { + make_signing_keys(wrapping_key) + } } impl<'a> Client { diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 3c7fcbac0..2f745a94c 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -27,4 +27,5 @@ pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, }; +pub use signing_crypto_key::{SigningCryptoKey, VerifyingKey}; mod utils; diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index a8b421448..c033fff67 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -22,20 +22,20 @@ enum VerifyingKeyEnum { } #[allow(unused)] -struct SigningCryptoKey { +pub struct SigningCryptoKey { id: KeyId, inner: SigningCryptoKeyEnum, } #[allow(unused)] -struct VerifyingKey { +pub struct VerifyingKey { id: KeyId, inner: VerifyingKeyEnum, } #[allow(unused)] impl SigningCryptoKey { - fn generate() -> Result { + pub fn generate() -> Result { Ok(SigningCryptoKey { id: KeyId::generate(), inner: SigningCryptoKeyEnum::Ed25519(SigningKey::generate(&mut OsRng)), @@ -48,7 +48,7 @@ impl SigningCryptoKey { } } - fn to_cose(&self) -> Result> { + pub fn to_cose(&self) -> Result> { match &self.inner { SigningCryptoKeyEnum::Ed25519(key) => { coset::CoseKeyBuilder::new_okp_key() @@ -82,7 +82,7 @@ impl SigningCryptoKey { } } - fn from_cose(bytes: &[u8]) -> Result { + pub fn from_cose(bytes: &[u8]) -> Result { let cose_key = CoseKey::from_slice(bytes).map_err(|_| CryptoError::InvalidKey)?; let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) else { @@ -143,7 +143,7 @@ impl SigningCryptoKey { } } - pub(crate) fn sign(&self, namespace: &SigningNamespace, data: &[u8]) -> Result> { + pub fn sign(&self, namespace: &SigningNamespace, data: &[u8]) -> Result> { coset::CoseSign1Builder::new() .protected( coset::HeaderBuilder::new() @@ -170,7 +170,7 @@ impl SigningCryptoKey { } } - fn to_verifying_key(&self) -> VerifyingKey { + pub fn to_verifying_key(&self) -> VerifyingKey { match &self.inner { SigningCryptoKeyEnum::Ed25519(key) => VerifyingKey { id: self.id, @@ -182,7 +182,7 @@ impl SigningCryptoKey { #[allow(unused)] impl VerifyingKey { - fn to_cose(&self) -> Result> { + pub fn to_cose(&self) -> Result> { match &self.inner { VerifyingKeyEnum::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key() .key_id(self.id.as_bytes().into()) @@ -202,7 +202,7 @@ impl VerifyingKey { } } - fn from_cose(bytes: &[u8]) -> Result { + pub fn from_cose(bytes: &[u8]) -> Result { let cose_key = coset::CoseKey::from_slice(bytes).map_err(|_| CryptoError::InvalidKey)?; let (key_id, Some(algorithm), key_type) = (cose_key.key_id, cose_key.alg, cose_key.kty) @@ -264,12 +264,7 @@ impl VerifyingKey { /// Verifies the signature of the given data, for the given namespace. /// This should never be used directly, but only through the `verify` method, to enforce /// strong domain separation of the signatures. - pub(crate) fn verify( - &self, - namespace: &SigningNamespace, - signature: &[u8], - data: &[u8], - ) -> bool { + pub fn verify(&self, namespace: &SigningNamespace, signature: &[u8], data: &[u8]) -> bool { let Ok(sign1) = coset::CoseSign1::from_slice(signature) else { return false; }; diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs index 58ab6c0ff..ad1e7de9c 100644 --- a/crates/bitwarden-wasm-internal/src/crypto.rs +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use bitwarden_core::{ client::encryption_settings::EncryptionSettingsError, mobile::crypto::{ - InitOrgCryptoRequest, InitUserCryptoRequest, MakeKeyPairResponse, + InitOrgCryptoRequest, InitUserCryptoRequest, MakeKeyPairResponse, MakeSigningKeysResponse, VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse, }, Client, @@ -55,4 +55,13 @@ impl CryptoClient { ) -> Result { self.0.crypto().verify_asymmetric_keys(request) } + + /// Generates a new signing key pair and encrypts the signing key with the provided symmetric + /// key. Crypto initialization not required. + pub fn make_signing_keys( + &self, + wrapping_key: String, + ) -> Result { + self.0.crypto().make_signing_keys(wrapping_key) + } } From 712dfeaf14c449c7c939a66b88586c58df8db65e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 13:20:15 +0200 Subject: [PATCH 101/108] Make signatures be a struct instead of vec --- crates/bitwarden-crypto/src/error.rs | 3 + .../src/keys/signing_crypto_key.rs | 143 +++++++++++------- crates/bitwarden-crypto/src/signing/mod.rs | 11 ++ 3 files changed, 104 insertions(+), 53 deletions(-) diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index a888b0dee..1c5c5cfc9 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -57,6 +57,9 @@ pub enum CryptoError { #[error("Invalid signature")] InvalidSignature, + #[error("Invalid namespace")] + InvalidNamespace, + #[error("Invalid nonce length")] InvalidNonceLength, } diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index a8b421448..a7df2301e 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -3,9 +3,9 @@ use ciborium::{value::Integer, Value}; use coset::{ iana::{self, Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter}, - CborSerializable, CoseKey, Label, RegisteredLabel, RegisteredLabelWithPrivate, + CborSerializable, CoseKey, CoseSign1, Label, RegisteredLabel, RegisteredLabelWithPrivate, }; -use ed25519_dalek::{Signature, Signer, SigningKey}; +use ed25519_dalek::Signer; use rand::rngs::OsRng; use super::key_id::KeyId; @@ -22,7 +22,7 @@ enum VerifyingKeyEnum { } #[allow(unused)] -struct SigningCryptoKey { +struct SigningKey { id: KeyId, inner: SigningCryptoKeyEnum, } @@ -34,11 +34,11 @@ struct VerifyingKey { } #[allow(unused)] -impl SigningCryptoKey { +impl SigningKey { fn generate() -> Result { - Ok(SigningCryptoKey { + Ok(SigningKey { id: KeyId::generate(), - inner: SigningCryptoKeyEnum::Ed25519(SigningKey::generate(&mut OsRng)), + inner: SigningCryptoKeyEnum::Ed25519(ed25519_dalek::SigningKey::generate(&mut OsRng)), }) } @@ -131,7 +131,7 @@ impl SigningCryptoKey { .try_into() .map_err(|_| CryptoError::InvalidKey)?; let key = ed25519_dalek::SigningKey::from_bytes(secret_key_bytes); - Ok(SigningCryptoKey { + Ok(SigningKey { id: key_id, inner: SigningCryptoKeyEnum::Ed25519(key), }) @@ -143,22 +143,22 @@ impl SigningCryptoKey { } } - pub(crate) fn sign(&self, namespace: &SigningNamespace, data: &[u8]) -> Result> { - coset::CoseSign1Builder::new() - .protected( - coset::HeaderBuilder::new() - .algorithm(self.cose_algorithm()) - .key_id(self.id.as_bytes().into()) - .value( - SIGNING_NAMESPACE, - ciborium::Value::Integer(Integer::from(namespace.as_i64())), - ) - .build(), - ) - .create_detached_signature(data, &[], |pt| self.sign_raw(pt)) - .build() - .to_vec() - .map_err(|_| crate::error::CryptoError::InvalidSignature) + pub(crate) fn sign(&self, namespace: &SigningNamespace, data: &[u8]) -> Signature { + Signature::from( + coset::CoseSign1Builder::new() + .protected( + coset::HeaderBuilder::new() + .algorithm(self.cose_algorithm()) + .key_id(self.id.as_bytes().into()) + .value( + SIGNING_NAMESPACE, + ciborium::Value::Integer(Integer::from(namespace.as_i64())), + ) + .build(), + ) + .create_detached_signature(data, &[], |pt| self.sign_raw(pt)) + .build(), + ) } /// Signs the given byte array with the signing key. @@ -267,36 +267,23 @@ impl VerifyingKey { pub(crate) fn verify( &self, namespace: &SigningNamespace, - signature: &[u8], + signature: &Signature, data: &[u8], ) -> bool { - let Ok(sign1) = coset::CoseSign1::from_slice(signature) else { - return false; - }; - let Some(_alg) = &sign1.protected.header.alg else { + let Some(_alg) = &signature.inner().protected.header.alg else { return false; }; - let mut signature_namespace = None; - for (key, value) in &sign1.protected.header.rest { - if let Label::Int(key) = key { - if *key == SIGNING_NAMESPACE { - signature_namespace.replace(value); - } - } - } - let Some(signature_namespace) = signature_namespace else { + let mut signature_namespace = signature.namespace(); + let Ok(signature_namespace) = signature.namespace() else { return false; }; - let Some(signature_namespace) = signature_namespace.as_integer() else { - return false; - }; - let signature_namespace: i128 = signature_namespace.into(); - if signature_namespace != namespace.as_i64() as i128 { + if signature_namespace != *namespace { return false; } - sign1 + signature + .inner() .verify_detached_signature(data, &[], |sig, data| self.verify_raw(sig, data)) .is_ok() } @@ -307,7 +294,7 @@ impl VerifyingKey { fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<()> { match &self.inner { VerifyingKeyEnum::Ed25519(key) => { - let sig = Signature::from_bytes( + let sig = ed25519_dalek::Signature::from_bytes( signature .try_into() .map_err(|_| crate::error::CryptoError::InvalidSignature)?, @@ -319,49 +306,99 @@ impl VerifyingKey { } } +/// A signature cryptographically attests to a (namespace, data) pair. The namespace is included in +/// the signature object, the data is not. One data object can be signed multiple times, with +/// different namespaces / by different signers, depending on the application needs. +#[allow(unused)] +struct Signature(CoseSign1); + +impl From for Signature { + fn from(cose_sign1: CoseSign1) -> Self { + Signature(cose_sign1) + } +} + +#[allow(unused)] +impl Signature { + fn from_bytes(bytes: &[u8]) -> Result { + let cose_sign1 = CoseSign1::from_slice(bytes).map_err(|_| CryptoError::InvalidSignature)?; + Ok(Signature(cose_sign1)) + } + + fn to_bytes(&self) -> Result> { + self.0 + .clone() + .to_vec() + .map_err(|_| CryptoError::InvalidSignature) + } + + fn inner(&self) -> &CoseSign1 { + &self.0 + } + + fn namespace(&self) -> Result { + let mut namespace = None; + for (key, value) in &self.0.protected.header.rest { + if let Label::Int(key) = key { + if *key == SIGNING_NAMESPACE { + namespace.replace(value); + } + } + } + let Some(namespace) = namespace else { + return Err(CryptoError::InvalidNamespace); + }; + let Some(namespace) = namespace.as_integer() else { + return Err(CryptoError::InvalidNamespace); + }; + let namespace: i128 = namespace.into(); + SigningNamespace::try_from_i64(namespace as i64) + } +} + #[cfg(test)] mod tests { use super::*; #[test] fn test_sign_roundtrip() { - let signing_key = SigningCryptoKey::generate().unwrap(); + let signing_key = SigningKey::generate().unwrap(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; - let signature = signing_key.sign(&namespace, data).unwrap(); + let signature = signing_key.sign(&namespace, data); assert!(verifying_key.verify(&namespace, &signature, data)); } #[test] fn test_changed_signature_fails() { - let signing_key = SigningCryptoKey::generate().unwrap(); + let signing_key = SigningKey::generate().unwrap(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; - let signature = signing_key.sign(&namespace, data).unwrap(); + let signature = signing_key.sign(&namespace, data); assert!(!verifying_key.verify(&namespace, &signature, b"Goodbye, world!")); } #[test] fn test_changed_namespace_fails() { - let signing_key = SigningCryptoKey::generate().unwrap(); + let signing_key = SigningKey::generate().unwrap(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; let other_namespace = SigningNamespace::Test; - let signature = signing_key.sign(&namespace, data).unwrap(); + let signature = signing_key.sign(&namespace, data); assert!(!verifying_key.verify(&other_namespace, &signature, data)); } #[test] fn test_cose_roundtrip_encode_signing() { - let signing_key = SigningCryptoKey::generate().unwrap(); + let signing_key = SigningKey::generate().unwrap(); let cose = signing_key.to_cose().unwrap(); - let parsed_key = SigningCryptoKey::from_cose(&cose).unwrap(); + let parsed_key = SigningKey::from_cose(&cose).unwrap(); assert_eq!( signing_key.to_cose().unwrap(), @@ -371,7 +408,7 @@ mod tests { #[test] fn test_cose_roundtrip_encode_verifying() { - let signing_key = SigningCryptoKey::generate().unwrap(); + let signing_key = SigningKey::generate().unwrap(); let cose = signing_key.to_verifying_key().to_cose().unwrap(); let parsed_key = VerifyingKey::from_cose(&cose).unwrap(); diff --git a/crates/bitwarden-crypto/src/signing/mod.rs b/crates/bitwarden-crypto/src/signing/mod.rs index 7cb35de81..6f9f21661 100644 --- a/crates/bitwarden-crypto/src/signing/mod.rs +++ b/crates/bitwarden-crypto/src/signing/mod.rs @@ -1,3 +1,5 @@ +use crate::CryptoError; + /// Signing is domain-separated within bitwarden, to prevent cross protocol attacks. /// /// A new signed entity or protocol shall use a new signing namespace. @@ -13,4 +15,13 @@ impl SigningNamespace { pub fn as_i64(&self) -> i64 { *self as i64 } + + pub fn try_from_i64(value: i64) -> Result { + match value { + 1 => Ok(Self::EncryptionMetadata), + #[cfg(test)] + -1 => Ok(Self::Test), + _ => Err(CryptoError::InvalidNamespace), + } + } } From 7b7f5077c8a03d735946c964cf09d0b3611ff1bb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 13:48:05 +0200 Subject: [PATCH 102/108] Add signing values to key context --- crates/bitwarden-core/src/auth/auth_request.rs | 1 + .../bitwarden-core/src/auth/login/auth_request.rs | 1 + crates/bitwarden-core/src/client/test_accounts.rs | 3 +++ crates/bitwarden-core/src/key_management/mod.rs | 7 ++++++- crates/bitwarden-core/src/mobile/crypto.rs | 9 +++++++++ crates/bitwarden-crypto/src/keys/key_id.rs | 2 +- .../src/keys/signing_crypto_key.rs | 8 ++++++-- crates/bitwarden-crypto/src/traits/key_id.rs | 15 ++++++++++++--- crates/bitwarden-crypto/src/traits/mod.rs | 10 +++++++++- 9 files changed, 48 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 25e6e1efb..1d4a36c97 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -246,6 +246,7 @@ mod tests { kdf_params: kdf, email: email.to_owned(), private_key: private_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::AuthRequest { request_private_key: auth_req.private_key, method: AuthRequestMethod::UserKey { diff --git a/crates/bitwarden-core/src/auth/login/auth_request.rs b/crates/bitwarden-core/src/auth/login/auth_request.rs index 6d2abd203..17ba9a234 100644 --- a/crates/bitwarden-core/src/auth/login/auth_request.rs +++ b/crates/bitwarden-core/src/auth/login/auth_request.rs @@ -118,6 +118,7 @@ pub(crate) async fn complete_auth_request( kdf_params: kdf, email: auth_req.email, private_key: require!(r.private_key), + signing_key: None, method: InitUserCryptoMethod::AuthRequest { request_private_key: auth_req.private_key, method, diff --git a/crates/bitwarden-core/src/client/test_accounts.rs b/crates/bitwarden-core/src/client/test_accounts.rs index 367c36495..f5d1819a7 100644 --- a/crates/bitwarden-core/src/client/test_accounts.rs +++ b/crates/bitwarden-core/src/client/test_accounts.rs @@ -123,6 +123,8 @@ pub fn test_bitwarden_com_account() -> TestAccount { email: "test@bitwarden.com".to_owned(), 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=".to_owned(), + signing_key: None, + method: InitUserCryptoMethod::Password { password: "asdfasdfasdf".to_owned(), user_key: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".to_owned(), @@ -179,6 +181,7 @@ pub fn test_legacy_user_key_account() -> TestAccount { }, email: "legacy@bitwarden.com".to_owned(), private_key: "2.leBIE5u0aQUeXi++JzAnrA==|P8x+hs00RJx7epw+49qVtBhLJxE/JTL5dEHg6kq5pbZLdUY8ZvWK49v0EqgHbv1r298N9+msoO9hmdSIVIAZyycemYDSoc1rX4S1KpS/ZMA/Vd3VLFb+o13Ts62GFQ5ygHKgQZfzjU6jO5P/B/0igzFoxyJDomhW5NBC1P9+e/5qNRZN8loKvAaWc/7XtpRayPQqWx+AgYc2ntb1GF5hRVrW4M47bG5ZKllbJWtQKg2sXIy2lDBbKLRFWF4RFzNVcXQGMoPdWLY0f3uTwUH01dyGmFFMbOvfBEuYqmZyPdd93ve8zuFOEqkj46Ulpq2CVG8NvZARTwsdKl6XB0wGuHFoTsDJT2SJGl67pBBKsVRGxy059QW+9hAIB+emIV0T/7+0rvdeSXZ4AbG+oXGEXFTkHefwJKfeT0MBTAjYKr7ZRLgqvf7n39+nCEJU4l22kp8FmjcWIU7AgNipdGHC+UT2yfOcYlvgBgWDcMXcbVDMyus9105RgcW6PHozUj7yjbohI/A3XWmAFufP6BSnmEFCKoik78X/ry09xwiH2rN4KVXe/k9LpRNB2QBGIVsfgCrkxjeE8r0nA59Rvwrhny1z5BkvMW/N1KrGuafg/IYgegx72gJNuZPZlFu1Vs7HxySHmzYvm3DPV7bzCaAxxNtvZmQquNIEnsDQfjJO76iL1JCtDqNJVzGLHTMTr7S5hkOcydcH3kfKwZdA1ULVd2qu0SwOUEP/ECjU/cS5INy6WPYzNMAe/g2DISpQjNwBb5K17PIiGOR7/Q/A6E8pVnkHiAXuUFr9aLOYN9BWSu5Z+BPHH65na2FDmssix5WV09I2sUBfvdNCjkrUGdYgo8E+vOTn35x9GJHF45uhmgC1yAn/+/RSpORlrSVJ7NNP11dn3htUpSsIy/b7ituAu8Ry5mhicFU8CXJL4NeMlXThUt8P++wxs4wMkBvJ8J9NJAVKbAOA2o+GOdjbh6Ww3IRegkurWh4oL/dFSx0LpaXJuw6HFT/LzticPlSwHtUP11hZ81seMsXmkSZd8IugRFfwpPl7N6PVRWDOKxLf4gPqcnJ11TvfasXy1uolV2vZCPbrbbVzQMPdVwL/OzwfhqsIgQZI8rsDMK5D2EX8MaT8MDfGcsYcVTL9PmuZYLpOUnnHX0A1opAAa9iPw3d+eWB/GAyLvKPnMTUqVNos8HcCktXckCshihA8QuBJOwg3m0j2LPSZ5Jvf8gbXauBmt9I4IlJq0xfpgquYY1WNnO8IcWE4N9W+ASvOr9gnduA6CkDeAlyMUFmdpkeCjGMcsV741bTCPApSQlL3/TOT1cjK3iejWpz0OaVHXyg02hW2fNkOfYfr81GvnLvlHxIg4Prw89gKuWU+kQk82lFQo6QQpqbCbJC2FleurD8tYoSY0srhuioVInffvTxw2NMF7FQEqUcsK9AMKSEiDqzBi35Um/fiE3JL4XZBFw8Xzl7X3ab5nlg8X+xD5uSZY+oxD3sDVXjLaQ5JUoys+MCm0FkUj85l0zT6rvM4QLhU1RDK1U51T9HJhh8hsFJsqL4abRzwEWG7PSi859zN4UsgyuQfmBJv/n7QAFCbrJhVBlGB1TKLZRzvgmKoxTYTG3cJFkjetLcUTwrwC9naxAQRfF4=|ufHf73IzJ707dx44w4fjkuD7tDa50OwmmkxcypAT9uQ=".to_owned(), + signing_key: None, method: InitUserCryptoMethod::Password { password: "asdfasdfasdf".to_owned(), user_key: "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".to_owned(), diff --git a/crates/bitwarden-core/src/key_management/mod.rs b/crates/bitwarden-core/src/key_management/mod.rs index dd13ab21c..efcfb86db 100644 --- a/crates/bitwarden-core/src/key_management/mod.rs +++ b/crates/bitwarden-core/src/key_management/mod.rs @@ -26,7 +26,12 @@ key_ids! { Local(&'static str), } - pub KeyIds => SymmetricKeyId, AsymmetricKeyId; + #[signing] + pub enum SigningKeyId { + UserSigningKey, + } + + pub KeyIds => SymmetricKeyId, AsymmetricKeyId, SigningKeyId; } /// This is a helper function to create a test KeyStore with a single user key. diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index aa13f3112..29ad53863 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -46,6 +46,10 @@ pub struct InitUserCryptoRequest { pub email: String, /// The user's encrypted private key pub private_key: String, + + /// The user's signing key + pub signing_key: Option, + /// The initialization method to use pub method: InitUserCryptoMethod, } @@ -596,6 +600,7 @@ mod tests { kdf_params: kdf.clone(), email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::Password { password: "asdfasdfasdf".into(), user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), @@ -615,6 +620,7 @@ mod tests { kdf_params: kdf.clone(), email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::Password { password: "123412341234".into(), user_key: new_password_response.new_key.to_string(), @@ -672,6 +678,7 @@ mod tests { }, email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::Password { password: "asdfasdfasdf".into(), user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), @@ -693,6 +700,7 @@ mod tests { }, email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::Pin { pin: "1234".into(), pin_protected_user_key: pin_key.pin_protected_user_key, @@ -735,6 +743,7 @@ mod tests { }, email: "test@bitwarden.com".into(), private_key: priv_key.to_owned(), + signing_key: None, method: InitUserCryptoMethod::Pin { pin: "1234".into(), pin_protected_user_key, diff --git a/crates/bitwarden-crypto/src/keys/key_id.rs b/crates/bitwarden-crypto/src/keys/key_id.rs index f7a692def..c86058ab9 100644 --- a/crates/bitwarden-crypto/src/keys/key_id.rs +++ b/crates/bitwarden-crypto/src/keys/key_id.rs @@ -1,6 +1,6 @@ use rand::RngCore; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, zeroize::ZeroizeOnDrop)] pub(crate) struct KeyId([u8; 24]); /// Fixed length identifiers for keys. diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 11744763e..3114315c2 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -8,10 +8,11 @@ use coset::{ use ed25519_dalek::Signer; use rand::rngs::OsRng; -use super::key_id::KeyId; +use super::{key_id::KeyId, CryptoKey}; use crate::{cose::SIGNING_NAMESPACE, error::Result, CryptoError, SigningNamespace}; #[allow(unused)] +#[derive(zeroize::ZeroizeOnDrop)] enum SigningCryptoKeyEnum { Ed25519(ed25519_dalek::SigningKey), } @@ -22,11 +23,14 @@ enum VerifyingKeyEnum { } #[allow(unused)] +#[derive(zeroize::ZeroizeOnDrop)] pub struct SigningKey { id: KeyId, inner: SigningCryptoKeyEnum, } +impl CryptoKey for SigningKey {} + #[allow(unused)] pub struct VerifyingKey { id: KeyId, @@ -173,7 +177,7 @@ impl SigningKey { pub fn to_verifying_key(&self) -> VerifyingKey { match &self.inner { SigningCryptoKeyEnum::Ed25519(key) => VerifyingKey { - id: self.id, + id: self.id.clone(), inner: VerifyingKeyEnum::Ed25519(key.verifying_key()), }, } diff --git a/crates/bitwarden-crypto/src/traits/key_id.rs b/crates/bitwarden-crypto/src/traits/key_id.rs index ba997a5b0..35d96e69b 100644 --- a/crates/bitwarden-crypto/src/traits/key_id.rs +++ b/crates/bitwarden-crypto/src/traits/key_id.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, hash::Hash}; use zeroize::ZeroizeOnDrop; -use crate::{AsymmetricCryptoKey, CryptoKey, SymmetricCryptoKey}; +use crate::{AsymmetricCryptoKey, CryptoKey, SigningKey, SymmetricCryptoKey}; /// Represents a key identifier that can be used to identify cryptographic keys in the /// key store. It is used to avoid exposing the key material directly in the public API. @@ -30,6 +30,7 @@ pub trait KeyId: pub trait KeyIds { type Symmetric: KeyId; type Asymmetric: KeyId; + type Signing: KeyId; } /// Just a small derive_like macro that can be used to generate the key identifier enums. @@ -63,7 +64,7 @@ macro_rules! key_ids { $(,)? } )+ - $ids_vis:vis $ids_name:ident => $symm_name:ident, $asymm_name:ident; + $ids_vis:vis $ids_name:ident => $symm_name:ident, $asymm_name:ident, $signing_name:ident; ) => { $( #[derive(std::fmt::Debug, Clone, Copy, std::hash::Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -88,11 +89,15 @@ macro_rules! key_ids { impl $crate::KeyIds for $ids_name { type Symmetric = $symm_name; type Asymmetric = $asymm_name; + type Signing = $signing_name; } }; ( @key_type symmetric ) => { $crate::SymmetricCryptoKey }; ( @key_type asymmetric ) => { $crate::AsymmetricCryptoKey }; + ( @key_type signing ) => { $crate::SigningKey }; + + ( @variant_match $variant:ident ( $inner:ty ) ) => { $variant ( _ ) }; ( @variant_match $variant:ident ( $inner:ty ) ) => { $variant (_) }; ( @variant_match $variant:ident ) => { $variant }; @@ -104,7 +109,7 @@ macro_rules! key_ids { #[cfg(test)] pub(crate) mod tests { use crate::{ - traits::tests::{TestAsymmKey, TestSymmKey}, + traits::tests::{TestAsymmKey, TestSigningKey, TestSymmKey}, KeyId, }; @@ -117,5 +122,9 @@ pub(crate) mod tests { assert!(!TestAsymmKey::A(0).is_local()); assert!(!TestAsymmKey::B.is_local()); assert!(TestAsymmKey::C("test").is_local()); + + assert!(!TestSigningKey::A(0).is_local()); + assert!(!TestSigningKey::B.is_local()); + assert!(TestSigningKey::C("test").is_local()); } } diff --git a/crates/bitwarden-crypto/src/traits/mod.rs b/crates/bitwarden-crypto/src/traits/mod.rs index 28b811e36..9110a7508 100644 --- a/crates/bitwarden-crypto/src/traits/mod.rs +++ b/crates/bitwarden-crypto/src/traits/mod.rs @@ -36,6 +36,14 @@ pub(crate) mod tests { C(&'static str), } - pub TestIds => TestSymmKey, TestAsymmKey; + #[signing] + pub enum TestSigningKey { + A(u8), + B, + #[local] + C(&'static str), + } + + pub TestIds => TestSymmKey, TestAsymmKey, TestSigningKey; } } From ab7570b0fc826a6d0e208f993465f3cf0254198e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 14:04:31 +0200 Subject: [PATCH 103/108] Add signing keys to global keystore and make make signing key not return result --- crates/bitwarden-core/src/mobile/crypto.rs | 2 +- crates/bitwarden-core/tests/register.rs | 2 + crates/bitwarden-crypto/src/keys/mod.rs | 2 +- .../src/keys/signing_crypto_key.rs | 20 +++---- crates/bitwarden-crypto/src/store/context.rs | 53 +++++++++++++++++-- crates/bitwarden-crypto/src/store/mod.rs | 5 ++ 6 files changed, 68 insertions(+), 16 deletions(-) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 29ad53863..74b706889 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -560,7 +560,7 @@ pub struct MakeSigningKeysResponse { pub fn make_signing_keys(wrapping_key: String) -> Result { let wrapping_key = SymmetricCryptoKey::try_from(wrapping_key)?; - let signature_keypair = SigningKey::make_ed25519_key()?; + let signature_keypair = SigningKey::make_ed25519_key(); // This needs to be changed to use the correct cose content format before rolling out to real // accounts let encrypted_signing_key = signature_keypair diff --git a/crates/bitwarden-core/tests/register.rs b/crates/bitwarden-core/tests/register.rs index 3f01b4763..c8ac264e8 100644 --- a/crates/bitwarden-core/tests/register.rs +++ b/crates/bitwarden-core/tests/register.rs @@ -33,6 +33,8 @@ async fn test_register_initialize_crypto() { email: email.to_owned(), private_key: register_response.keys.private.to_string(), + signing_key: None, + method: InitUserCryptoMethod::Password { password: password.to_owned(), user_key: register_response.encrypted_user_key, diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 1a5f3655e..95a39b33c 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -27,5 +27,5 @@ pub use kdf::{ default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, default_pbkdf2_iterations, Kdf, }; -pub use signing_crypto_key::{SigningKey, VerifyingKey}; +pub use signing_crypto_key::{Signature, SigningKey, VerifyingKey}; mod utils; diff --git a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs index 3114315c2..74c456d70 100644 --- a/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/signing_crypto_key.rs @@ -12,7 +12,7 @@ use super::{key_id::KeyId, CryptoKey}; use crate::{cose::SIGNING_NAMESPACE, error::Result, CryptoError, SigningNamespace}; #[allow(unused)] -#[derive(zeroize::ZeroizeOnDrop)] +#[derive(zeroize::ZeroizeOnDrop, Clone)] enum SigningCryptoKeyEnum { Ed25519(ed25519_dalek::SigningKey), } @@ -23,7 +23,7 @@ enum VerifyingKeyEnum { } #[allow(unused)] -#[derive(zeroize::ZeroizeOnDrop)] +#[derive(zeroize::ZeroizeOnDrop, Clone)] pub struct SigningKey { id: KeyId, inner: SigningCryptoKeyEnum, @@ -39,11 +39,11 @@ pub struct VerifyingKey { #[allow(unused)] impl SigningKey { - pub fn make_ed25519_key() -> Result { - Ok(SigningKey { + pub fn make_ed25519_key() -> Self { + SigningKey { id: KeyId::generate(), inner: SigningCryptoKeyEnum::Ed25519(ed25519_dalek::SigningKey::generate(&mut OsRng)), - }) + } } fn cose_algorithm(&self) -> Algorithm { @@ -361,7 +361,7 @@ mod tests { #[test] fn test_sign_roundtrip() { - let signing_key = SigningKey::make_ed25519_key().unwrap(); + let signing_key = SigningKey::make_ed25519_key(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; @@ -372,7 +372,7 @@ mod tests { #[test] fn test_changed_signature_fails() { - let signing_key = SigningKey::make_ed25519_key().unwrap(); + let signing_key = SigningKey::make_ed25519_key(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; @@ -383,7 +383,7 @@ mod tests { #[test] fn test_changed_namespace_fails() { - let signing_key = SigningKey::make_ed25519_key().unwrap(); + let signing_key = SigningKey::make_ed25519_key(); let verifying_key = signing_key.to_verifying_key(); let data = b"Hello, world!"; let namespace = SigningNamespace::EncryptionMetadata; @@ -395,7 +395,7 @@ mod tests { #[test] fn test_cose_roundtrip_encode_signing() { - let signing_key = SigningKey::make_ed25519_key().unwrap(); + let signing_key = SigningKey::make_ed25519_key(); let cose = signing_key.to_cose().unwrap(); let parsed_key = SigningKey::from_cose(&cose).unwrap(); @@ -407,7 +407,7 @@ mod tests { #[test] fn test_cose_roundtrip_encode_verifying() { - let signing_key = SigningKey::make_ed25519_key().unwrap(); + let signing_key = SigningKey::make_ed25519_key(); let cose = signing_key.to_verifying_key().to_cose().unwrap(); let parsed_key = VerifyingKey::from_cose(&cose).unwrap(); diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 92b89998a..16270bab0 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, CryptoError, EncString, KeyId, KeyIds, Result, SymmetricCryptoKey, - UnauthenticatedSharedKey, + AsymmetricCryptoKey, CryptoError, EncString, KeyId, KeyIds, Result, Signature, SigningKey, + SigningNamespace, SymmetricCryptoKey, UnauthenticatedSharedKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -66,6 +66,7 @@ pub struct KeyStoreContext<'a, Ids: KeyIds> { pub(super) local_symmetric_keys: Box>, pub(super) local_asymmetric_keys: Box>, + pub(super) local_signing_keys: Box>, // Make sure the context is !Send & !Sync pub(super) _phantom: std::marker::PhantomData<(Cell<()>, RwLockReadGuard<'static, ()>)>, @@ -104,6 +105,7 @@ impl KeyStoreContext<'_, Ids> { pub fn clear_local(&mut self) { self.local_symmetric_keys.clear(); self.local_asymmetric_keys.clear(); + self.local_signing_keys.clear(); } /// Remove all symmetric keys from the context for which the predicate returns false @@ -316,6 +318,15 @@ impl KeyStoreContext<'_, Ids> { .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}"))) } + fn get_signing_key(&self, key_id: Ids::Signing) -> Result<&SigningKey> { + if key_id.is_local() { + self.local_signing_keys.get(key_id) + } else { + self.global_keys.get().signing_keys.get(key_id) + } + .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}"))) + } + #[deprecated(note = "This function should ideally never be used outside this crate")] pub fn set_symmetric_key( &mut self, @@ -350,6 +361,16 @@ impl KeyStoreContext<'_, Ids> { Ok(()) } + #[deprecated(note = "This function should ideally never be used outside this crate")] + pub fn set_signing_key(&mut self, key_id: Ids::Signing, key: SigningKey) -> Result<()> { + if key_id.is_local() { + self.local_signing_keys.upsert(key_id, key); + } else { + self.global_keys.get_mut()?.signing_keys.upsert(key_id, key); + } + Ok(()) + } + pub(crate) fn decrypt_data_with_symmetric_key( &self, key: Ids::Symmetric, @@ -385,6 +406,17 @@ impl KeyStoreContext<'_, Ids> { } } } + + #[allow(dead_code)] + pub(crate) fn sign_data( + &self, + key: Ids::Signing, + namespace: SigningNamespace, + data: &[u8], + ) -> Result { + let key = self.get_signing_key(key)?; + Ok(key.sign(&namespace, data)) + } } #[cfg(test)] @@ -392,10 +424,23 @@ impl KeyStoreContext<'_, Ids> { mod tests { use crate::{ store::{tests::DataView, KeyStore}, - traits::tests::{TestIds, TestSymmKey}, - Decryptable, Encryptable, SymmetricCryptoKey, + traits::tests::{TestIds, TestSigningKey, TestSymmKey}, + Decryptable, Encryptable, SigningKey, SymmetricCryptoKey, }; + #[test] + fn test_set_signing_key() { + let store: KeyStore = KeyStore::default(); + + // Generate and insert a key + let key_a0_id = TestSigningKey::A(0); + let key_a0 = SigningKey::make_ed25519_key(); + store + .context_mut() + .set_signing_key(key_a0_id, key_a0.clone()) + .unwrap(); + } + #[test] fn test_set_keys_for_encryption() { let store: KeyStore = KeyStore::default(); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 7b4c35c79..8926b1b2e 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -101,6 +101,7 @@ impl std::fmt::Debug for KeyStore { struct KeyStoreInner { symmetric_keys: Box>, asymmetric_keys: Box>, + signing_keys: Box>, } /// Create a new key store with the best available implementation for the current platform. @@ -110,6 +111,7 @@ impl Default for KeyStore { inner: Arc::new(RwLock::new(KeyStoreInner { symmetric_keys: create_store(), asymmetric_keys: create_store(), + signing_keys: create_store(), })), } } @@ -122,6 +124,7 @@ impl KeyStore { let mut keys = self.inner.write().expect("RwLock is poisoned"); keys.symmetric_keys.clear(); keys.asymmetric_keys.clear(); + keys.signing_keys.clear(); } /// Initiate an encryption/decryption context. This context will have read only access to the @@ -160,6 +163,7 @@ impl KeyStore { global_keys: GlobalKeys::ReadOnly(self.inner.read().expect("RwLock is poisoned")), local_symmetric_keys: create_store(), local_asymmetric_keys: create_store(), + local_signing_keys: create_store(), _phantom: std::marker::PhantomData, } } @@ -189,6 +193,7 @@ impl KeyStore { global_keys: GlobalKeys::ReadWrite(self.inner.write().expect("RwLock is poisoned")), local_symmetric_keys: create_store(), local_asymmetric_keys: create_store(), + local_signing_keys: create_store(), _phantom: std::marker::PhantomData, } } From 0b0076a0172edae32e66f91ea760af4457588afc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 14:40:29 +0200 Subject: [PATCH 104/108] Add signing keys to init code --- .../api/response/identity_success_response.rs | 3 +++ crates/bitwarden-core/src/auth/auth_request.rs | 4 ++-- crates/bitwarden-core/src/auth/login/api_key.rs | 2 +- crates/bitwarden-core/src/auth/login/password.rs | 3 ++- .../bitwarden-core/src/auth/password/validate.rs | 4 ++-- crates/bitwarden-core/src/auth/pin.rs | 2 +- crates/bitwarden-core/src/auth/tde.rs | 2 +- .../src/client/encryption_settings.rs | 15 +++++++++++++-- crates/bitwarden-core/src/client/internal.rs | 9 ++++++--- crates/bitwarden-core/src/mobile/crypto.rs | 13 +++++++------ .../src/platform/generate_fingerprint.rs | 1 + 11 files changed, 39 insertions(+), 19 deletions(-) diff --git a/crates/bitwarden-core/src/auth/api/response/identity_success_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_success_response.rs index 94ebe9445..0fe7ddff2 100644 --- a/crates/bitwarden-core/src/auth/api/response/identity_success_response.rs +++ b/crates/bitwarden-core/src/auth/api/response/identity_success_response.rs @@ -15,6 +15,8 @@ pub struct IdentityTokenSuccessResponse { pub(crate) private_key: Option, #[serde(alias = "Key")] pub(crate) key: Option, + #[serde(alias = "userKeyEncryptedSigningKey")] + pub(crate) user_key_encrypted_signing_key: Option, #[serde(rename = "twoFactorToken")] two_factor_token: Option, #[serde(alias = "Kdf")] @@ -53,6 +55,7 @@ mod test { refresh_token: Default::default(), token_type: Default::default(), private_key: Default::default(), + user_key_encrypted_signing_key: Default::default(), key: Default::default(), two_factor_token: Default::default(), kdf: KdfType::default(), diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index 1d4a36c97..cf2c7e1dd 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -162,7 +162,7 @@ mod tests { 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, private_key, None) .unwrap(); let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyLRDUwXB4BfQ507D4meFPmwn5zwy3IqTPJO4plrrhnclWahXa240BzyFW9gHgYu+Jrgms5xBfRTBMcEsqqNm7+JpB6C1B6yvnik0DpJgWQw1rwvy4SUYidpR/AWbQi47n/hvnmzI/sQxGddVfvWu1iTKOlf5blbKYAXnUE5DZBGnrWfacNXwRRdtP06tFB0LwDgw+91CeLSJ9py6dm1qX5JIxoO8StJOQl65goLCdrTWlox+0Jh4xFUfCkb+s3px+OhSCzJbvG/hlrSRcUz5GnwlCEyF3v5lfUtV96MJD+78d8pmH6CfFAp2wxKRAbGdk+JccJYO6y6oIXd3Fm7twIDAQAB"; @@ -229,7 +229,7 @@ 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, private_key.parse().unwrap(), None) .unwrap(); // Initialize a new device which will request to be logged in diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index 649e448f8..0237a1729 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -53,7 +53,7 @@ pub(crate) async fn login_api_key( client .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + .initialize_user_crypto_master_key(master_key, user_key, private_key, None)?; } 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..354222312 100644 --- a/crates/bitwarden-core/src/auth/login/password.rs +++ b/crates/bitwarden-core/src/auth/login/password.rs @@ -52,10 +52,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()?; + let signing_key = r.user_key_encrypted_signing_key.clone().map(|s| s.parse()).transpose()?; client .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + .initialize_user_crypto_master_key(master_key, user_key, private_key, signing_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 39abee276..703015548 100644 --- a/crates/bitwarden-core/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -140,7 +140,7 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) .unwrap(); let result = @@ -183,7 +183,7 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) .unwrap(); let result = diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs index 93e172f25..a065a3d54 100644 --- a/crates/bitwarden-core/src/auth/pin.rs +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -75,7 +75,7 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) .unwrap(); client diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index cb9441127..e65557c6a 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -39,7 +39,7 @@ pub(super) fn make_register_tde_keys( )); client .internal - .initialize_user_crypto_decrypted_key(user_key.0, key_pair.private.clone())?; + .initialize_user_crypto_decrypted_key(user_key.0, key_pair.private.clone(), None)?; Ok(RegisterTdeKeyResponse { private_key: key_pair.private, diff --git a/crates/bitwarden-core/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs index 83cf275ec..01b5761f6 100644 --- a/crates/bitwarden-core/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -40,12 +40,13 @@ impl EncryptionSettings { pub(crate) fn new_decrypted_key( user_key: SymmetricCryptoKey, private_key: EncString, + signing_key: Option, store: &KeyStore, ) -> Result<(), EncryptionSettingsError> { - use bitwarden_crypto::{AsymmetricCryptoKey, KeyDecryptable}; + use bitwarden_crypto::{AsymmetricCryptoKey, KeyDecryptable, SigningKey}; use log::warn; - use crate::key_management::{AsymmetricKeyId, SymmetricKeyId}; + use crate::key_management::{AsymmetricKeyId, SigningKeyId, SymmetricKeyId}; let private_key = { let dec: Vec = private_key.decrypt_with_key(&user_key)?; @@ -63,6 +64,12 @@ impl EncryptionSettings { // .map_err(|_| EncryptionSettingsError::InvalidPrivateKey)?, // ) }; + let signing_key = signing_key + .map(|key| { + let dec: Vec = key.decrypt_with_key(&user_key)?; + SigningKey::from_cose(dec.as_slice()) + }) + .transpose()?; // FIXME: [PM-18098] When this is part of crypto we won't need to use deprecated methods #[allow(deprecated)] @@ -72,6 +79,10 @@ impl EncryptionSettings { if let Some(private_key) = private_key { ctx.set_asymmetric_key(AsymmetricKeyId::UserPrivateKey, private_key)?; } + + if let Some(signing_key) = signing_key { + ctx.set_signing_key(SigningKeyId::UserSigningKey, signing_key)?; + } } Ok(()) diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs index b4cd338c9..59a486468 100644 --- a/crates/bitwarden-core/src/client/internal.rs +++ b/crates/bitwarden-core/src/client/internal.rs @@ -176,9 +176,10 @@ impl InternalClient { master_key: MasterKey, user_key: EncString, private_key: EncString, + signing_key: Option, ) -> Result<(), EncryptionSettingsError> { let user_key = master_key.decrypt_user_key(user_key)?; - EncryptionSettings::new_decrypted_key(user_key, private_key, &self.key_store)?; + EncryptionSettings::new_decrypted_key(user_key, private_key, signing_key, &self.key_store)?; Ok(()) } @@ -188,8 +189,9 @@ impl InternalClient { &self, user_key: SymmetricCryptoKey, private_key: EncString, + signing_key: Option, ) -> Result<(), EncryptionSettingsError> { - EncryptionSettings::new_decrypted_key(user_key, private_key, &self.key_store)?; + EncryptionSettings::new_decrypted_key(user_key, private_key, signing_key, &self.key_store)?; Ok(()) } @@ -200,9 +202,10 @@ impl InternalClient { pin_key: PinKey, pin_protected_user_key: EncString, private_key: EncString, + signing_key: Option, ) -> Result<(), EncryptionSettingsError> { let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; - self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) + self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key, signing_key) } #[cfg(feature = "secrets")] diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 74b706889..836830887 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -143,13 +143,13 @@ pub async fn initialize_user_crypto( let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?; client .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + .initialize_user_crypto_master_key(master_key, user_key, private_key, None)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?; client .internal - .initialize_user_crypto_decrypted_key(user_key, private_key)?; + .initialize_user_crypto_decrypted_key(user_key, private_key, None)?; } InitUserCryptoMethod::Pin { pin, @@ -160,6 +160,7 @@ pub async fn initialize_user_crypto( pin_key, pin_protected_user_key, private_key, + None )?; } InitUserCryptoMethod::AuthRequest { @@ -181,7 +182,7 @@ pub async fn initialize_user_crypto( }; client .internal - .initialize_user_crypto_decrypted_key(user_key, private_key)?; + .initialize_user_crypto_decrypted_key(user_key, private_key, None)?; } InitUserCryptoMethod::DeviceKey { device_key, @@ -194,7 +195,7 @@ pub async fn initialize_user_crypto( client .internal - .initialize_user_crypto_decrypted_key(user_key, private_key)?; + .initialize_user_crypto_decrypted_key(user_key, private_key, None)?; } InitUserCryptoMethod::KeyConnector { master_key, @@ -208,7 +209,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, private_key, None)?; } } @@ -794,7 +795,7 @@ mod tests { 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, private_key, None) .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 5a5edd0b8..badea24cd 100644 --- a/crates/bitwarden-core/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden-core/src/platform/generate_fingerprint.rs @@ -108,6 +108,7 @@ mod tests { master_key, user_key.parse().unwrap(), private_key.parse().unwrap(), + None, ) .unwrap(); From 2c92b19759ed14a7a3d0e142c6558bce26378935 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 15:30:44 +0200 Subject: [PATCH 105/108] Fix comment --- crates/bitwarden-crypto/src/store/context.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 16270bab0..7c1b8552d 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -39,7 +39,11 @@ use crate::{ /// # pub enum AsymmKeyId { /// # UserPrivate, /// # } -/// # pub Ids => SymmKeyId, AsymmKeyId; +/// # . #[signing] +/// # pub enum SigningKeyId { +/// # UserSigning, +/// # } +/// # pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId; /// # } /// struct Data { /// key: EncString, From d275e17f2f259fedf47233885b6764c9d423cb5d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 15:33:27 +0200 Subject: [PATCH 106/108] Cargo fmt --- .../bitwarden-core/src/auth/auth_request.rs | 7 ++++++- .../bitwarden-core/src/auth/login/api_key.rs | 9 ++++++--- .../bitwarden-core/src/auth/login/password.rs | 15 ++++++++++---- .../src/auth/password/validate.rs | 14 +++++++++++-- crates/bitwarden-core/src/auth/pin.rs | 7 ++++++- crates/bitwarden-core/src/auth/tde.rs | 8 +++++--- crates/bitwarden-core/src/mobile/crypto.rs | 20 ++++++++++++------- 7 files changed, 59 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs index cf2c7e1dd..fd5c9108a 100644 --- a/crates/bitwarden-core/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -229,7 +229,12 @@ mod tests { existing_device .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key.parse().unwrap(), None) + .initialize_user_crypto_master_key( + master_key, + user_key, + private_key.parse().unwrap(), + None, + ) .unwrap(); // Initialize a new device which will request to be logged in diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index 0237a1729..d6d70743a 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -51,9 +51,12 @@ 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, None)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key, + private_key, + None, + )?; } 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 354222312..7c4b7fbd1 100644 --- a/crates/bitwarden-core/src/auth/login/password.rs +++ b/crates/bitwarden-core/src/auth/login/password.rs @@ -52,11 +52,18 @@ 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()?; - let signing_key = r.user_key_encrypted_signing_key.clone().map(|s| s.parse()).transpose()?; + let signing_key = r + .user_key_encrypted_signing_key + .clone() + .map(|s| s.parse()) + .transpose()?; - client - .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key, signing_key)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key, + private_key, + signing_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 703015548..229c7fde7 100644 --- a/crates/bitwarden-core/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -140,7 +140,12 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) + .initialize_user_crypto_master_key( + master_key, + user_key.parse().unwrap(), + private_key, + None, + ) .unwrap(); let result = @@ -183,7 +188,12 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) + .initialize_user_crypto_master_key( + master_key, + user_key.parse().unwrap(), + private_key, + None, + ) .unwrap(); let result = diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs index a065a3d54..c337f9327 100644 --- a/crates/bitwarden-core/src/auth/pin.rs +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -75,7 +75,12 @@ mod tests { client .internal - .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key, None) + .initialize_user_crypto_master_key( + master_key, + user_key.parse().unwrap(), + private_key, + None, + ) .unwrap(); client diff --git a/crates/bitwarden-core/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs index e65557c6a..160befcc4 100644 --- a/crates/bitwarden-core/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -37,9 +37,11 @@ pub(super) fn make_register_tde_keys( kdf: Kdf::default(), }, )); - client - .internal - .initialize_user_crypto_decrypted_key(user_key.0, key_pair.private.clone(), None)?; + client.internal.initialize_user_crypto_decrypted_key( + user_key.0, + key_pair.private.clone(), + None, + )?; Ok(RegisterTdeKeyResponse { private_key: key_pair.private, diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 836830887..c2d671037 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -141,9 +141,12 @@ pub async fn initialize_user_crypto( let user_key: EncString = user_key.parse()?; let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?; - client - .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key, None)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key, + private_key, + None, + )?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?; @@ -160,7 +163,7 @@ pub async fn initialize_user_crypto( pin_key, pin_protected_user_key, private_key, - None + None, )?; } InitUserCryptoMethod::AuthRequest { @@ -207,9 +210,12 @@ 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, private_key, None)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key, + private_key, + None, + )?; } } From 17dfbd81a7dc916c4211641eb1df35fc2f6d1415 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 15:49:50 +0200 Subject: [PATCH 107/108] Fix documentation --- crates/bitwarden-crypto/src/store/context.rs | 2 +- crates/bitwarden-crypto/src/store/mod.rs | 6 +++++- crates/bitwarden-crypto/src/traits/key_id.rs | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 7c1b8552d..8c5ad26f2 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -39,7 +39,7 @@ use crate::{ /// # pub enum AsymmKeyId { /// # UserPrivate, /// # } -/// # . #[signing] +/// # #[signing] /// # pub enum SigningKeyId { /// # UserSigning, /// # } diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index 8926b1b2e..bc2e32eca 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -58,7 +58,11 @@ pub use context::KeyStoreContext; /// pub enum AsymmKeyId { /// UserPrivate, /// } -/// pub Ids => SymmKeyId, AsymmKeyId; +/// #[signing] +/// pub enum SigningKeyId { +/// UserSigning, +/// } +/// pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId; /// } /// /// // Initialize the store and insert a test key diff --git a/crates/bitwarden-crypto/src/traits/key_id.rs b/crates/bitwarden-crypto/src/traits/key_id.rs index 35d96e69b..e520016d1 100644 --- a/crates/bitwarden-crypto/src/traits/key_id.rs +++ b/crates/bitwarden-crypto/src/traits/key_id.rs @@ -50,7 +50,13 @@ pub trait KeyIds { /// pub enum AsymmKeyId { /// PrivateKey, /// } -/// pub Ids => SymmKeyId, AsymmKeyId; +/// +/// #[signing] +/// pub enum SigningKeyId { +/// SigningKey, +/// } +/// +/// pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId; /// } #[macro_export] macro_rules! key_ids { From ac447696a40599c4f04a60685864b5d38beb2eca Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 30 Apr 2025 15:52:44 +0200 Subject: [PATCH 108/108] Cargo fmt --- crates/bitwarden-crypto/src/traits/key_id.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-crypto/src/traits/key_id.rs b/crates/bitwarden-crypto/src/traits/key_id.rs index e520016d1..854149424 100644 --- a/crates/bitwarden-crypto/src/traits/key_id.rs +++ b/crates/bitwarden-crypto/src/traits/key_id.rs @@ -50,12 +50,12 @@ pub trait KeyIds { /// pub enum AsymmKeyId { /// PrivateKey, /// } -/// +/// /// #[signing] /// pub enum SigningKeyId { /// SigningKey, /// } -/// +/// /// pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId; /// } #[macro_export]