diff --git a/bitwarden_license/bitwarden-sm/src/projects/create.rs b/bitwarden_license/bitwarden-sm/src/projects/create.rs index e7cb488ac..aafc4fb7a 100644 --- a/bitwarden_license/bitwarden-sm/src/projects/create.rs +++ b/bitwarden_license/bitwarden-sm/src/projects/create.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::ProjectCreateRequestModel; use bitwarden_core::{key_management::SymmetricKeyId, Client}; -use bitwarden_crypto::Encryptable; +use bitwarden_crypto::{ContentFormat, Encryptable}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -34,7 +34,7 @@ pub(crate) async fn create_project( .name .clone() .trim() - .encrypt(&mut key_store.context(), key)? + .encrypt(&mut key_store.context(), key, ContentFormat::Utf8)? .to_string(), }); diff --git a/bitwarden_license/bitwarden-sm/src/projects/update.rs b/bitwarden_license/bitwarden-sm/src/projects/update.rs index b1da9aade..ae79c670f 100644 --- a/bitwarden_license/bitwarden-sm/src/projects/update.rs +++ b/bitwarden_license/bitwarden-sm/src/projects/update.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::ProjectUpdateRequestModel; use bitwarden_core::{key_management::SymmetricKeyId, Client}; -use bitwarden_crypto::Encryptable; +use bitwarden_crypto::{ContentFormat, Encryptable}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -36,7 +36,7 @@ pub(crate) async fn update_project( .name .clone() .trim() - .encrypt(&mut key_store.context(), key)? + .encrypt(&mut key_store.context(), key, ContentFormat::Utf8)? .to_string(), }); diff --git a/bitwarden_license/bitwarden-sm/src/secrets/create.rs b/bitwarden_license/bitwarden-sm/src/secrets/create.rs index bd3c96215..75e8837b9 100644 --- a/bitwarden_license/bitwarden-sm/src/secrets/create.rs +++ b/bitwarden_license/bitwarden-sm/src/secrets/create.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::SecretCreateRequestModel; use bitwarden_core::{key_management::SymmetricKeyId, Client}; -use bitwarden_crypto::Encryptable; +use bitwarden_crypto::{ContentFormat, Encryptable}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -40,13 +40,22 @@ pub(crate) async fn create_secret( let secret = { let mut ctx = key_store.context(); Some(SecretCreateRequestModel { - key: input.key.clone().trim().encrypt(&mut ctx, key)?.to_string(), - value: input.value.clone().encrypt(&mut ctx, key)?.to_string(), + key: input + .key + .clone() + .trim() + .encrypt(&mut ctx, key, ContentFormat::Utf8)? + .to_string(), + value: input + .value + .clone() + .encrypt(&mut ctx, key, ContentFormat::Utf8)? + .to_string(), note: input .note .clone() .trim() - .encrypt(&mut ctx, key)? + .encrypt(&mut ctx, key, ContentFormat::Utf8)? .to_string(), project_ids: input.project_ids.clone(), access_policies_requests: None, diff --git a/bitwarden_license/bitwarden-sm/src/secrets/update.rs b/bitwarden_license/bitwarden-sm/src/secrets/update.rs index 462320d62..03151e8ca 100644 --- a/bitwarden_license/bitwarden-sm/src/secrets/update.rs +++ b/bitwarden_license/bitwarden-sm/src/secrets/update.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::SecretUpdateRequestModel; use bitwarden_core::{key_management::SymmetricKeyId, Client}; -use bitwarden_crypto::Encryptable; +use bitwarden_crypto::{ContentFormat, Encryptable}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -39,13 +39,22 @@ pub(crate) async fn update_secret( let secret = { let mut ctx = key_store.context(); Some(SecretUpdateRequestModel { - key: input.key.clone().trim().encrypt(&mut ctx, key)?.to_string(), - value: input.value.clone().encrypt(&mut ctx, key)?.to_string(), + key: input + .key + .clone() + .trim() + .encrypt(&mut ctx, key, ContentFormat::Utf8)? + .to_string(), + value: input + .value + .clone() + .encrypt(&mut ctx, key, ContentFormat::Utf8)? + .to_string(), note: input .note .clone() .trim() - .encrypt(&mut ctx, key)? + .encrypt(&mut ctx, key, ContentFormat::Utf8)? .to_string(), project_ids: input.project_ids.clone(), access_policies_requests: None, diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 6e2905ce8..83fcb0b06 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -33,7 +33,7 @@ pub enum MobileCryptoError { Crypto(#[from] bitwarden_crypto::CryptoError), } -/// State used for initializing the user cryptographic state. +/// thState used for initializing the user cryptographic state. #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -329,7 +329,7 @@ pub(super) fn derive_pin_key( Ok(DerivePinKeyResponse { pin_protected_user_key, - encrypted_pin: pin.encrypt_with_key(user_key)?, + encrypted_pin: pin.encrypt_with_key(user_key, &bitwarden_crypto::ContentFormat::Utf8)?, }) } @@ -860,7 +860,7 @@ mod tests { let invalid_private_key = "bad_key" .to_string() .into_bytes() - .encrypt_with_key(&user_key.0) + .encrypt_with_key(&user_key.0, &bitwarden_crypto::ContentFormat::Utf8) .unwrap(); let request = VerifyAsymmetricKeysRequest { diff --git a/crates/bitwarden-core/src/secrets_manager/state.rs b/crates/bitwarden-core/src/secrets_manager/state.rs index 39e25ab78..45b298403 100644 --- a/crates/bitwarden-core/src/secrets_manager/state.rs +++ b/crates/bitwarden-core/src/secrets_manager/state.rs @@ -5,7 +5,7 @@ use std::{fmt::Debug, path::Path}; -use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable}; +use bitwarden_crypto::{ContentFormat, EncString, KeyDecryptable, KeyEncryptable}; use serde::{Deserialize, Serialize}; use crate::auth::AccessToken; @@ -73,7 +73,7 @@ pub(crate) fn set( ) -> Result<(), StateFileError> { let serialized_state: String = serde_json::to_string(&state)?; let encrypted_state: EncString = - serialized_state.encrypt_with_key(&access_token.encryption_key)?; + serialized_state.encrypt_with_key(&access_token.encryption_key, &ContentFormat::Utf8)?; let state_string: String = encrypted_state.to_string(); Ok(std::fs::write(state_file, state_string)?) diff --git a/crates/bitwarden-crypto/README.md b/crates/bitwarden-crypto/README.md index c568f475c..fe89f72e4 100644 --- a/crates/bitwarden-crypto/README.md +++ b/crates/bitwarden-crypto/README.md @@ -13,13 +13,13 @@ secure. ## Example: ```rust -use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; +use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError, ContentFormat}; async fn example() -> Result<(), CryptoError> { let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); let data = "Hello, World!".to_owned(); - let encrypted = data.clone().encrypt_with_key(&key)?; + let encrypted = data.clone().encrypt_with_key(&key, &ContentFormat::Utf8)?; let decrypted: String = encrypted.decrypt_with_key(&key)?; assert_eq!(data, decrypted); diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index c98a9f89d..58f565804 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -3,8 +3,14 @@ //! unless there is a a clear benefit, such as a clear cryptographic benefit, which MUST //! be documented publicly. -use coset::{iana, CborSerializable, Label}; +use coset::{ + iana::{self, CoapContentFormat}, + CborSerializable, ContentType, Label, +}; use generic_array::GenericArray; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "wasm")] +use tsify_next::Tsify; use typenum::U32; use crate::{ @@ -15,22 +21,59 @@ use crate::{ /// 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; +const XCHACHA20_TEXT_PAD_BLOCK_SIZE: usize = 32; +const CONTENT_TYPE_PADDED_UTF8: &str = "application/utf8-padded"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub enum ContentFormat { + Utf8, + Pkcs8, + CoseKey, + OctetStream, + /// Domain object should never be serialized. It is used to indicate when we call an encrypt + /// operation on a complex object that consists of multiple, individually encrypted fields + DomainObject, +} /// Encrypts a plaintext message using XChaCha20Poly1305 and returns a COSE Encrypt0 message pub(crate) fn encrypt_xchacha20_poly1305( plaintext: &[u8], key: &crate::XChaCha20Poly1305Key, + content_format: &ContentFormat, ) -> Result, CryptoError> { - let mut protected_header = coset::HeaderBuilder::new().build(); + let protected_header = coset::HeaderBuilder::new(); + let protected_header = match content_format { + // UTF-8 directly would leak the plaintext size. This is not acceptable for certain data + // (passwords). + ContentFormat::Utf8 => protected_header.content_type(CONTENT_TYPE_PADDED_UTF8.to_string()), + ContentFormat::Pkcs8 => protected_header.content_format(CoapContentFormat::Pkcs8), + ContentFormat::CoseKey => protected_header.content_format(CoapContentFormat::CoseKey), + ContentFormat::OctetStream => { + protected_header.content_format(CoapContentFormat::OctetStream) + } + // This should panic, and should never be implemented to be reachable! + ContentFormat::DomainObject => unreachable!(), + }; + let mut protected_header = protected_header.build(); // This should be adjusted to use the builder pattern once implemented in coset. // The related coset upstream issue is: // https://github.com/google/coset/issues/105 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305)); + let encoded_plaintext = if *content_format == ContentFormat::Utf8 { + // Pad the data to a block size in order to hide plaintext length + let mut plaintext = plaintext.to_vec(); + crate::keys::utils::pad_bytes(&mut plaintext, XCHACHA20_TEXT_PAD_BLOCK_SIZE); + plaintext + } else { + plaintext.to_vec() + }; + let mut nonce = [0u8; xchacha20::NONCE_SIZE]; let cose_encrypt0 = coset::CoseEncrypt0Builder::new() .protected(protected_header) - .create_ciphertext(plaintext, &[], |data, aad| { + .create_ciphertext(&encoded_plaintext, &[], |data, aad| { let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); nonce = ciphertext.nonce(); @@ -71,6 +114,13 @@ pub(crate) fn decrypt_xchacha20_poly1305( aad, ) })?; + + if let Some(ref content_type) = msg.protected.header.content_type { + if *content_type == ContentType::Text(CONTENT_TYPE_PADDED_UTF8.to_string()) { + // Unpad the data to get the original plaintext + return crate::keys::utils::unpad_bytes(&decrypted_message).map(|bytes| bytes.to_vec()); + } + } Ok(decrypted_message) } @@ -125,7 +175,8 @@ mod test { }; let plaintext = b"Hello, world!"; - let encrypted = encrypt_xchacha20_poly1305(plaintext, key).unwrap(); + let encrypted = + encrypt_xchacha20_poly1305(plaintext, key, &ContentFormat::OctetStream).unwrap(); let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); assert_eq!(decrypted, plaintext); } diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 33d287e58..7d06a7dbd 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -7,7 +7,8 @@ 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, XChaCha20Poly1305Key, + Aes256CbcHmacKey, ContentFormat, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + XChaCha20Poly1305Key, }; #[cfg(feature = "wasm")] @@ -258,8 +259,9 @@ impl EncString { pub(crate) fn encrypt_xchacha20_poly1305( data_dec: &[u8], key: &XChaCha20Poly1305Key, + content_format: &ContentFormat, ) -> Result { - let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key)?; + let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key, content_format)?; Ok(EncString::Cose_Encrypt0_B64 { data }) } @@ -274,11 +276,15 @@ impl EncString { } impl KeyEncryptable for &[u8] { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key( + self, + key: &SymmetricCryptoKey, + content_format: &ContentFormat, + ) -> Result { match key { SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key), SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => { - EncString::encrypt_xchacha20_poly1305(self, inner_key) + EncString::encrypt_xchacha20_poly1305(self, inner_key, content_format) } SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, @@ -311,14 +317,22 @@ impl KeyDecryptable> for EncString { } impl KeyEncryptable for String { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { - self.as_bytes().encrypt_with_key(key) + fn encrypt_with_key( + self, + key: &SymmetricCryptoKey, + content_format: &ContentFormat, + ) -> Result { + self.as_bytes().encrypt_with_key(key, content_format) } } impl KeyEncryptable for &str { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { - self.as_bytes().encrypt_with_key(key) + fn encrypt_with_key( + self, + key: &SymmetricCryptoKey, + content_format: &ContentFormat, + ) -> Result { + self.as_bytes().encrypt_with_key(key, content_format) } } @@ -347,8 +361,8 @@ mod tests { use super::EncString; use crate::{ - derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, - KEY_ID_SIZE, + derive_symmetric_key, ContentFormat, CryptoError, KeyDecryptable, KeyEncryptable, + SymmetricCryptoKey, KEY_ID_SIZE, }; #[test] @@ -361,7 +375,10 @@ mod tests { }); let test_string = "encrypted_test_string"; - let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap(); + let cipher = test_string + .to_owned() + .encrypt_with_key(&key, &ContentFormat::Utf8) + .unwrap(); let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); assert_eq!(decrypted_str, test_string); } @@ -371,7 +388,10 @@ mod tests { let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test")); let test_string = "encrypted_test_string"; - let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap(); + let cipher = test_string + .to_string() + .encrypt_with_key(&key, &ContentFormat::Utf8) + .unwrap(); let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); assert_eq!(decrypted_str, test_string); @@ -381,8 +401,11 @@ mod tests { fn test_enc_string_ref_roundtrip() { let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test")); - let test_string = "encrypted_test_string"; - let cipher = test_string.encrypt_with_key(&key).unwrap(); + let test_string: &'static str = "encrypted_test_string"; + let cipher = test_string + .to_string() + .encrypt_with_key(&key, &ContentFormat::Utf8) + .unwrap(); let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); assert_eq!(decrypted_str, test_string); diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index d2604b123..0e9b617ad 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -56,6 +56,9 @@ pub enum CryptoError { #[error("Invalid nonce length")] InvalidNonceLength, + + #[error("Invalid padding")] + InvalidPadding, } #[derive(Debug, Error)] diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index 6574ea91b..8d5456e87 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -39,11 +39,11 @@ impl DeviceKey { let protected_device_public_key = device_private_key .to_public_der()? - .encrypt_with_key(user_key)?; + .encrypt_with_key(user_key, &crate::cose::ContentFormat::OctetStream)?; let protected_device_private_key = device_private_key .to_der()? - .encrypt_with_key(&device_key.0)?; + .encrypt_with_key(&device_key.0, &crate::cose::ContentFormat::Pkcs8)?; Ok(TrustDeviceResponse { device_key: device_key.to_base64(), diff --git a/crates/bitwarden-crypto/src/keys/key_encryptable.rs b/crates/bitwarden-crypto/src/keys/key_encryptable.rs index 7ddf689d8..63b24f070 100644 --- a/crates/bitwarden-crypto/src/keys/key_encryptable.rs +++ b/crates/bitwarden-crypto/src/keys/key_encryptable.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, hash::Hash, sync::Arc}; use rayon::prelude::*; use uuid::Uuid; -use crate::{error::Result, CryptoError, SymmetricCryptoKey}; +use crate::{cose::ContentFormat, error::Result, CryptoError, SymmetricCryptoKey}; pub trait KeyContainer: Send + Sync { fn get_key(&self, org_id: &Option) -> Result<&SymmetricCryptoKey, CryptoError>; @@ -18,7 +18,7 @@ impl KeyContainer for Arc { pub trait CryptoKey {} pub trait KeyEncryptable { - fn encrypt_with_key(self, key: &Key) -> Result; + fn encrypt_with_key(self, key: &Key, content_format: &ContentFormat) -> Result; } pub trait KeyDecryptable { @@ -28,8 +28,9 @@ pub trait KeyDecryptable { impl, Key: CryptoKey, Output> KeyEncryptable> for Option { - fn encrypt_with_key(self, key: &Key) -> Result> { - self.map(|e| e.encrypt_with_key(key)).transpose() + fn encrypt_with_key(self, key: &Key, content_format: &ContentFormat) -> Result> { + self.map(|e| e.encrypt_with_key(key, content_format)) + .transpose() } } @@ -44,8 +45,8 @@ impl, Key: CryptoKey, Output> KeyDecryptable, Key: CryptoKey, Output> KeyEncryptable for Box { - fn encrypt_with_key(self, key: &Key) -> Result { - (*self).encrypt_with_key(key) + fn encrypt_with_key(self, key: &Key, content_format: &ContentFormat) -> Result { + (*self).encrypt_with_key(key, content_format) } } @@ -63,9 +64,9 @@ impl< Output: Send + Sync, > KeyEncryptable> for Vec { - fn encrypt_with_key(self, key: &Key) -> Result> { + fn encrypt_with_key(self, key: &Key, content_format: &ContentFormat) -> Result> { self.into_par_iter() - .map(|e| e.encrypt_with_key(key)) + .map(|e| e.encrypt_with_key(key, content_format)) .collect() } } @@ -90,9 +91,13 @@ impl< Id: Hash + Eq + Send + Sync, > KeyEncryptable> for HashMap { - fn encrypt_with_key(self, key: &Key) -> Result> { + fn encrypt_with_key( + self, + key: &Key, + content_format: &ContentFormat, + ) -> Result> { self.into_par_iter() - .map(|(id, e)| Ok((id, e.encrypt_with_key(key)?))) + .map(|(id, e)| Ok((id, e.encrypt_with_key(key, content_format)?))) .collect() } } diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index efdbc52d4..3841ccc47 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -28,4 +28,4 @@ pub use kdf::{ }; #[cfg(test)] pub(crate) use key_id::KEY_ID_SIZE; -mod utils; +pub(crate) mod utils; diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index e069a4c62..c8421e244 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -4,7 +4,8 @@ use super::{ utils::stretch_key, }; use crate::{ - keys::key_encryptable::CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey, + keys::key_encryptable::CryptoKey, ContentFormat, EncString, KeyEncryptable, Result, + SymmetricCryptoKey, }; /// Pin Key. @@ -32,14 +33,14 @@ impl PinKey { impl CryptoKey for PinKey {} impl KeyEncryptable for &[u8] { - fn encrypt_with_key(self, key: &PinKey) -> Result { + fn encrypt_with_key(self, key: &PinKey, content_format: &ContentFormat) -> Result { let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&key.0 .0)?); - self.encrypt_with_key(&stretched_key) + self.encrypt_with_key(&stretched_key, content_format) } } impl KeyEncryptable for String { - fn encrypt_with_key(self, key: &PinKey) -> Result { - self.as_bytes().encrypt_with_key(key) + fn encrypt_with_key(self, key: &PinKey, content_format: &ContentFormat) -> Result { + self.as_bytes().encrypt_with_key(key, content_format) } } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 91e001290..8d208405a 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::{cmp::max, pin::Pin}; +use std::pin::Pin; use base64::{engine::general_purpose::STANDARD, Engine}; use coset::{iana::KeyOperation, CborSerializable, RegisteredLabelWithPrivate}; @@ -347,10 +347,7 @@ impl std::fmt::Debug for XChaCha20Poly1305Key { /// 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); - let padded_length = max(min_length, key_bytes.len() + 1); - key_bytes.resize(padded_length, pad_bytes as u8); + crate::keys::utils::pad_bytes(key_bytes, min_length); } /// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key]. @@ -364,11 +361,7 @@ fn pad_key(key_bytes: &mut Vec, min_length: usize) { /// 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]) -> Result<&[u8], CryptoError> { - let pad_len = *key_bytes.last().ok_or(CryptoError::InvalidKey)? as usize; - if pad_len >= key_bytes.len() { - return Err(CryptoError::InvalidKey); - } - Ok(key_bytes[..key_bytes.len() - pad_len].as_ref()) + crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey) } #[cfg(test)] diff --git a/crates/bitwarden-crypto/src/keys/utils.rs b/crates/bitwarden-crypto/src/keys/utils.rs index 21edd60af..35f39a1d8 100644 --- a/crates/bitwarden-crypto/src/keys/utils.rs +++ b/crates/bitwarden-crypto/src/keys/utils.rs @@ -1,10 +1,10 @@ -use std::pin::Pin; +use std::{cmp::max, pin::Pin}; use generic_array::GenericArray; use typenum::U32; use super::Aes256CbcHmacKey; -use crate::{util::hkdf_expand, Result}; +use crate::{util::hkdf_expand, CryptoError, Result}; /// Stretch the given key using HKDF. /// This can be either a kdf-derived key (PIN/Master password) or @@ -16,6 +16,27 @@ pub(super) fn stretch_key(key: &Pin>>) -> Result, min_length: usize) { + // at least 1 byte of padding is required + let pad_bytes = min_length.saturating_sub(bytes.len()).max(1); + let padded_length = max(min_length, bytes.len() + 1); + bytes.resize(padded_length, pad_bytes as u8); +} + +/// Unpads bytes that is padded using the PKCS7-like padding defined by [pad_bytes]. +/// 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. +pub(crate) fn unpad_bytes(padded_bytes: &[u8]) -> Result<&[u8], CryptoError> { + let pad_len = *padded_bytes.last().ok_or(CryptoError::InvalidPadding)? as usize; + if pad_len >= padded_bytes.len() { + return Err(CryptoError::InvalidPadding); + } + Ok(padded_bytes[..padded_bytes.len() - pad_len].as_ref()) +} + #[cfg(test)] mod tests { use super::*; @@ -46,4 +67,17 @@ mod tests { stretched.mac_key.as_slice() ); } + + #[test] + fn test_pad_bytes_roundtrip() { + let original_bytes = vec![1u8; 10]; + let mut cloned_bytes = original_bytes.clone(); + let mut encoded_bytes = vec![1u8; 12]; + encoded_bytes[10] = 2; + encoded_bytes[11] = 2; + pad_bytes(&mut cloned_bytes, 12); + assert_eq!(encoded_bytes, cloned_bytes); + let unpadded_bytes = unpad_bytes(&cloned_bytes).unwrap(); + assert_eq!(original_bytes, unpadded_bytes); + } } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index d3a0e304d..33ca433ed 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -32,6 +32,7 @@ mod store; pub use store::{KeyStore, KeyStoreContext}; mod cose; mod traits; +pub use cose::ContentFormat; 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 261067af8..3804b977c 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, - UnsignedSharedKey, + AsymmetricCryptoKey, ContentFormat, CryptoError, EncString, KeyId, KeyIds, Result, + SymmetricCryptoKey, UnsignedSharedKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -54,9 +54,9 @@ use crate::{ /// const LOCAL_KEY: SymmKeyId = SymmKeyId::Local("local_key_id"); /// /// impl Encryptable for Data { -/// fn encrypt(&self, ctx: &mut KeyStoreContext, key: SymmKeyId) -> Result { +/// fn encrypt(&self, ctx: &mut KeyStoreContext, key: SymmKeyId, content_format: ContentFormat) -> Result { /// let local_key_id = ctx.unwrap_symmetric_key(key, LOCAL_KEY, &self.key)?; -/// self.name.encrypt(ctx, local_key_id) +/// self.name.encrypt(ctx, local_key_id, content_format) /// } /// } /// ``` @@ -182,6 +182,7 @@ impl KeyStoreContext<'_, Ids> { .encrypt_data_with_symmetric_key( wrapping_key, key_to_wrap_instance.to_encoded().as_slice(), + ContentFormat::OctetStream, ), _ => Err(CryptoError::OperationNotSupported( UnsupportedOperation::EncryptionNotImplementedForKey, @@ -362,6 +363,7 @@ impl KeyStoreContext<'_, Ids> { &self, key: Ids::Symmetric, data: &[u8], + content_format: ContentFormat, ) -> Result { let key = self.get_symmetric_key(key)?; match key { @@ -370,7 +372,7 @@ impl KeyStoreContext<'_, Ids> { )), SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key), SymmetricCryptoKey::XChaCha20Poly1305Key(key) => { - EncString::encrypt_xchacha20_poly1305(data, key) + EncString::encrypt_xchacha20_poly1305(data, key, &content_format) } } } @@ -380,7 +382,11 @@ impl KeyStoreContext<'_, Ids> { #[allow(deprecated)] mod tests { use crate::{ - store::{tests::DataView, KeyStore}, + cose::ContentFormat, + store::{ + tests::{Data, DataView}, + KeyStore, + }, traits::tests::{TestIds, TestSymmKey}, Decryptable, Encryptable, SymmetricCryptoKey, }; @@ -402,7 +408,9 @@ mod tests { // Encrypt some data with the key let data = DataView("Hello, World!".to_string(), key_a0_id); - let _encrypted = data.encrypt(&mut store.context(), key_a0_id).unwrap(); + let _encrypted: Data = data + .encrypt(&mut store.context(), key_a0_id, ContentFormat::DomainObject) + .unwrap(); } #[test] @@ -440,7 +448,9 @@ mod tests { // with one and decrypt with the other let data = DataView("Hello, World!".to_string(), key_2_id); - let encrypted = data.encrypt(&mut ctx, key_2_id).unwrap(); + let encrypted = data + .encrypt(&mut ctx, key_2_id, ContentFormat::OctetStream) + .unwrap(); let decrypted1 = encrypted.decrypt(&mut ctx, key_2_id).unwrap(); let decrypted2 = encrypted.decrypt(&mut ctx, new_key_id).unwrap(); diff --git a/crates/bitwarden-crypto/src/store/mod.rs b/crates/bitwarden-crypto/src/store/mod.rs index f447f58b2..d9a6b6c7d 100644 --- a/crates/bitwarden-crypto/src/store/mod.rs +++ b/crates/bitwarden-crypto/src/store/mod.rs @@ -26,7 +26,7 @@ use std::sync::{Arc, RwLock}; use rayon::prelude::*; -use crate::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; +use crate::{cose::ContentFormat, Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds}; mod backend; mod context; @@ -75,8 +75,8 @@ pub use context::KeyStoreContext; /// } /// } /// impl Encryptable for Data { -/// fn encrypt(&self, ctx: &mut KeyStoreContext, key: SymmKeyId) -> Result { -/// self.0.encrypt(ctx, key) +/// fn encrypt(&self, ctx: &mut KeyStoreContext, key: SymmKeyId, content_format: ContentFormat) -> Result { +/// self.0.encrypt(ctx, key, content_format) /// } /// } /// @@ -214,7 +214,7 @@ impl KeyStore { data: Data, ) -> Result { let key = data.key_identifier(); - data.encrypt(&mut self.context(), key) + data.encrypt(&mut self.context(), key, ContentFormat::OctetStream) } /// Decrypt a list of items using this key store. The keys returned by @@ -272,7 +272,7 @@ impl KeyStore { for item in chunk { let key = item.key_identifier(); - result.push(item.encrypt(&mut ctx, key)); + result.push(item.encrypt(&mut ctx, key, ContentFormat::DomainObject)); ctx.clear_local(); } @@ -329,8 +329,9 @@ pub(crate) mod tests { &self, ctx: &mut KeyStoreContext, key: TestSymmKey, + _content_format: crate::cose::ContentFormat, ) -> Result { - Ok(Data(self.0.encrypt(ctx, key)?, key)) + Ok(Data(self.0.encrypt(ctx, key, _content_format)?, key)) } } diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/traits/encryptable.rs index 84d4b7bdc..259015ed8 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/traits/encryptable.rs @@ -4,7 +4,12 @@ use crate::{store::KeyStoreContext, CryptoError, EncString, KeyId, KeyIds}; /// Implementations should generally consist of calling [Encryptable::encrypt] for all the fields of /// the type. pub trait Encryptable { - fn encrypt(&self, ctx: &mut KeyStoreContext, key: Key) -> Result; + fn encrypt( + &self, + ctx: &mut KeyStoreContext, + key: Key, + content_format: crate::cose::ContentFormat, + ) -> Result; } impl Encryptable for &[u8] { @@ -12,8 +17,9 @@ impl Encryptable for &[u8] { &self, ctx: &mut KeyStoreContext, key: Ids::Symmetric, + content_format: crate::cose::ContentFormat, ) -> Result { - ctx.encrypt_data_with_symmetric_key(key, self) + ctx.encrypt_data_with_symmetric_key(key, self, content_format) } } @@ -22,8 +28,9 @@ impl Encryptable for Vec { &self, ctx: &mut KeyStoreContext, key: Ids::Symmetric, + content_format: crate::cose::ContentFormat, ) -> Result { - ctx.encrypt_data_with_symmetric_key(key, self) + ctx.encrypt_data_with_symmetric_key(key, self, content_format) } } @@ -32,8 +39,9 @@ impl Encryptable for &str { &self, ctx: &mut KeyStoreContext, key: Ids::Symmetric, + content_format: crate::cose::ContentFormat, ) -> Result { - self.as_bytes().encrypt(ctx, key) + self.as_bytes().encrypt(ctx, key, content_format) } } @@ -42,8 +50,9 @@ impl Encryptable for String { &self, ctx: &mut KeyStoreContext, key: Ids::Symmetric, + content_format: crate::cose::ContentFormat, ) -> Result { - self.as_bytes().encrypt(ctx, key) + self.as_bytes().encrypt(ctx, key, content_format) } } @@ -54,9 +63,10 @@ impl, Output> &self, ctx: &mut KeyStoreContext, key: Key, + content_format: crate::cose::ContentFormat, ) -> Result, CryptoError> { self.as_ref() - .map(|value| value.encrypt(ctx, key)) + .map(|value| value.encrypt(ctx, key, content_format)) .transpose() } } @@ -68,16 +78,19 @@ impl, Output> &self, ctx: &mut KeyStoreContext, key: Key, + content_format: crate::cose::ContentFormat, ) -> Result, CryptoError> { - self.iter().map(|value| value.encrypt(ctx, key)).collect() + self.iter() + .map(|value| value.encrypt(ctx, key, content_format)) + .collect() } } #[cfg(test)] mod tests { use crate::{ - traits::tests::*, AsymmetricCryptoKey, Decryptable, Encryptable, KeyStore, - SymmetricCryptoKey, + cose::ContentFormat, traits::tests::*, AsymmetricCryptoKey, Decryptable, Encryptable, + KeyStore, SymmetricCryptoKey, }; fn test_store() -> KeyStore { @@ -109,8 +122,12 @@ mod tests { 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_encrypted = vec_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); + let slice_encrypted = slice_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); let vec_decrypted: Vec = vec_encrypted.decrypt(&mut ctx, key).unwrap(); let slice_decrypted: Vec = slice_encrypted.decrypt(&mut ctx, key).unwrap(); @@ -128,8 +145,12 @@ mod tests { 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_encrypted = string_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); + let str_encrypted = str_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); let string_decrypted: String = string_encrypted.decrypt(&mut ctx, key).unwrap(); let str_decrypted: String = str_encrypted.decrypt(&mut ctx, key).unwrap(); @@ -146,7 +167,9 @@ mod tests { let string_data = Some("Hello, World!".to_string()); - let string_encrypted = string_data.encrypt(&mut ctx, key).unwrap(); + let string_encrypted = string_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); let string_decrypted: Option = string_encrypted.decrypt(&mut ctx, key).unwrap(); @@ -160,13 +183,17 @@ mod tests { let key = TestSymmKey::A(0); let none_data: Option = None; - let string_encrypted = none_data.encrypt(&mut ctx, key).unwrap(); + let string_encrypted = none_data + .encrypt(&mut ctx, key, ContentFormat::OctetStream) + .unwrap(); assert_eq!(string_encrypted, None); // The None implementation will not do any decrypt operations, so it won't fail even if the // key doesn't exist let bad_key = TestSymmKey::B((0, 1)); - let string_encrypted_bad = none_data.encrypt(&mut ctx, bad_key).unwrap(); + let string_encrypted_bad = none_data + .encrypt(&mut ctx, bad_key, ContentFormat::OctetStream) + .unwrap(); assert_eq!(string_encrypted_bad, None); } } diff --git a/crates/bitwarden-exporters/src/encrypted_json.rs b/crates/bitwarden-exporters/src/encrypted_json.rs index 32f1c9307..081dc5d8f 100644 --- a/crates/bitwarden-exporters/src/encrypted_json.rs +++ b/crates/bitwarden-exporters/src/encrypted_json.rs @@ -1,5 +1,5 @@ use base64::{engine::general_purpose::STANDARD, Engine}; -use bitwarden_crypto::{generate_random_bytes, Kdf, KeyEncryptable, PinKey}; +use bitwarden_crypto::{generate_random_bytes, ContentFormat, Kdf, KeyEncryptable, PinKey}; use serde::Serialize; use thiserror::Error; use uuid::Uuid; @@ -57,8 +57,12 @@ pub(crate) fn export_encrypted_json( kdf_iterations, kdf_memory, kdf_parallelism, - enc_key_validation: enc_key_validation.encrypt_with_key(&key)?.to_string(), - data: decrypted_export.encrypt_with_key(&key)?.to_string(), + enc_key_validation: enc_key_validation + .encrypt_with_key(&key, &ContentFormat::Utf8)? + .to_string(), + data: decrypted_export + .encrypt_with_key(&key, &ContentFormat::Utf8)? + .to_string(), }; Ok(serde_json::to_string_pretty(&encrypted_export)?) diff --git a/crates/bitwarden-exporters/src/export.rs b/crates/bitwarden-exporters/src/export.rs index 234e1b3ca..f1a510638 100644 --- a/crates/bitwarden-exporters/src/export.rs +++ b/crates/bitwarden-exporters/src/export.rs @@ -80,7 +80,11 @@ fn encrypt_import( view.set_new_fido2_credentials(ctx, passkeys)?; } - let new_cipher = view.encrypt(ctx, view.key_identifier())?; + let new_cipher = view.encrypt( + ctx, + view.key_identifier(), + bitwarden_crypto::ContentFormat::DomainObject, + )?; Ok(new_cipher) } diff --git a/crates/bitwarden-send/src/send.rs b/crates/bitwarden-send/src/send.rs index a9f54ed4c..7c8bb4019 100644 --- a/crates/bitwarden-send/src/send.rs +++ b/crates/bitwarden-send/src/send.rs @@ -8,8 +8,8 @@ use bitwarden_core::{ require, }; use bitwarden_crypto::{ - generate_random_bytes, CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, - KeyStoreContext, + generate_random_bytes, ContentFormat, CryptoError, Decryptable, EncString, Encryptable, + IdentifyKey, KeyStoreContext, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -194,9 +194,10 @@ impl Encryptable for SendTextView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(SendText { - text: self.text.encrypt(ctx, key)?, + text: self.text.encrypt(ctx, key, ContentFormat::Utf8)?, hidden: self.hidden, }) } @@ -222,10 +223,11 @@ impl Encryptable for SendFileView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(SendFile { id: self.id.clone(), - file_name: self.file_name.encrypt(ctx, key)?, + file_name: self.file_name.encrypt(ctx, key, ContentFormat::Utf8)?, size: self.size.clone(), size_name: self.size_name.clone(), }) @@ -302,6 +304,7 @@ impl Encryptable for SendView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and @@ -325,17 +328,18 @@ impl Encryptable for SendView { id: self.id, access_id: self.access_id.clone(), - name: self.name.encrypt(ctx, send_key)?, - notes: self.notes.encrypt(ctx, send_key)?, - key: k.encrypt(ctx, key)?, + name: self.name.encrypt(ctx, send_key, ContentFormat::Utf8)?, + notes: self.notes.encrypt(ctx, send_key, ContentFormat::Utf8)?, + // In the future, this should support cose key content type + key: k.encrypt(ctx, key, ContentFormat::OctetStream)?, password: self.new_password.as_ref().map(|password| { let password = bitwarden_crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); STANDARD.encode(password) }), r#type: self.r#type, - file: self.file.encrypt(ctx, send_key)?, - text: self.text.encrypt(ctx, send_key)?, + file: self.file.encrypt(ctx, send_key, ContentFormat::Utf8)?, + text: self.text.encrypt(ctx, send_key, ContentFormat::Utf8)?, max_access_count: self.max_access_count, access_count: self.access_count, diff --git a/crates/bitwarden-send/src/send_client.rs b/crates/bitwarden-send/src/send_client.rs index b30bb6f02..8095ce95c 100644 --- a/crates/bitwarden-send/src/send_client.rs +++ b/crates/bitwarden-send/src/send_client.rs @@ -1,7 +1,7 @@ use std::path::Path; use bitwarden_core::Client; -use bitwarden_crypto::{Decryptable, EncString, Encryptable, IdentifyKey}; +use bitwarden_crypto::{ContentFormat, Decryptable, EncString, Encryptable, IdentifyKey}; use thiserror::Error; use crate::{Send, SendListView, SendView}; @@ -115,7 +115,7 @@ impl SendClient { let key = Send::get_key(&mut ctx, &send.key, send.key_identifier())?; - let encrypted = buffer.encrypt(&mut ctx, key)?; + let encrypted = buffer.encrypt(&mut ctx, key, ContentFormat::OctetStream)?; Ok(encrypted.to_buffer()?) } } diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index 5d997f631..86229765d 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -1,6 +1,6 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, }; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] @@ -78,6 +78,7 @@ impl Encryptable for Attachment &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.cipher.key)?; @@ -86,7 +87,9 @@ impl Encryptable for Attachment // Because this is a new attachment, we have to generate a key for it, encrypt the contents // with it, and then encrypt the key with the cipher key let attachment_key = ctx.generate_symmetric_key(ATTACHMENT_KEY)?; - let encrypted_contents = self.contents.encrypt(ctx, attachment_key)?; + let encrypted_contents = + self.contents + .encrypt(ctx, attachment_key, ContentFormat::OctetStream)?; attachment.key = Some(ctx.wrap_symmetric_key(ciphers_key, attachment_key)?); let contents = encrypted_contents.to_buffer()?; @@ -96,7 +99,7 @@ impl Encryptable for Attachment attachment.size_name = Some(size_name(contents.len())); Ok(AttachmentEncryptResult { - attachment: attachment.encrypt(ctx, ciphers_key)?, + attachment: attachment.encrypt(ctx, ciphers_key, ContentFormat::DomainObject)?, contents, }) } @@ -137,13 +140,14 @@ impl Encryptable for AttachmentView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Attachment { id: self.id.clone(), url: self.url.clone(), size: self.size.clone(), size_name: self.size_name.clone(), - file_name: self.file_name.encrypt(ctx, key)?, + file_name: self.file_name.encrypt(ctx, key, ContentFormat::Utf8)?, key: self.key.clone(), }) } diff --git a/crates/bitwarden-vault/src/cipher/card.rs b/crates/bitwarden-vault/src/cipher/card.rs index c33da8066..a1be406ee 100644 --- a/crates/bitwarden-vault/src/cipher/card.rs +++ b/crates/bitwarden-vault/src/cipher/card.rs @@ -1,6 +1,8 @@ use bitwarden_api_api::models::CipherCardModel; use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; -use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext, +}; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] use tsify_next::Tsify; @@ -55,14 +57,17 @@ impl Encryptable for CardView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Card { - cardholder_name: self.cardholder_name.encrypt(ctx, key)?, - exp_month: self.exp_month.encrypt(ctx, key)?, - exp_year: self.exp_year.encrypt(ctx, key)?, - code: self.code.encrypt(ctx, key)?, - brand: self.brand.encrypt(ctx, key)?, - number: self.number.encrypt(ctx, key)?, + cardholder_name: self + .cardholder_name + .encrypt(ctx, key, ContentFormat::Utf8)?, + exp_month: self.exp_month.encrypt(ctx, key, ContentFormat::Utf8)?, + exp_year: self.exp_year.encrypt(ctx, key, ContentFormat::Utf8)?, + code: self.code.encrypt(ctx, key, ContentFormat::Utf8)?, + brand: self.brand.encrypt(ctx, key, ContentFormat::Utf8)?, + number: self.number.encrypt(ctx, key, ContentFormat::Utf8)?, }) } } diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 36a1b011b..36f7c6f2e 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -4,7 +4,7 @@ use bitwarden_core::{ require, MissingFieldError, VaultLockedError, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, }; use bitwarden_error::bitwarden_error; use chrono::{DateTime, Utc}; @@ -215,6 +215,7 @@ impl Encryptable for CipherView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; @@ -231,23 +232,47 @@ impl Encryptable for CipherView { folder_id: cipher_view.folder_id, collection_ids: cipher_view.collection_ids, key: cipher_view.key, - name: cipher_view.name.encrypt(ctx, ciphers_key)?, - notes: cipher_view.notes.encrypt(ctx, ciphers_key)?, + name: cipher_view + .name + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + notes: cipher_view + .notes + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, r#type: cipher_view.r#type, - login: cipher_view.login.encrypt(ctx, ciphers_key)?, - identity: cipher_view.identity.encrypt(ctx, ciphers_key)?, - card: cipher_view.card.encrypt(ctx, ciphers_key)?, - secure_note: cipher_view.secure_note.encrypt(ctx, ciphers_key)?, - ssh_key: cipher_view.ssh_key.encrypt(ctx, ciphers_key)?, + login: cipher_view + .login + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + identity: cipher_view + .identity + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + card: cipher_view + .card + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + secure_note: cipher_view + .secure_note + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + ssh_key: cipher_view + .ssh_key + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, favorite: cipher_view.favorite, reprompt: cipher_view.reprompt, organization_use_totp: cipher_view.organization_use_totp, edit: cipher_view.edit, view_password: cipher_view.view_password, - local_data: cipher_view.local_data.encrypt(ctx, ciphers_key)?, - attachments: cipher_view.attachments.encrypt(ctx, ciphers_key)?, - fields: cipher_view.fields.encrypt(ctx, ciphers_key)?, - password_history: cipher_view.password_history.encrypt(ctx, ciphers_key)?, + local_data: cipher_view + .local_data + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + attachments: cipher_view + .attachments + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + fields: cipher_view + .fields + .encrypt(ctx, ciphers_key, ContentFormat::Utf8)?, + password_history: cipher_view.password_history.encrypt( + ctx, + ciphers_key, + ContentFormat::Utf8, + )?, creation_date: cipher_view.creation_date, deleted_date: cipher_view.deleted_date, revision_date: cipher_view.revision_date, @@ -453,7 +478,7 @@ impl CipherView { ctx: &mut KeyStoreContext, key: SymmetricKeyId, ) -> Result<(), CryptoError> { - let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; + let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.take())?; const NEW_KEY: SymmetricKeyId = SymmetricKeyId::Local("new_cipher_key"); @@ -489,8 +514,9 @@ impl CipherView { if let Some(attachments) = &mut self.attachments { for attachment in attachments { if let Some(attachment_key) = &mut attachment.key { - let dec_attachment_key: Vec = attachment_key.decrypt(ctx, old_key)?; - *attachment_key = dec_attachment_key.encrypt(ctx, new_key)?; + let tmp_attachment_key_id = SymmetricKeyId::Local("attachment_key"); + ctx.unwrap_symmetric_key(old_key, tmp_attachment_key_id, attachment_key)?; + *attachment_key = ctx.wrap_symmetric_key(new_key, tmp_attachment_key_id)?; } } } @@ -523,7 +549,8 @@ impl CipherView { if let Some(fido2_credentials) = &mut login.fido2_credentials { let dec_fido2_credentials: Vec = fido2_credentials.decrypt(ctx, old_key)?; - *fido2_credentials = dec_fido2_credentials.encrypt(ctx, new_key)?; + *fido2_credentials = + dec_fido2_credentials.encrypt(ctx, new_key, ContentFormat::DomainObject)?; } } Ok(()) @@ -544,8 +571,9 @@ impl CipherView { // If the cipher has a key, we need to re-encrypt it with the new organization key if let Some(cipher_key) = &mut self.key { - let dec_cipher_key: Vec = cipher_key.decrypt(ctx, old_key)?; - *cipher_key = dec_cipher_key.encrypt(ctx, new_key)?; + let tmp_cipher_key_id = SymmetricKeyId::Local("cipher_key"); + ctx.unwrap_symmetric_key(old_key, tmp_cipher_key_id, cipher_key)?; + *cipher_key = ctx.wrap_symmetric_key(new_key, tmp_cipher_key_id)?; } else { // If the cipher does not have a key, we need to reencrypt all attachment keys self.reencrypt_attachment_keys(ctx, old_key, new_key)?; @@ -565,7 +593,8 @@ impl CipherView { let ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; - require!(self.login.as_mut()).fido2_credentials = Some(creds.encrypt(ctx, ciphers_key)?); + require!(self.login.as_mut()).fido2_credentials = + Some(creds.encrypt(ctx, ciphers_key, ContentFormat::DomainObject)?); Ok(()) } @@ -796,18 +825,42 @@ mod tests { fn generate_fido2(ctx: &mut KeyStoreContext, key: SymmetricKeyId) -> Fido2Credential { Fido2Credential { - credential_id: "123".to_string().encrypt(ctx, key).unwrap(), - key_type: "public-key".to_string().encrypt(ctx, key).unwrap(), - key_algorithm: "ECDSA".to_string().encrypt(ctx, key).unwrap(), - key_curve: "P-256".to_string().encrypt(ctx, key).unwrap(), - key_value: "123".to_string().encrypt(ctx, key).unwrap(), - rp_id: "123".to_string().encrypt(ctx, key).unwrap(), + credential_id: "123" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), + key_type: "public-key" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), + key_algorithm: "ECDSA" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), + key_curve: "P-256" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), + key_value: "123" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), + rp_id: "123" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), user_handle: None, user_name: None, - counter: "123".to_string().encrypt(ctx, key).unwrap(), + counter: "123" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), rp_name: None, user_display_name: None, - discoverable: "true".to_string().encrypt(ctx, key).unwrap(), + discoverable: "true" + .to_string() + .encrypt(ctx, key, ContentFormat::Utf8) + .unwrap(), creation_date: "2024-06-07T14:12:36.150Z".parse().unwrap(), } } @@ -944,11 +997,14 @@ mod tests { .unwrap(); // Make sure that the cipher key is decryptable - let _: Vec = original_cipher - .key - .unwrap() - .decrypt(&mut key_store.context(), SymmetricKeyId::User) - .unwrap(); + let wrapped_key = original_cipher.key.unwrap(); + let mut ctx = key_store.context(); + ctx.unwrap_symmetric_key( + SymmetricKeyId::User, + SymmetricKeyId::Local("test_cipher_key"), + &wrapped_key, + ) + .unwrap(); } #[test] @@ -1090,12 +1146,20 @@ mod tests { // Check that the attachment key has been re-encrypted with the org key, // and the value matches with the original attachment key let new_attachment_key = cipher.attachments.unwrap()[0].key.clone().unwrap(); - let new_attachment_key_dec: Vec<_> = new_attachment_key - .decrypt(&mut key_store.context(), org_key) + let mut ctx = key_store.context(); + let new_attachment_key_id = ctx + .unwrap_symmetric_key( + org_key, + SymmetricKeyId::Local("test_attachment_key"), + &new_attachment_key, + ) + .unwrap(); + #[allow(deprecated)] + let new_attachment_key_dec = ctx + .dangerous_get_symmetric_key(new_attachment_key_id) .unwrap(); - let new_attachment_key_dec: SymmetricCryptoKey = new_attachment_key_dec.try_into().unwrap(); - assert_eq!(new_attachment_key_dec, attachment_key_val); + assert_eq!(*new_attachment_key_dec, attachment_key_val); let cred2: Fido2CredentialFullView = cipher .login @@ -1152,19 +1216,20 @@ mod tests { cipher.move_to_organization(&mut ctx, org).unwrap(); // Check that the cipher key has been re-encrypted with the org key, - let new_cipher_key_dec: Vec<_> = cipher - .key - .clone() - .unwrap() - .decrypt(&mut ctx, org_key) + let wrapped_new_cipher_key = cipher.key.clone().unwrap(); + let new_cipher_key_dec = ctx + .unwrap_symmetric_key( + org_key, + SymmetricKeyId::Local("test_cipher_key"), + &wrapped_new_cipher_key, + ) .unwrap(); - - let new_cipher_key_dec: SymmetricCryptoKey = new_cipher_key_dec.try_into().unwrap(); - + #[allow(deprecated)] + let new_cipher_key_dec = ctx.dangerous_get_symmetric_key(new_cipher_key_dec).unwrap(); #[allow(deprecated)] let cipher_key_val = ctx.dangerous_get_symmetric_key(cipher_key).unwrap(); - assert_eq!(new_cipher_key_dec, *cipher_key_val); + assert_eq!(new_cipher_key_dec, cipher_key_val); // Check that the attachment key hasn't changed assert_eq!( @@ -1289,9 +1354,18 @@ mod tests { let mut ctx = key_store.context(); let original_subtitle = "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string(); - let fingerprint_encrypted = original_subtitle.to_owned().encrypt(&mut ctx, key).unwrap(); - let private_key_encrypted = "".to_string().encrypt(&mut ctx, key).unwrap(); - let public_key_encrypted = "".to_string().encrypt(&mut ctx, key).unwrap(); + let fingerprint_encrypted = original_subtitle + .to_owned() + .encrypt(&mut ctx, key, ContentFormat::Utf8) + .unwrap(); + let private_key_encrypted = "" + .to_string() + .encrypt(&mut ctx, key, ContentFormat::Utf8) + .unwrap(); + let public_key_encrypted = "" + .to_string() + .encrypt(&mut ctx, key, ContentFormat::Utf8) + .unwrap(); let ssh_key_cipher = Cipher { id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()), organization_id: None, @@ -1301,7 +1375,7 @@ mod tests { key: None, name: "My test ssh key" .to_string() - .encrypt(&mut ctx, key) + .encrypt(&mut ctx, key, ContentFormat::Utf8) .unwrap(), notes: None, login: None, diff --git a/crates/bitwarden-vault/src/cipher/field.rs b/crates/bitwarden-vault/src/cipher/field.rs index 30df96a21..57002d541 100644 --- a/crates/bitwarden-vault/src/cipher/field.rs +++ b/crates/bitwarden-vault/src/cipher/field.rs @@ -3,7 +3,9 @@ use bitwarden_core::{ key_management::{KeyIds, SymmetricKeyId}, require, }; -use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext, +}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[cfg(feature = "wasm")] @@ -54,10 +56,11 @@ impl Encryptable for FieldView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Field { - name: self.name.encrypt(ctx, key)?, - value: self.value.encrypt(ctx, key)?, + name: self.name.encrypt(ctx, key, ContentFormat::Utf8)?, + value: self.value.encrypt(ctx, key, ContentFormat::Utf8)?, r#type: self.r#type, linked_id: self.linked_id, }) diff --git a/crates/bitwarden-vault/src/cipher/identity.rs b/crates/bitwarden-vault/src/cipher/identity.rs index 88edecb40..066ea338a 100644 --- a/crates/bitwarden-vault/src/cipher/identity.rs +++ b/crates/bitwarden-vault/src/cipher/identity.rs @@ -1,6 +1,8 @@ use bitwarden_api_api::models::CipherIdentityModel; use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; -use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext, +}; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] use tsify_next::Tsify; @@ -62,26 +64,29 @@ impl Encryptable for IdentityView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Identity { - title: self.title.encrypt(ctx, key)?, - first_name: self.first_name.encrypt(ctx, key)?, - middle_name: self.middle_name.encrypt(ctx, key)?, - last_name: self.last_name.encrypt(ctx, key)?, - address1: self.address1.encrypt(ctx, key)?, - address2: self.address2.encrypt(ctx, key)?, - address3: self.address3.encrypt(ctx, key)?, - city: self.city.encrypt(ctx, key)?, - state: self.state.encrypt(ctx, key)?, - postal_code: self.postal_code.encrypt(ctx, key)?, - country: self.country.encrypt(ctx, key)?, - company: self.company.encrypt(ctx, key)?, - email: self.email.encrypt(ctx, key)?, - phone: self.phone.encrypt(ctx, key)?, - ssn: self.ssn.encrypt(ctx, key)?, - username: self.username.encrypt(ctx, key)?, - passport_number: self.passport_number.encrypt(ctx, key)?, - license_number: self.license_number.encrypt(ctx, key)?, + title: self.title.encrypt(ctx, key, ContentFormat::Utf8)?, + first_name: self.first_name.encrypt(ctx, key, ContentFormat::Utf8)?, + middle_name: self.middle_name.encrypt(ctx, key, ContentFormat::Utf8)?, + last_name: self.last_name.encrypt(ctx, key, ContentFormat::Utf8)?, + address1: self.address1.encrypt(ctx, key, ContentFormat::Utf8)?, + address2: self.address2.encrypt(ctx, key, ContentFormat::Utf8)?, + address3: self.address3.encrypt(ctx, key, ContentFormat::Utf8)?, + city: self.city.encrypt(ctx, key, ContentFormat::Utf8)?, + state: self.state.encrypt(ctx, key, ContentFormat::Utf8)?, + postal_code: self.postal_code.encrypt(ctx, key, ContentFormat::Utf8)?, + country: self.country.encrypt(ctx, key, ContentFormat::Utf8)?, + company: self.company.encrypt(ctx, key, ContentFormat::Utf8)?, + email: self.email.encrypt(ctx, key, ContentFormat::Utf8)?, + phone: self.phone.encrypt(ctx, key, ContentFormat::Utf8)?, + ssn: self.ssn.encrypt(ctx, key, ContentFormat::Utf8)?, + username: self.username.encrypt(ctx, key, ContentFormat::Utf8)?, + passport_number: self + .passport_number + .encrypt(ctx, key, ContentFormat::Utf8)?, + license_number: self.license_number.encrypt(ctx, key, ContentFormat::Utf8)?, }) } } diff --git a/crates/bitwarden-vault/src/cipher/local_data.rs b/crates/bitwarden-vault/src/cipher/local_data.rs index b8f302b80..13e3cb4f5 100644 --- a/crates/bitwarden-vault/src/cipher/local_data.rs +++ b/crates/bitwarden-vault/src/cipher/local_data.rs @@ -1,5 +1,5 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; -use bitwarden_crypto::{CryptoError, Decryptable, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ContentFormat, CryptoError, Decryptable, Encryptable, KeyStoreContext}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] @@ -28,6 +28,7 @@ impl Encryptable for LocalDataView { &self, _ctx: &mut KeyStoreContext, _key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(LocalData { last_used_date: self.last_used_date, diff --git a/crates/bitwarden-vault/src/cipher/login.rs b/crates/bitwarden-vault/src/cipher/login.rs index 07591c1ca..6f7c94385 100644 --- a/crates/bitwarden-vault/src/cipher/login.rs +++ b/crates/bitwarden-vault/src/cipher/login.rs @@ -4,7 +4,9 @@ use bitwarden_core::{ key_management::{KeyIds, SymmetricKeyId}, require, }; -use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext, +}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -195,24 +197,27 @@ impl Encryptable for Fido2CredentialFul &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Fido2Credential { - credential_id: self.credential_id.encrypt(ctx, key)?, - key_type: self.key_type.encrypt(ctx, key)?, - key_algorithm: self.key_algorithm.encrypt(ctx, key)?, - key_curve: self.key_curve.encrypt(ctx, key)?, - key_value: self.key_value.encrypt(ctx, key)?, - rp_id: self.rp_id.encrypt(ctx, key)?, + credential_id: self.credential_id.encrypt(ctx, key, ContentFormat::Utf8)?, + key_type: self.key_type.encrypt(ctx, key, ContentFormat::Utf8)?, + key_algorithm: self.key_algorithm.encrypt(ctx, key, ContentFormat::Utf8)?, + key_curve: self.key_curve.encrypt(ctx, key, ContentFormat::Utf8)?, + key_value: self.key_value.encrypt(ctx, key, ContentFormat::Utf8)?, + rp_id: self.rp_id.encrypt(ctx, key, ContentFormat::Utf8)?, user_handle: self .user_handle .as_ref() - .map(|h| h.encrypt(ctx, key)) + .map(|h| h.encrypt(ctx, key, ContentFormat::Utf8)) .transpose()?, - user_name: self.user_name.encrypt(ctx, key)?, - counter: self.counter.encrypt(ctx, key)?, - rp_name: self.rp_name.encrypt(ctx, key)?, - user_display_name: self.user_display_name.encrypt(ctx, key)?, - discoverable: self.discoverable.encrypt(ctx, key)?, + user_name: self.user_name.encrypt(ctx, key, ContentFormat::Utf8)?, + counter: self.counter.encrypt(ctx, key, ContentFormat::Utf8)?, + rp_name: self.rp_name.encrypt(ctx, key, ContentFormat::Utf8)?, + user_display_name: self + .user_display_name + .encrypt(ctx, key, ContentFormat::Utf8)?, + discoverable: self.discoverable.encrypt(ctx, key, ContentFormat::Utf8)?, creation_date: self.creation_date, }) } @@ -317,11 +322,12 @@ impl Encryptable for LoginUriView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(LoginUri { - uri: self.uri.encrypt(ctx, key)?, + uri: self.uri.encrypt(ctx, key, ContentFormat::Utf8)?, r#match: self.r#match, - uri_checksum: self.uri_checksum.encrypt(ctx, key)?, + uri_checksum: self.uri_checksum.encrypt(ctx, key, ContentFormat::Utf8)?, }) } } @@ -331,13 +337,14 @@ impl Encryptable for LoginView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Login { - username: self.username.encrypt(ctx, key)?, - password: self.password.encrypt(ctx, key)?, + username: self.username.encrypt(ctx, key, ContentFormat::Utf8)?, + password: self.password.encrypt(ctx, key, ContentFormat::Utf8)?, password_revision_date: self.password_revision_date, - uris: self.uris.encrypt(ctx, key)?, - totp: self.totp.encrypt(ctx, key)?, + uris: self.uris.encrypt(ctx, key, ContentFormat::Utf8)?, + totp: self.totp.encrypt(ctx, key, ContentFormat::Utf8)?, autofill_on_page_load: self.autofill_on_page_load, fido2_credentials: self.fido2_credentials.clone(), }) @@ -401,28 +408,31 @@ impl Encryptable for Fido2CredentialVie &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(Fido2Credential { - credential_id: self.credential_id.encrypt(ctx, key)?, - key_type: self.key_type.encrypt(ctx, key)?, - key_algorithm: self.key_algorithm.encrypt(ctx, key)?, - key_curve: self.key_curve.encrypt(ctx, key)?, + credential_id: self.credential_id.encrypt(ctx, key, ContentFormat::Utf8)?, + key_type: self.key_type.encrypt(ctx, key, ContentFormat::Utf8)?, + key_algorithm: self.key_algorithm.encrypt(ctx, key, ContentFormat::Utf8)?, + key_curve: self.key_curve.encrypt(ctx, key, ContentFormat::Utf8)?, key_value: self.key_value.clone(), - rp_id: self.rp_id.encrypt(ctx, key)?, + rp_id: self.rp_id.encrypt(ctx, key, ContentFormat::Utf8)?, user_handle: self .user_handle .as_ref() - .map(|h| h.encrypt(ctx, key)) + .map(|h| h.encrypt(ctx, key, ContentFormat::Utf8)) .transpose()?, user_name: self .user_name .as_ref() - .map(|n| n.encrypt(ctx, key)) + .map(|n| n.encrypt(ctx, key, ContentFormat::Utf8)) .transpose()?, - counter: self.counter.encrypt(ctx, key)?, - rp_name: self.rp_name.encrypt(ctx, key)?, - user_display_name: self.user_display_name.encrypt(ctx, key)?, - discoverable: self.discoverable.encrypt(ctx, key)?, + counter: self.counter.encrypt(ctx, key, ContentFormat::Utf8)?, + rp_name: self.rp_name.encrypt(ctx, key, ContentFormat::Utf8)?, + user_display_name: self + .user_display_name + .encrypt(ctx, key, ContentFormat::Utf8)?, + discoverable: self.discoverable.encrypt(ctx, key, ContentFormat::Utf8)?, creation_date: self.creation_date, }) } diff --git a/crates/bitwarden-vault/src/cipher/secure_note.rs b/crates/bitwarden-vault/src/cipher/secure_note.rs index 223a3a35e..19b4a2718 100644 --- a/crates/bitwarden-vault/src/cipher/secure_note.rs +++ b/crates/bitwarden-vault/src/cipher/secure_note.rs @@ -3,7 +3,7 @@ use bitwarden_core::{ key_management::{KeyIds, SymmetricKeyId}, require, }; -use bitwarden_crypto::{CryptoError, Decryptable, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ContentFormat, CryptoError, Decryptable, Encryptable, KeyStoreContext}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[cfg(feature = "wasm")] @@ -42,6 +42,7 @@ impl Encryptable for SecureNoteView { &self, _ctx: &mut KeyStoreContext, _key: SymmetricKeyId, + _content_format: ContentFormat, ) -> Result { Ok(SecureNote { r#type: self.r#type, diff --git a/crates/bitwarden-vault/src/cipher/ssh_key.rs b/crates/bitwarden-vault/src/cipher/ssh_key.rs index c2318d02a..286b8cf36 100644 --- a/crates/bitwarden-vault/src/cipher/ssh_key.rs +++ b/crates/bitwarden-vault/src/cipher/ssh_key.rs @@ -1,5 +1,7 @@ use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; -use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext}; +use bitwarden_crypto::{ + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext, +}; use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] use tsify_next::Tsify; @@ -35,11 +37,12 @@ impl Encryptable for SshKeyView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + content_format: ContentFormat, ) -> Result { Ok(SshKey { - private_key: self.private_key.encrypt(ctx, key)?, - public_key: self.public_key.encrypt(ctx, key)?, - fingerprint: self.fingerprint.encrypt(ctx, key)?, + private_key: self.private_key.encrypt(ctx, key, content_format)?, + public_key: self.public_key.encrypt(ctx, key, content_format)?, + fingerprint: self.fingerprint.encrypt(ctx, key, content_format)?, }) } } diff --git a/crates/bitwarden-vault/src/folder.rs b/crates/bitwarden-vault/src/folder.rs index d398b986c..c664e366e 100644 --- a/crates/bitwarden-vault/src/folder.rs +++ b/crates/bitwarden-vault/src/folder.rs @@ -4,7 +4,7 @@ use bitwarden_core::{ require, }; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -50,10 +50,11 @@ impl Encryptable for FolderView { &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + content_format: ContentFormat, ) -> Result { Ok(Folder { id: self.id, - name: self.name.encrypt(ctx, key)?, + name: self.name.encrypt(ctx, key, content_format)?, revision_date: self.revision_date, }) } diff --git a/crates/bitwarden-vault/src/password_history.rs b/crates/bitwarden-vault/src/password_history.rs index 31d1cad7e..0fba1fa41 100644 --- a/crates/bitwarden-vault/src/password_history.rs +++ b/crates/bitwarden-vault/src/password_history.rs @@ -1,7 +1,7 @@ use bitwarden_api_api::models::CipherPasswordHistoryModel; use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, + ContentFormat, CryptoError, Decryptable, EncString, Encryptable, IdentifyKey, KeyStoreContext, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -44,9 +44,10 @@ impl Encryptable for PasswordHistoryVie &self, ctx: &mut KeyStoreContext, key: SymmetricKeyId, + content_format: ContentFormat, ) -> Result { Ok(PasswordHistory { - password: self.password.encrypt(ctx, key)?, + password: self.password.encrypt(ctx, key, content_format)?, last_used_date: self.last_used_date, }) } diff --git a/crates/bitwarden-wasm-internal/src/pure_crypto.rs b/crates/bitwarden-wasm-internal/src/pure_crypto.rs index df0aaa5d3..848f68978 100644 --- a/crates/bitwarden-wasm-internal/src/pure_crypto.rs +++ b/crates/bitwarden-wasm-internal/src/pure_crypto.rs @@ -2,9 +2,9 @@ use std::str::FromStr; use bitwarden_core::key_management::{KeyIds, SymmetricKeyId}; use bitwarden_crypto::{ - AsymmetricCryptoKey, AsymmetricPublicCryptoKey, CryptoError, Decryptable, EncString, - Encryptable, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey, SymmetricCryptoKey, - UnsignedSharedKey, + AsymmetricCryptoKey, AsymmetricPublicCryptoKey, ContentFormat, CryptoError, Decryptable, + EncString, Encryptable, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey, + SymmetricCryptoKey, UnsignedSharedKey, }; use wasm_bindgen::prelude::*; @@ -56,13 +56,16 @@ impl PureCrypto { pub fn symmetric_encrypt_string(plain: String, key: Vec) -> Result { plain - .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) + .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?, &ContentFormat::Utf8) .map(|enc| enc.to_string()) } pub fn symmetric_encrypt_bytes(plain: Vec, key: Vec) -> Result { plain - .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?) + .encrypt_with_key( + &SymmetricCryptoKey::try_from(key)?, + &ContentFormat::OctetStream, + ) .map(|enc| enc.to_string()) } @@ -71,7 +74,10 @@ impl PureCrypto { key: Vec, ) -> Result, CryptoError> { plain - .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)? + .encrypt_with_key( + &SymmetricCryptoKey::try_from(key)?, + &ContentFormat::OctetStream, + )? .to_buffer() } @@ -172,7 +178,11 @@ impl PureCrypto { )?; // Note: The order of arguments is different here, and should probably be refactored Ok(encapsulation_key - .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))? + .encrypt( + &mut context, + SymmetricKeyId::Local("wrapping_key"), + ContentFormat::OctetStream, + )? .to_string()) } @@ -205,7 +215,11 @@ impl PureCrypto { )?; // Note: The order of arguments is different here, and should probably be refactored Ok(decapsulation_key - .encrypt(&mut context, SymmetricKeyId::Local("wrapping_key"))? + .encrypt( + &mut context, + SymmetricKeyId::Local("wrapping_key"), + ContentFormat::Pkcs8, + )? .to_string()) }