diff --git a/Cargo.lock b/Cargo.lock index ca1dc0ea4..6c0f7c77d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,7 @@ dependencies = [ "bitwarden-crypto", "chrono", "csv", + "itertools 0.12.1", "serde", "serde_json", "thiserror", diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index f9bda838a..f4c8b0f7c 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, DecryptedString, DecryptedVec, KeyDecryptable, }; // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop @@ -166,8 +166,8 @@ impl AsymmetricEncString { } } -impl KeyDecryptable> for AsymmetricEncString { - fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result> { +impl KeyDecryptable for AsymmetricEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { use AsymmetricEncString::*; match self { Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), @@ -181,14 +181,15 @@ impl KeyDecryptable> for AsymmetricEncString { key.key.decrypt(Oaep::new::(), data) } } + .map(|v| DecryptedVec::new(Box::new(v))) .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) +impl KeyDecryptable for AsymmetricEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { + let dec: DecryptedVec = self.decrypt_with_key(key)?; + dec.try_into() } } @@ -209,8 +210,11 @@ mod tests { use schemars::schema_for; use super::{AsymmetricCryptoKey, AsymmetricEncString, KeyDecryptable}; + use crate::{DecryptedString, SensitiveString}; - const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- + fn rsa_private_key_string() -> SensitiveString { + SensitiveString::test( + "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS 8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 @@ -237,42 +241,44 @@ AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz XKZBokBGnjFnTnKcs7nv/O8= ------END PRIVATE KEY-----"; +-----END PRIVATE KEY-----", + ) + } #[test] fn test_enc_string_rsa2048_oaep_sha256_b64() { - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let private_key = AsymmetricCryptoKey::from_pem(rsa_private_key_string()).unwrap(); let enc_str: &str = "3.YFqzW9LL/uLjCnl0RRLtndzGJ1FV27mcwQwGjfJPOVrgCX9nJSUYCCDd0iTIyOZ/zRxG47b6L1Z3qgkEfcxjmrSBq60gijc3E2TBMAg7OCLVcjORZ+i1sOVOudmOPWro6uA8refMrg4lqbieDlbLMzjVEwxfi5WpcL876cD0vYyRwvLO3bzFrsE7x33HHHtZeOPW79RqMn5efsB5Dj9wVheC9Ix9AYDjbo+rjg9qR6guwKmS7k2MSaIQlrDR7yu8LP+ePtiSjx+gszJV5jQGfcx60dtiLQzLS/mUD+RmU7B950Bpx0H7x56lT5yXZbWK5YkoP6qd8B8D2aKbP68Ywg=="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); assert_eq!(enc_string.enc_type(), 3); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res: DecryptedString = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res.expose(), "EncryptMe!"); } #[test] fn test_enc_string_rsa2048_oaep_sha1_b64() { - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let private_key = AsymmetricCryptoKey::from_pem(rsa_private_key_string()).unwrap(); 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(); assert_eq!(enc_string.enc_type(), 4); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res: DecryptedString = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res.expose(), "EncryptMe!"); } #[test] fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let private_key = AsymmetricCryptoKey::from_pem(rsa_private_key_string()).unwrap(); let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); assert_eq!(enc_string.enc_type(), 6); - let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); - assert_eq!(res, "EncryptMe!"); + let res: DecryptedString = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res.expose(), "EncryptMe!"); } #[test] diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index d312882d7..e8c4c2d04 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -8,7 +8,7 @@ use serde::Deserialize; use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, - KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, + DecryptedString, DecryptedVec, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }; /// # Encrypted string primitive @@ -233,11 +233,15 @@ impl KeyEncryptable for &[u8] { } } -impl KeyDecryptable> for EncString { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { +impl KeyDecryptable for EncString { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { match self { EncString::AesCbc256_B64 { iv, data } => { - let dec = crate::aes::decrypt_aes256(iv, data.clone(), &key.key)?; + let dec = DecryptedVec::new(Box::new(crate::aes::decrypt_aes256( + iv, + data.clone(), + &key.key, + )?)); Ok(dec) } EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => { @@ -247,13 +251,24 @@ impl KeyDecryptable> for EncString { // When refactoring the key handling, this should be fixed. let enc_key = key.key[0..16].into(); let mac_key = key.key[16..32].into(); - let dec = crate::aes::decrypt_aes128_hmac(iv, mac, data.clone(), mac_key, enc_key)?; + let dec = DecryptedVec::new(Box::new(crate::aes::decrypt_aes128_hmac( + iv, + mac, + data.clone(), + mac_key, + enc_key, + )?)); Ok(dec) } EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { let mac_key = key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?; - let dec = - crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, &key.key)?; + let dec = DecryptedVec::new(Box::new(crate::aes::decrypt_aes256_hmac( + iv, + mac, + data.clone(), + mac_key, + &key.key, + )?)); Ok(dec) } } @@ -266,10 +281,10 @@ impl KeyEncryptable for String { } } -impl KeyDecryptable for EncString { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { - let dec: Vec = self.decrypt_with_key(key)?; - String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) +impl KeyDecryptable for EncString { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + let dec: DecryptedVec = self.decrypt_with_key(key)?; + dec.try_into() } } @@ -301,8 +316,8 @@ mod tests { 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); + let decrypted_str: SensitiveString = cipher.decrypt_with_key(&key).unwrap(); + assert_eq!(decrypted_str.expose(), test_string); } #[test] @@ -399,8 +414,8 @@ mod tests { let enc_string: EncString = enc_str.parse().unwrap(); assert_eq!(enc_string.enc_type(), 0); - let dec_str: String = enc_string.decrypt_with_key(&key).unwrap(); - assert_eq!(dec_str, "EncryptMe!"); + let dec_str: SensitiveString = enc_string.decrypt_with_key(&key).unwrap(); + assert_eq!(dec_str.expose(), "EncryptMe!"); } #[test] @@ -412,8 +427,8 @@ mod tests { let enc_string: EncString = enc_str.parse().unwrap(); assert_eq!(enc_string.enc_type(), 1); - let dec_str: String = enc_string.decrypt_with_key(&key).unwrap(); - assert_eq!(dec_str, "EncryptMe!"); + let dec_str: SensitiveString = enc_string.decrypt_with_key(&key).unwrap(); + assert_eq!(dec_str.expose(), "EncryptMe!"); } #[test] diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index be523bbc6..9636defe1 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -3,7 +3,10 @@ use std::pin::Pin; use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey}; use super::key_encryptable::CryptoKey; -use crate::error::{CryptoError, Result}; +use crate::{ + error::{CryptoError, Result}, + SensitiveString, SensitiveVec, +}; /// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to /// encrypt [AsymmetricEncString](crate::AsymmetricEncString). @@ -20,9 +23,9 @@ pub struct AsymmetricPublicCryptoKey { impl AsymmetricPublicCryptoKey { /// Build a public key from the SubjectPublicKeyInfo DER. - pub fn from_der(der: &[u8]) -> Result { + pub fn from_der(der: SensitiveVec) -> Result { Ok(Self { - key: rsa::RsaPublicKey::from_public_key_der(der) + key: rsa::RsaPublicKey::from_public_key_der(der.expose()) .map_err(|_| CryptoError::InvalidKey)?, }) } @@ -65,17 +68,21 @@ impl AsymmetricCryptoKey { } } - pub fn from_pem(pem: &str) -> Result { + pub fn from_pem(pem: SensitiveString) -> Result { use rsa::pkcs8::DecodePrivateKey; Ok(Self { - key: Box::pin(RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?), + key: Box::pin( + RsaPrivateKey::from_pkcs8_pem(pem.expose()).map_err(|_| CryptoError::InvalidKey)?, + ), }) } - pub fn from_der(der: &[u8]) -> Result { + pub fn from_der(der: SensitiveVec) -> Result { use rsa::pkcs8::DecodePrivateKey; Ok(Self { - key: Box::pin(RsaPrivateKey::from_pkcs8_der(der).map_err(|_| CryptoError::InvalidKey)?), + key: Box::pin( + RsaPrivateKey::from_pkcs8_der(der.expose()).map_err(|_| CryptoError::InvalidKey)?, + ), }) } @@ -117,15 +124,17 @@ impl std::fmt::Debug for AsymmetricCryptoKey { #[cfg(test)] mod tests { - use base64::{engine::general_purpose::STANDARD, Engine}; + use base64::engine::general_purpose::STANDARD; use crate::{ - AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, KeyDecryptable, + AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, DecryptedString, + KeyDecryptable, SensitiveString, }; #[test] fn test_asymmetric_crypto_key() { - let pem_key_str = "-----BEGIN PRIVATE KEY----- + let pem_key_str = SensitiveString::test( + "-----BEGIN PRIVATE KEY----- MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5 qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm @@ -152,74 +161,75 @@ Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf DnqOsltgPomWZ7xVfMkm9niL2OA= ------END PRIVATE KEY-----"; +-----END PRIVATE KEY-----", + ); - let der_key_vec = STANDARD.decode("MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=").unwrap(); + let der_key_vec = SensitiveString::test("MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=").decode_base64(STANDARD).unwrap(); // Load the two different formats and check they are the same key let pem_key = AsymmetricCryptoKey::from_pem(pem_key_str).unwrap(); - let der_key = AsymmetricCryptoKey::from_der(&der_key_vec).unwrap(); + let der_key = AsymmetricCryptoKey::from_der(der_key_vec.clone()).unwrap(); assert_eq!(pem_key.key, der_key.key); // Check that the keys can be converted back to DER - assert_eq!(der_key.to_der().unwrap(), der_key_vec); - assert_eq!(pem_key.to_der().unwrap(), der_key_vec); + assert_eq!(&der_key.to_der().unwrap(), der_key_vec.expose()); + assert_eq!(&pem_key.to_der().unwrap(), der_key_vec.expose()); } #[test] fn test_encrypt_public_decrypt_private() { - let private_key = STANDARD - .decode(concat!( - "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", - "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", - "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", - "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", - "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", - "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", - "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", - "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", - "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", - "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", - "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", - "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", - "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", - "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", - "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", - "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", - "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", - "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", - "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", - "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", - "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", - "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", - "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", - "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", - "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", - "GDgRMUB6cL3IRVzcR0dC6cY=", - )) - .unwrap(); - - let public_key = STANDARD - .decode(concat!( - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", - "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", - "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", - "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", - "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", - "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", - "XQIDAQAB", - )) - .unwrap(); - - let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); - let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); + let private_key = SensitiveString::test(concat!( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", + "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", + "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", + "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", + "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", + "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", + "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", + "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", + "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", + "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", + "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", + "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", + "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", + "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", + "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", + "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", + "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", + "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", + "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", + "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", + "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", + "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", + "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", + "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", + "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", + "GDgRMUB6cL3IRVzcR0dC6cY=", + )) + .decode_base64(STANDARD) + .unwrap(); + + let public_key = SensitiveString::test(concat!( + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", + "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", + "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", + "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", + "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", + "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", + "XQIDAQAB", + )) + .decode_base64(STANDARD) + .unwrap(); + + let private_key = AsymmetricCryptoKey::from_der(private_key).unwrap(); + let public_key = AsymmetricPublicCryptoKey::from_der(public_key).unwrap(); let plaintext = "Hello, world!"; let encrypted = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(plaintext.as_bytes(), &public_key) .unwrap(); - let decrypted: String = encrypted.decrypt_with_key(&private_key).unwrap(); + let decrypted: DecryptedString = encrypted.decrypt_with_key(&private_key).unwrap(); - assert_eq!(plaintext, decrypted); + assert_eq!(plaintext, decrypted.expose()); } } diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index d33e0dc32..e54dbaa88 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -62,11 +62,11 @@ impl DeviceKey { protected_device_private_key: EncString, protected_user_key: AsymmetricEncString, ) -> 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)?; + let device_private_key: DecryptedVec = + 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 = DecryptedVec::new(Box::new(dec)); + let dec: DecryptedVec = protected_user_key.decrypt_with_key(&device_private_key)?; let user_key = SymmetricCryptoKey::try_from(dec)?; Ok(user_key) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 5a24125af..86b4d7ec1 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{ - util, CryptoError, EncString, KeyDecryptable, Result, SensitiveVec, SymmetricCryptoKey, UserKey, + util, CryptoError, DecryptedVec, EncString, KeyDecryptable, Result, SensitiveVec, + SymmetricCryptoKey, UserKey, }; /// Key Derivation Function for Bitwarden Account @@ -95,8 +96,8 @@ impl MasterKey { pub fn decrypt_user_key(&self, user_key: EncString) -> Result { let stretched_key = stretch_kdf_key(&self.0)?; - let mut dec: Vec = user_key.decrypt_with_key(&stretched_key)?; - SymmetricCryptoKey::try_from(dec.as_mut_slice()) + let dec: DecryptedVec = user_key.decrypt_with_key(&stretched_key)?; + SymmetricCryptoKey::try_from(dec) } pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index babcc921d..bd5902e1f 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -12,16 +12,18 @@ //! ## Example: //! //! ```rust -//! use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError}; +//! use bitwarden_crypto::{ +//! CryptoError, DecryptedString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +//! }; //! //! async fn example() -> Result<(), CryptoError> { //! let key = SymmetricCryptoKey::generate(rand::thread_rng()); //! //! let data = "Hello, World!".to_owned(); //! let encrypted = data.clone().encrypt_with_key(&key)?; -//! let decrypted: String = encrypted.decrypt_with_key(&key)?; +//! let decrypted: DecryptedString = encrypted.decrypt_with_key(&key)?; //! -//! assert_eq!(data, decrypted); +//! assert_eq!(&data, decrypted.expose()); //! Ok(()) //! } //! ``` diff --git a/crates/bitwarden-crypto/src/sensitive/sensitive.rs b/crates/bitwarden-crypto/src/sensitive/sensitive.rs index 33e6fddc1..9673c47e5 100644 --- a/crates/bitwarden-crypto/src/sensitive/sensitive.rs +++ b/crates/bitwarden-crypto/src/sensitive/sensitive.rs @@ -3,6 +3,7 @@ use std::{ fmt::{self, Formatter}, }; +use generic_array::{ArrayLength, GenericArray}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -61,6 +62,18 @@ impl From> for SensitiveVec { } } +/// Helper to convert a `&SensitiveVec` to a `Sensitive<[u8, N]>`. +impl TryFrom<&SensitiveVec> for Sensitive<[u8; N]> { + type Error = CryptoError; + + fn try_from(v: &SensitiveVec) -> Result { + Ok(Sensitive::new(Box::new( + TryInto::<[u8; N]>::try_into(v.expose().as_slice()) + .map_err(|_| CryptoError::InvalidKey)?, + ))) + } +} + /// Helper to convert a `Sensitive>` to a `Sensitive`, care is taken to ensure any /// intermediate copies are zeroed to avoid leaking sensitive data. impl TryFrom for SensitiveString { @@ -81,6 +94,12 @@ impl From for SensitiveVec { } } +impl> From>> for SensitiveVec { + fn from(val: Sensitive>) -> Self { + SensitiveVec::new(Box::new(val.value.to_vec())) + } +} + impl SensitiveString { pub fn decode_base64(self, engine: T) -> Result { // Prevent accidental copies by allocating the full size diff --git a/crates/bitwarden-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml index fc118b922..ddfff5b38 100644 --- a/crates/bitwarden-exporters/Cargo.toml +++ b/crates/bitwarden-exporters/Cargo.toml @@ -23,6 +23,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } csv = "1.3.0" +itertools = "0.12.1" serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" thiserror = ">=1.0.40, <2.0" diff --git a/crates/bitwarden-exporters/src/csv.rs b/crates/bitwarden-exporters/src/csv.rs index a0dcd1040..d4bbe5174 100644 --- a/crates/bitwarden-exporters/src/csv.rs +++ b/crates/bitwarden-exporters/src/csv.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; +use bitwarden_crypto::{DecryptedString, Sensitive}; use csv::Writer; use serde::Serializer; use thiserror::Error; -use uuid::Uuid; use crate::{Cipher, CipherType, Field, Folder}; @@ -14,7 +14,7 @@ pub enum CsvError { } pub(crate) fn export_csv(folders: Vec, ciphers: Vec) -> Result { - let folders: HashMap = folders.into_iter().map(|f| (f.id, f.name)).collect(); + let folders: HashMap<_, _> = folders.into_iter().map(|f| (f.id, f.name)).collect(); let rows = ciphers .into_iter() @@ -59,27 +59,30 @@ pub(crate) fn export_csv(folders: Vec, ciphers: Vec) -> Result, + folder: Option, #[serde(serialize_with = "bool_serialize")] favorite: bool, r#type: String, - name: String, - notes: Option, + name: DecryptedString, + notes: Option, #[serde(serialize_with = "fields_serialize")] fields: Vec, reprompt: u8, #[serde(serialize_with = "vec_serialize")] - login_uri: Vec, - login_username: Option, - login_password: Option, - login_totp: Option, + login_uri: Vec, + login_username: Option, + login_password: Option, + login_totp: Option, } -fn vec_serialize(x: &[String], s: S) -> Result +fn vec_serialize(x: &[DecryptedString], s: S) -> Result where S: Serializer, { - s.serialize_str(x.join(",").as_str()) + let iter = itertools::Itertools::intersperse(x.iter().map(|s| s.expose().as_str()), ","); + let result: Sensitive = Sensitive::new(Box::new(iter.collect())); + + s.serialize_str(result.expose()) } fn bool_serialize(x: &bool, s: S) -> Result @@ -98,8 +101,14 @@ where .map(|f| { format!( "{}: {}", - f.name.to_owned().unwrap_or_default(), - f.value.to_owned().unwrap_or_default() + f.name + .as_ref() + .map(|n| n.expose().as_str()) + .unwrap_or_default(), + f.value + .as_ref() + .map(|n| n.expose().as_str()) + .unwrap_or_default(), ) }) .collect::>() @@ -118,24 +127,24 @@ mod tests { let folders = vec![ Folder { id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), - name: "Test Folder A".to_string(), + name: DecryptedString::test("Test Folder A"), }, Folder { id: "583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap(), - name: "Test Folder B".to_string(), + name: DecryptedString::test("Test Folder B"), }, ]; let ciphers = vec![ Cipher { id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), folder_id: None, - name: "test@bitwarden.com".to_string(), + name: DecryptedString::test("test@bitwarden.com"), notes: None, r#type: CipherType::Login(Box::new(Login { - username: Some("test@bitwarden.com".to_string()), - password: Some("Abc123".to_string()), + username: Some(DecryptedString::test("test@bitwarden.com")), + password: Some(DecryptedString::test("Abc123")), login_uris: vec![LoginUri { - uri: Some("https://google.com".to_string()), + uri: Some(DecryptedString::test("https://google.com")), r#match: None, }], totp: None, @@ -150,29 +159,29 @@ mod tests { Cipher { id: "7dd81bd0-cc72-4f42-96e7-b0fc014e71a3".parse().unwrap(), folder_id: Some("583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap()), - name: "Steam Account".to_string(), + name: DecryptedString::test("Steam Account"), notes: None, r#type: CipherType::Login(Box::new(Login { - username: Some("steam".to_string()), - password: Some("3Pvb8u7EfbV*nJ".to_string()), + username: Some(DecryptedString::test("steam")), + password: Some(DecryptedString::test("3Pvb8u7EfbV*nJ")), login_uris: vec![LoginUri { - uri: Some("https://steampowered.com".to_string()), + uri: Some(DecryptedString::test("https://steampowered.com")), r#match: None, }], - totp: Some("steam://ABCD123".to_string()), + totp: Some(DecryptedString::test("steam://ABCD123")), })), favorite: true, reprompt: 0, fields: vec![ Field { - name: Some("Test".to_string()), - value: Some("v".to_string()), + name: Some(DecryptedString::test("Test")), + value: Some(DecryptedString::test("v")), r#type: 0, linked_id: None, }, Field { - name: Some("Hidden".to_string()), - value: Some("asdfer".to_string()), + name: Some(DecryptedString::test("Hidden")), + value: Some(DecryptedString::test("asdfer")), r#type: 1, linked_id: None, }, @@ -200,7 +209,7 @@ mod tests { let ciphers = vec![Cipher { id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), folder_id: None, - name: "My Card".to_string(), + name: DecryptedString::test("My Card"), notes: None, r#type: CipherType::Card(Box::new(Card { cardholder_name: None, @@ -229,7 +238,7 @@ mod tests { let ciphers = vec![Cipher { id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), folder_id: None, - name: "My Identity".to_string(), + name: DecryptedString::test("My Identity"), notes: None, r#type: CipherType::Identity(Box::new(Identity { title: None, diff --git a/crates/bitwarden-exporters/src/encrypted_json.rs b/crates/bitwarden-exporters/src/encrypted_json.rs index 6dabc90d5..c0d425f87 100644 --- a/crates/bitwarden-exporters/src/encrypted_json.rs +++ b/crates/bitwarden-exporters/src/encrypted_json.rs @@ -85,6 +85,8 @@ pub(crate) struct EncryptedJsonExport { mod tests { use std::num::NonZeroU32; + use bitwarden_crypto::DecryptedString; + use super::*; use crate::{ Card, Cipher, CipherType, Field, Identity, Login, LoginUri, SecureNote, SecureNoteType, @@ -95,24 +97,24 @@ mod tests { let _export = export_encrypted_json( vec![Folder { id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(), - name: "Important".to_string(), + name: DecryptedString::test("Important"), }], vec![ Cipher { id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "Bitwarden".to_string(), - notes: Some("My note".to_string()), + name: DecryptedString::test("Bitwarden"), + notes: Some(DecryptedString::test("My note")), r#type: CipherType::Login(Box::new(Login { - username: Some("test@bitwarden.com".to_string()), - password: Some("asdfasdfasdf".to_string()), + username: Some(DecryptedString::test("test@bitwarden.com")), + password: Some(DecryptedString::test("asdfasdfasdf")), login_uris: vec![LoginUri { - uri: Some("https://vault.bitwarden.com".to_string()), + uri: Some(DecryptedString::test("https://vault.bitwarden.com")), r#match: None, }], - totp: Some("ABC".to_string()), + totp: Some(DecryptedString::test("ABC")), })), favorite: true, @@ -120,31 +122,31 @@ mod tests { fields: vec![ Field { - name: Some("Text".to_string()), - value: Some("A".to_string()), + name: Some(DecryptedString::test("Text")), + value: Some(DecryptedString::test("A")), r#type: 0, linked_id: None, }, Field { - name: Some("Hidden".to_string()), - value: Some("B".to_string()), + name: Some(DecryptedString::test("Hidden")), + value: Some(DecryptedString::test("B")), r#type: 1, linked_id: None, }, Field { - name: Some("Boolean (true)".to_string()), - value: Some("true".to_string()), + name: Some(DecryptedString::test("Boolean (true)")), + value: Some(DecryptedString::test("true")), r#type: 2, linked_id: None, }, Field { - name: Some("Boolean (false)".to_string()), - value: Some("false".to_string()), + name: Some(DecryptedString::test("Boolean (false)")), + value: Some(DecryptedString::test("false")), r#type: 2, linked_id: None, }, Field { - name: Some("Linked".to_string()), + name: Some(DecryptedString::test("Linked")), value: None, r#type: 3, linked_id: Some(101), @@ -159,8 +161,8 @@ mod tests { id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), folder_id: None, - name: "My secure note".to_string(), - notes: Some("Very secure!".to_string()), + name: DecryptedString::test("My secure note"), + notes: Some(DecryptedString::test("Very secure!")), r#type: CipherType::SecureNote(Box::new(SecureNote { r#type: SecureNoteType::Generic, @@ -179,16 +181,16 @@ mod tests { id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), folder_id: None, - name: "My card".to_string(), + name: DecryptedString::test("My card"), notes: None, r#type: CipherType::Card(Box::new(Card { - cardholder_name: Some("John Doe".to_string()), - exp_month: Some("1".to_string()), - exp_year: Some("2032".to_string()), - code: Some("123".to_string()), - brand: Some("Visa".to_string()), - number: Some("4111111111111111".to_string()), + cardholder_name: Some(DecryptedString::test("John Doe")), + exp_month: Some(DecryptedString::test("1")), + exp_year: Some(DecryptedString::test("2032")), + code: Some(DecryptedString::test("123")), + brand: Some(DecryptedString::test("Visa")), + number: Some(DecryptedString::test("4111111111111111")), })), favorite: false, @@ -204,14 +206,14 @@ mod tests { id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "My identity".to_string(), + name: DecryptedString::test("My identity"), notes: None, r#type: CipherType::Identity(Box::new(Identity { - title: Some("Mr".to_string()), - first_name: Some("John".to_string()), + title: Some(DecryptedString::test("Mr")), + first_name: Some(DecryptedString::test("John")), middle_name: None, - last_name: Some("Doe".to_string()), + last_name: Some(DecryptedString::test("Doe")), address1: None, address2: None, address3: None, @@ -219,11 +221,11 @@ mod tests { state: None, postal_code: None, country: None, - company: Some("Bitwarden".to_string()), + company: Some(DecryptedString::test("Bitwarden")), email: None, phone: None, ssn: None, - username: Some("JDoe".to_string()), + username: Some(DecryptedString::test("JDoe")), passport_number: None, license_number: None, })), diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs index 3f6c72c1f..f2c889f9f 100644 --- a/crates/bitwarden-exporters/src/json.rs +++ b/crates/bitwarden-exporters/src/json.rs @@ -1,3 +1,4 @@ +use bitwarden_crypto::DecryptedString; use chrono::{DateTime, Utc}; use thiserror::Error; use uuid::Uuid; @@ -36,7 +37,7 @@ struct JsonExport { #[serde(rename_all = "camelCase")] struct JsonFolder { id: Uuid, - name: String, + name: DecryptedString, } impl From for JsonFolder { @@ -57,8 +58,8 @@ struct JsonCipher { organization_id: Option, collection_ids: Option>, - name: String, - notes: Option, + name: DecryptedString, + notes: Option, r#type: u8, #[serde(skip_serializing_if = "Option::is_none")] @@ -75,7 +76,7 @@ struct JsonCipher { #[serde(skip_serializing_if = "Vec::is_empty")] fields: Vec, - password_history: Option>, + password_history: Option>, revision_date: DateTime, creation_date: DateTime, @@ -85,11 +86,11 @@ struct JsonCipher { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonLogin { - username: Option, - password: Option, + username: Option, + password: Option, uris: Vec, - totp: Option, - fido2_credentials: Vec, + totp: Option, + fido2_credentials: Vec, } impl From for JsonLogin { @@ -107,7 +108,7 @@ impl From for JsonLogin { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonLoginUri { - uri: Option, + uri: Option, r#match: Option, } @@ -137,12 +138,12 @@ impl From for JsonSecureNote { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonCard { - cardholder_name: Option, - exp_month: Option, - exp_year: Option, - code: Option, - brand: Option, - number: Option, + cardholder_name: Option, + exp_month: Option, + exp_year: Option, + code: Option, + brand: Option, + number: Option, } impl From for JsonCard { @@ -161,24 +162,24 @@ impl From for JsonCard { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonIdentity { - title: Option, - first_name: Option, - middle_name: Option, - last_name: Option, - address1: Option, - address2: Option, - address3: Option, - city: Option, - state: Option, - postal_code: Option, - country: Option, - company: Option, - email: Option, - phone: Option, - ssn: Option, - username: Option, - passport_number: Option, - license_number: Option, + title: Option, + first_name: Option, + middle_name: Option, + last_name: Option, + address1: Option, + address2: Option, + address3: Option, + city: Option, + state: Option, + postal_code: Option, + country: Option, + company: Option, + email: Option, + phone: Option, + ssn: Option, + username: Option, + passport_number: Option, + license_number: Option, } impl From for JsonIdentity { @@ -209,8 +210,8 @@ impl From for JsonIdentity { #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonField { - name: Option, - value: Option, + name: Option, + value: Option, r#type: u8, linked_id: Option, } @@ -278,17 +279,17 @@ mod tests { id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "Bitwarden".to_string(), - notes: Some("My note".to_string()), + name: DecryptedString::test("Bitwarden"), + notes: Some(DecryptedString::test("My note")), r#type: CipherType::Login(Box::new(Login { - username: Some("test@bitwarden.com".to_string()), - password: Some("asdfasdfasdf".to_string()), + username: Some(DecryptedString::test("test@bitwarden.com")), + password: Some(DecryptedString::test("asdfasdfasdf")), login_uris: vec![LoginUri { - uri: Some("https://vault.bitwarden.com".to_string()), + uri: Some(DecryptedString::test("https://vault.bitwarden.com")), r#match: None, }], - totp: Some("ABC".to_string()), + totp: Some(DecryptedString::test("ABC")), })), favorite: true, @@ -296,31 +297,31 @@ mod tests { fields: vec![ Field { - name: Some("Text".to_string()), - value: Some("A".to_string()), + name: Some(DecryptedString::test("Text")), + value: Some(DecryptedString::test("A")), r#type: 0, linked_id: None, }, Field { - name: Some("Hidden".to_string()), - value: Some("B".to_string()), + name: Some(DecryptedString::test("Hidden")), + value: Some(DecryptedString::test("B")), r#type: 1, linked_id: None, }, Field { - name: Some("Boolean (true)".to_string()), - value: Some("true".to_string()), + name: Some(DecryptedString::test("Boolean (true)")), + value: Some(DecryptedString::test("true")), r#type: 2, linked_id: None, }, Field { - name: Some("Boolean (false)".to_string()), - value: Some("false".to_string()), + name: Some(DecryptedString::test("Boolean (false)")), + value: Some(DecryptedString::test("false")), r#type: 2, linked_id: None, }, Field { - name: Some("Linked".to_string()), + name: Some(DecryptedString::test("Linked")), value: None, r#type: 3, linked_id: Some(101), @@ -406,8 +407,8 @@ mod tests { id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), folder_id: None, - name: "My secure note".to_string(), - notes: Some("Very secure!".to_string()), + name: DecryptedString::test("My secure note"), + notes: Some(DecryptedString::test("Very secure!")), r#type: CipherType::SecureNote(Box::new(SecureNote { r#type: SecureNoteType::Generic, @@ -456,16 +457,16 @@ mod tests { id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), folder_id: None, - name: "My card".to_string(), + name: DecryptedString::test("My card"), notes: None, r#type: CipherType::Card(Box::new(Card { - cardholder_name: Some("John Doe".to_string()), - exp_month: Some("1".to_string()), - exp_year: Some("2032".to_string()), - code: Some("123".to_string()), - brand: Some("Visa".to_string()), - number: Some("4111111111111111".to_string()), + cardholder_name: Some(DecryptedString::test("John Doe")), + exp_month: Some(DecryptedString::test("1")), + exp_year: Some(DecryptedString::test("2032")), + code: Some(DecryptedString::test("123")), + brand: Some(DecryptedString::test("Visa")), + number: Some(DecryptedString::test("4111111111111111")), })), favorite: false, @@ -516,14 +517,14 @@ mod tests { id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "My identity".to_string(), + name: DecryptedString::test("My identity"), notes: None, r#type: CipherType::Identity(Box::new(Identity { - title: Some("Mr".to_string()), - first_name: Some("John".to_string()), + title: Some(DecryptedString::test("Mr")), + first_name: Some(DecryptedString::test("John")), middle_name: None, - last_name: Some("Doe".to_string()), + last_name: Some(DecryptedString::test("Doe")), address1: None, address2: None, address3: None, @@ -531,11 +532,11 @@ mod tests { state: None, postal_code: None, country: None, - company: Some("Bitwarden".to_string()), + company: Some(DecryptedString::test("Bitwarden")), email: None, phone: None, ssn: None, - username: Some("JDoe".to_string()), + username: Some(DecryptedString::test("JDoe")), passport_number: None, license_number: None, })), @@ -608,24 +609,24 @@ mod tests { let export = export_json( vec![Folder { id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(), - name: "Important".to_string(), + name: DecryptedString::test("Important"), }], vec![ Cipher { id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "Bitwarden".to_string(), - notes: Some("My note".to_string()), + name: DecryptedString::test("Bitwarden"), + notes: Some(DecryptedString::test("My note")), r#type: CipherType::Login(Box::new(Login { - username: Some("test@bitwarden.com".to_string()), - password: Some("asdfasdfasdf".to_string()), + username: Some(DecryptedString::test("test@bitwarden.com")), + password: Some(DecryptedString::test("asdfasdfasdf")), login_uris: vec![LoginUri { - uri: Some("https://vault.bitwarden.com".to_string()), + uri: Some(DecryptedString::test("https://vault.bitwarden.com")), r#match: None, }], - totp: Some("ABC".to_string()), + totp: Some(DecryptedString::test("ABC")), })), favorite: true, @@ -633,31 +634,31 @@ mod tests { fields: vec![ Field { - name: Some("Text".to_string()), - value: Some("A".to_string()), + name: Some(DecryptedString::test("Text")), + value: Some(DecryptedString::test("A")), r#type: 0, linked_id: None, }, Field { - name: Some("Hidden".to_string()), - value: Some("B".to_string()), + name: Some(DecryptedString::test("Hidden")), + value: Some(DecryptedString::test("B")), r#type: 1, linked_id: None, }, Field { - name: Some("Boolean (true)".to_string()), - value: Some("true".to_string()), + name: Some(DecryptedString::test("Boolean (true)")), + value: Some(DecryptedString::test("true")), r#type: 2, linked_id: None, }, Field { - name: Some("Boolean (false)".to_string()), - value: Some("false".to_string()), + name: Some(DecryptedString::test("Boolean (false)")), + value: Some(DecryptedString::test("false")), r#type: 2, linked_id: None, }, Field { - name: Some("Linked".to_string()), + name: Some(DecryptedString::test("Linked")), value: None, r#type: 3, linked_id: Some(101), @@ -672,8 +673,8 @@ mod tests { id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), folder_id: None, - name: "My secure note".to_string(), - notes: Some("Very secure!".to_string()), + name: DecryptedString::test("My secure note"), + notes: Some(DecryptedString::test("Very secure!")), r#type: CipherType::SecureNote(Box::new(SecureNote { r#type: SecureNoteType::Generic, @@ -692,16 +693,16 @@ mod tests { id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), folder_id: None, - name: "My card".to_string(), + name: DecryptedString::test("My card"), notes: None, r#type: CipherType::Card(Box::new(Card { - cardholder_name: Some("John Doe".to_string()), - exp_month: Some("1".to_string()), - exp_year: Some("2032".to_string()), - code: Some("123".to_string()), - brand: Some("Visa".to_string()), - number: Some("4111111111111111".to_string()), + cardholder_name: Some(DecryptedString::test("John Doe")), + exp_month: Some(DecryptedString::test("1")), + exp_year: Some(DecryptedString::test("2032")), + code: Some(DecryptedString::test("123")), + brand: Some(DecryptedString::test("Visa")), + number: Some(DecryptedString::test("4111111111111111")), })), favorite: false, @@ -717,14 +718,14 @@ mod tests { id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), - name: "My identity".to_string(), + name: DecryptedString::test("My identity"), notes: None, r#type: CipherType::Identity(Box::new(Identity { - title: Some("Mr".to_string()), - first_name: Some("John".to_string()), + title: Some(DecryptedString::test("Mr")), + first_name: Some(DecryptedString::test("John")), middle_name: None, - last_name: Some("Doe".to_string()), + last_name: Some(DecryptedString::test("Doe")), address1: None, address2: None, address3: None, @@ -732,11 +733,11 @@ mod tests { state: None, postal_code: None, country: None, - company: Some("Bitwarden".to_string()), + company: Some(DecryptedString::test("Bitwarden")), email: None, phone: None, ssn: None, - username: Some("JDoe".to_string()), + username: Some(DecryptedString::test("JDoe")), passport_number: None, license_number: None, })), diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs index f17d31a2d..22febad8f 100644 --- a/crates/bitwarden-exporters/src/lib.rs +++ b/crates/bitwarden-exporters/src/lib.rs @@ -1,4 +1,4 @@ -use bitwarden_crypto::Kdf; +use bitwarden_crypto::{DecryptedString, Kdf}; use chrono::{DateTime, Utc}; use thiserror::Error; use uuid::Uuid; @@ -22,7 +22,7 @@ pub enum Format { /// that is not tied to the internal vault models. We may revisit this in the future. pub struct Folder { pub id: Uuid, - pub name: String, + pub name: DecryptedString, } /// Export representation of a Bitwarden cipher. @@ -33,8 +33,8 @@ pub struct Cipher { pub id: Uuid, pub folder_id: Option, - pub name: String, - pub notes: Option, + pub name: DecryptedString, + pub notes: Option, pub r#type: CipherType, @@ -50,8 +50,8 @@ pub struct Cipher { #[derive(Clone)] pub struct Field { - pub name: Option, - pub value: Option, + pub name: Option, + pub value: Option, pub r#type: u8, pub linked_id: Option, } @@ -75,24 +75,24 @@ impl ToString for CipherType { } pub struct Login { - pub username: Option, - pub password: Option, + pub username: Option, + pub password: Option, pub login_uris: Vec, - pub totp: Option, + pub totp: Option, } pub struct LoginUri { - pub uri: Option, + pub uri: Option, pub r#match: Option, } pub struct Card { - pub cardholder_name: Option, - pub exp_month: Option, - pub exp_year: Option, - pub code: Option, - pub brand: Option, - pub number: Option, + pub cardholder_name: Option, + pub exp_month: Option, + pub exp_year: Option, + pub code: Option, + pub brand: Option, + pub number: Option, } pub struct SecureNote { @@ -104,24 +104,24 @@ pub enum SecureNoteType { } pub struct Identity { - pub title: Option, - pub first_name: Option, - pub middle_name: Option, - pub last_name: Option, - pub address1: Option, - pub address2: Option, - pub address3: Option, - pub city: Option, - pub state: Option, - pub postal_code: Option, - pub country: Option, - pub company: Option, - pub email: Option, - pub phone: Option, - pub ssn: Option, - pub username: Option, - pub passport_number: Option, - pub license_number: Option, + pub title: Option, + pub first_name: Option, + pub middle_name: Option, + pub last_name: Option, + pub address1: Option, + pub address2: Option, + pub address3: Option, + pub city: Option, + pub state: Option, + pub postal_code: Option, + pub country: Option, + pub company: Option, + pub email: Option, + pub phone: Option, + pub ssn: Option, + pub username: Option, + pub passport_number: Option, + pub license_number: Option, } #[derive(Error, Debug)] diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs index 8c3523d20..bb341bf4d 100644 --- a/crates/bitwarden/src/auth/auth_request.rs +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -1,6 +1,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ fingerprint, AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, + SensitiveString, }; #[cfg(feature = "mobile")] use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; @@ -57,10 +58,13 @@ pub(crate) fn auth_request_decrypt_user_key( private_key: String, user_key: AsymmetricEncString, ) -> Result { - let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut key: Vec = user_key.decrypt_with_key(&key)?; + use bitwarden_crypto::DecryptedVec; - Ok(SymmetricCryptoKey::try_from(key.as_mut_slice())?) + let private_key = SensitiveString::new(Box::new(private_key)); + let key = AsymmetricCryptoKey::from_der(private_key.decode_base64(STANDARD)?)?; + let key: DecryptedVec = user_key.decrypt_with_key(&key)?; + + Ok(SymmetricCryptoKey::try_from(key)?) } /// Decrypt the user key using the private key generated previously. @@ -70,11 +74,12 @@ pub(crate) fn auth_request_decrypt_master_key( master_key: AsymmetricEncString, user_key: EncString, ) -> Result { - use bitwarden_crypto::MasterKey; + use bitwarden_crypto::{DecryptedVec, MasterKey}; - let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; - let mut master_key: Vec = master_key.decrypt_with_key(&key)?; - let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key.as_mut_slice())?); + let private_key = SensitiveString::new(Box::new(private_key)); + let key = AsymmetricCryptoKey::from_der(private_key.decode_base64(STANDARD)?)?; + let master_key: DecryptedVec = master_key.decrypt_with_key(&key)?; + let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key)?); Ok(master_key.decrypt_user_key(user_key)?) } @@ -86,7 +91,8 @@ pub(crate) fn approve_auth_request( client: &mut Client, public_key: String, ) -> Result { - let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; + let public_key = SensitiveString::new(Box::new(public_key)); + let public_key = AsymmetricPublicCryptoKey::from_der(public_key.decode_base64(STANDARD)?)?; let enc = client.get_encryption_settings()?; let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; @@ -108,8 +114,9 @@ fn test_auth_request() { 67, 35, 61, 245, 93, ]; + let private_key = SensitiveString::new(Box::new(request.private_key.clone())); let private_key = - AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); + AsymmetricCryptoKey::from_der(private_key.decode_base64(STANDARD).unwrap()).unwrap(); let encrypted = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret, &private_key).unwrap(); diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index cca52512b..f95dda52a 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -1,7 +1,9 @@ use std::path::{Path, PathBuf}; use base64::engine::general_purpose::STANDARD; -use bitwarden_crypto::{EncString, KeyDecryptable, SensitiveString, SymmetricCryptoKey}; +use bitwarden_crypto::{ + DecryptedVec, EncString, KeyDecryptable, SensitiveString, SymmetricCryptoKey, +}; use chrono::Utc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -53,7 +55,8 @@ pub(crate) async fn login_access_token( // Extract the encrypted payload and use the access token encryption key to decrypt it let payload: EncString = r.encrypted_payload.parse()?; - let decrypted_payload: Vec = payload.decrypt_with_key(&access_token.encryption_key)?; + let decrypted_payload: DecryptedVec = + payload.decrypt_with_key(&access_token.encryption_key)?; // Once decrypted, we have to JSON decode to extract the organization encryption key #[derive(serde::Deserialize)] @@ -62,7 +65,7 @@ pub(crate) async fn login_access_token( encryption_key: SensitiveString, } - let payload: Payload = serde_json::from_slice(&decrypted_payload)?; + let payload: Payload = serde_json::from_slice(decrypted_payload.expose())?; let encryption_key = payload.encryption_key.clone().decode_base64(STANDARD)?; let encryption_key = SymmetricCryptoKey::try_from(encryption_key)?; diff --git a/crates/bitwarden/src/auth/tde.rs b/crates/bitwarden/src/auth/tde.rs index 420448f16..9b21aaa18 100644 --- a/crates/bitwarden/src/auth/tde.rs +++ b/crates/bitwarden/src/auth/tde.rs @@ -1,7 +1,7 @@ -use base64::{engine::general_purpose::STANDARD, Engine}; +use base64::engine::general_purpose::STANDARD; use bitwarden_crypto::{ - AsymmetricEncString, AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey, - TrustDeviceResponse, UserKey, + AsymmetricEncString, AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SensitiveString, + SymmetricCryptoKey, TrustDeviceResponse, UserKey, }; use crate::{error::Result, Client}; @@ -15,7 +15,9 @@ pub(super) fn make_register_tde_keys( org_public_key: String, remember_device: bool, ) -> Result { - let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(org_public_key)?)?; + let public_key = AsymmetricPublicCryptoKey::from_der( + SensitiveString::new(Box::new(org_public_key)).decode_base64(STANDARD)?, + )?; let mut rng = rand::thread_rng(); diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 025b3cec7..463c67815 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -42,11 +42,11 @@ impl EncryptionSettings { user_key: SymmetricCryptoKey, private_key: EncString, ) -> Result { - use bitwarden_crypto::KeyDecryptable; + use bitwarden_crypto::{DecryptedVec, KeyDecryptable}; let private_key = { - let dec: Vec = private_key.decrypt_with_key(&user_key)?; - Some(AsymmetricCryptoKey::from_der(&dec)?) + let dec: DecryptedVec = private_key.decrypt_with_key(&user_key)?; + Some(AsymmetricCryptoKey::from_der(dec)?) }; Ok(EncryptionSettings { @@ -71,7 +71,7 @@ impl EncryptionSettings { &mut self, org_enc_keys: Vec<(Uuid, AsymmetricEncString)>, ) -> Result<&mut Self> { - use bitwarden_crypto::KeyDecryptable; + use bitwarden_crypto::{DecryptedVec, KeyDecryptable}; use crate::error::Error; @@ -83,9 +83,9 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - let mut dec: Vec = org_enc_key.decrypt_with_key(private_key)?; + let dec: DecryptedVec = org_enc_key.decrypt_with_key(private_key)?; - let org_key = SymmetricCryptoKey::try_from(dec.as_mut_slice())?; + let org_key = SymmetricCryptoKey::try_from(dec)?; self.org_keys.insert(org_id, org_key); } diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 4027302df..32961249b 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -267,15 +267,14 @@ pub fn derive_pin_key(client: &mut Client, pin: SensitiveString) -> Result Result { - use bitwarden_crypto::Sensitive; + use bitwarden_crypto::DecryptedString; let user_key = client .get_encryption_settings()? .get_key(&None) .ok_or(Error::VaultLocked)?; - let pin: String = encrypted_pin.decrypt_with_key(user_key)?; - let pin = Sensitive::new(Box::new(pin)); + let pin: DecryptedString = encrypted_pin.decrypt_with_key(user_key)?; let login_method = client .login_method @@ -307,10 +306,11 @@ pub(super) fn enroll_admin_password_reset( client: &mut Client, public_key: String, ) -> Result { - use base64::{engine::general_purpose::STANDARD, Engine}; + use base64::engine::general_purpose::STANDARD; use bitwarden_crypto::AsymmetricPublicCryptoKey; - let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; + let public_key = SensitiveString::new(Box::new(public_key)); + let public_key = AsymmetricPublicCryptoKey::from_der(public_key.decode_base64(STANDARD)?)?; let enc = client.get_encryption_settings()?; let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; @@ -502,8 +502,8 @@ mod tests { fn test_enroll_admin_password_reset() { use std::num::NonZeroU32; - use base64::{engine::general_purpose::STANDARD, Engine}; - use bitwarden_crypto::{AsymmetricCryptoKey, SensitiveVec}; + use base64::engine::general_purpose::STANDARD; + use bitwarden_crypto::{AsymmetricCryptoKey, DecryptedString, DecryptedVec, SensitiveVec}; let mut client = Client::new(None); @@ -527,15 +527,16 @@ mod tests { let encrypted = enroll_admin_password_reset(&mut client, public_key.to_owned()).unwrap(); 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 = DecryptedString::test(private_key); let private_key = - AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap(); - let decrypted: Vec = encrypted.decrypt_with_key(&private_key).unwrap(); + AsymmetricCryptoKey::from_der(private_key.decode_base64(STANDARD).unwrap()).unwrap(); + let decrypted: DecryptedVec = encrypted.decrypt_with_key(&private_key).unwrap(); let expected = client .get_encryption_settings() .unwrap() .get_key(&None) .unwrap(); - assert_eq!(&decrypted, expected.to_vec().expose()); + assert_eq!(decrypted.expose(), expected.to_vec().expose()); } } diff --git a/crates/bitwarden/src/mobile/vault/client_attachments.rs b/crates/bitwarden/src/mobile/vault/client_attachments.rs index e40721b04..cfe23d414 100644 --- a/crates/bitwarden/src/mobile/vault/client_attachments.rs +++ b/crates/bitwarden/src/mobile/vault/client_attachments.rs @@ -65,6 +65,7 @@ impl<'a> ClientAttachments<'a> { } .decrypt_with_key(key) .map_err(Error::Crypto) + .map(|s| s.expose().to_owned()) } pub async fn decrypt_file( &self, diff --git a/crates/bitwarden/src/mobile/vault/client_sends.rs b/crates/bitwarden/src/mobile/vault/client_sends.rs index e03432313..94c186a58 100644 --- a/crates/bitwarden/src/mobile/vault/client_sends.rs +++ b/crates/bitwarden/src/mobile/vault/client_sends.rs @@ -1,6 +1,8 @@ use std::path::Path; -use bitwarden_crypto::{Decryptable, EncString, Encryptable, KeyDecryptable, KeyEncryptable}; +use bitwarden_crypto::{ + Decryptable, DecryptedVec, EncString, Encryptable, KeyDecryptable, KeyEncryptable, +}; use super::client_vault::ClientVault; use crate::{ @@ -48,7 +50,8 @@ impl<'a> ClientSends<'a> { let key = Send::get_key(&send.key, key)?; let buf = EncString::from_buffer(encrypted_buffer)?; - Ok(buf.decrypt_with_key(&key)?) + let dec: DecryptedVec = buf.decrypt_with_key(&key)?; + Ok(dec.expose().to_owned()) } pub async fn encrypt(&self, send_view: SendView) -> Result { diff --git a/crates/bitwarden/src/secrets_manager/projects/project_response.rs b/crates/bitwarden/src/secrets_manager/projects/project_response.rs index af67df9ab..5fa1862a0 100644 --- a/crates/bitwarden/src/secrets_manager/projects/project_response.rs +++ b/crates/bitwarden/src/secrets_manager/projects/project_response.rs @@ -1,5 +1,5 @@ use bitwarden_api_api::models::ProjectResponseModel; -use bitwarden_crypto::{Decryptable, EncString}; +use bitwarden_crypto::{Decryptable, DecryptedString, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -27,14 +27,14 @@ impl ProjectResponse { ) -> Result { let organization_id = require!(response.organization_id); - let name = require!(response.name) + let name: DecryptedString = require!(response.name) .parse::()? .decrypt(enc, &Some(organization_id))?; Ok(ProjectResponse { id: require!(response.id), organization_id, - name, + name: name.expose().to_owned(), creation_date: require!(response.creation_date).parse()?, revision_date: require!(response.revision_date).parse()?, diff --git a/crates/bitwarden/src/secrets_manager/secrets/list.rs b/crates/bitwarden/src/secrets_manager/secrets/list.rs index 48a507667..8b1c5a967 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/list.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/list.rs @@ -1,7 +1,7 @@ use bitwarden_api_api::models::{ SecretWithProjectsListResponseModel, SecretsWithProjectsInnerSecret, }; -use bitwarden_crypto::{Decryptable, EncString}; +use bitwarden_crypto::{Decryptable, DecryptedString, EncString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -95,14 +95,14 @@ impl SecretIdentifierResponse { ) -> Result { let organization_id = require!(response.organization_id); - let key = require!(response.key) + let key: DecryptedString = require!(response.key) .parse::()? .decrypt(enc, &Some(organization_id))?; Ok(SecretIdentifierResponse { id: require!(response.id), organization_id, - key, + key: key.expose().to_owned(), }) } } diff --git a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs index ffaa7f7e0..05692faae 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs @@ -1,7 +1,7 @@ use bitwarden_api_api::models::{ BaseSecretResponseModel, BaseSecretResponseModelListResponseModel, SecretResponseModel, }; -use bitwarden_crypto::{Decryptable, EncString}; +use bitwarden_crypto::{Decryptable, DecryptedString, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -51,13 +51,13 @@ impl SecretResponse { ) -> Result { let org_id = response.organization_id; - let key = require!(response.key) + let key: DecryptedString = require!(response.key) .parse::()? .decrypt(enc, &org_id)?; - let value = require!(response.value) + let value: DecryptedString = require!(response.value) .parse::()? .decrypt(enc, &org_id)?; - let note = require!(response.note) + let note: DecryptedString = require!(response.note) .parse::()? .decrypt(enc, &org_id)?; @@ -70,9 +70,9 @@ impl SecretResponse { id: require!(response.id), organization_id: require!(org_id), project_id: project, - key, - value, - note, + key: key.expose().to_owned(), + value: value.expose().to_owned(), + note: note.expose().to_owned(), creation_date: require!(response.creation_date).parse()?, revision_date: require!(response.revision_date).parse()?, diff --git a/crates/bitwarden/src/secrets_manager/state.rs b/crates/bitwarden/src/secrets_manager/state.rs index c677ab7e9..aadea564d 100644 --- a/crates/bitwarden/src/secrets_manager/state.rs +++ b/crates/bitwarden/src/secrets_manager/state.rs @@ -1,6 +1,8 @@ use std::{fmt::Debug, path::Path}; -use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable, SensitiveString}; +use bitwarden_crypto::{ + DecryptedString, EncString, KeyDecryptable, KeyEncryptable, SensitiveString, +}; use serde::{Deserialize, Serialize}; use crate::{ @@ -32,8 +34,9 @@ pub fn get(state_file: &Path, access_token: &AccessToken) -> Result let file_content = std::fs::read_to_string(state_file)?; let encrypted_state: EncString = file_content.parse()?; - let decrypted_state: String = encrypted_state.decrypt_with_key(&access_token.encryption_key)?; - let client_state: ClientState = serde_json::from_str(&decrypted_state)?; + let decrypted_state: DecryptedString = + encrypted_state.decrypt_with_key(&access_token.encryption_key)?; + let client_state: ClientState = serde_json::from_str(decrypted_state.expose())?; if client_state.version != STATE_VERSION { return Err(Error::InvalidStateFileVersion); diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs index 2da6ac833..014e08a73 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden/src/tool/exporters/mod.rs @@ -206,7 +206,7 @@ impl From for bitwarden_exporters::SecureNoteType { mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{DecryptedString, Kdf}; use chrono::{DateTime, Utc}; use super::*; @@ -216,7 +216,7 @@ mod tests { fn test_try_from_folder_view() { let view = FolderView { id: Some("fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap()), - name: "test_name".to_string(), + name: DecryptedString::test("test_name"), revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), }; @@ -226,7 +226,7 @@ mod tests { f.id, "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap() ); - assert_eq!(f.name, "test_name".to_string()); + assert_eq!(f.name.expose(), "test_name"); } #[test] @@ -234,8 +234,8 @@ mod tests { let cipher_view = CipherView { r#type: CipherType::Login, login: Some(LoginView { - username: Some("test_username".to_string()), - password: Some("test_password".to_string()), + username: Some(DecryptedString::test("test_username")), + password: Some(DecryptedString::test("test_password")), password_revision_date: None, uris: None, totp: None, @@ -247,7 +247,7 @@ mod tests { folder_id: None, collection_ids: vec![], key: None, - name: "My login".to_string(), + name: DecryptedString::test("My login"), notes: None, identity: None, card: None, @@ -273,7 +273,7 @@ mod tests { "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap() ); assert_eq!(cipher.folder_id, None); - assert_eq!(cipher.name, "My login".to_string()); + assert_eq!(cipher.name.expose(), "My login"); assert_eq!(cipher.notes, None); assert!(!cipher.favorite); assert_eq!(cipher.reprompt, 0); @@ -289,8 +289,8 @@ mod tests { assert_eq!(cipher.deleted_date, None); if let bitwarden_exporters::CipherType::Login(l) = cipher.r#type { - assert_eq!(l.username, Some("test_username".to_string())); - assert_eq!(l.password, Some("test_password".to_string())); + assert_eq!(l.username.unwrap().expose(), "test_username"); + assert_eq!(l.password.unwrap().expose(), "test_password"); assert!(l.login_uris.is_empty()); assert_eq!(l.totp, None); } else { diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index a573e92b0..84cf773fd 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -1,5 +1,6 @@ use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, DecryptedString, DecryptedVec, EncString, KeyDecryptable, KeyEncryptable, + SymmetricCryptoKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -28,7 +29,7 @@ pub struct AttachmentView { pub url: Option, pub size: Option, pub size_name: Option, - pub file_name: Option, + pub file_name: Option, pub key: Option, } @@ -80,18 +81,18 @@ impl<'a> KeyEncryptable for Attachm } } -impl KeyDecryptable> for AttachmentFile { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result, CryptoError> { +impl KeyDecryptable for AttachmentFile { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.cipher.key)?; let ciphers_key = ciphers_key.as_ref().unwrap_or(key); - let mut attachment_key: Vec = self + let attachment_key: DecryptedVec = self .attachment .key .as_ref() .ok_or(CryptoError::MissingKey)? .decrypt_with_key(ciphers_key)?; - let attachment_key = SymmetricCryptoKey::try_from(attachment_key.as_mut_slice())?; + let attachment_key = SymmetricCryptoKey::try_from(attachment_key)?; self.contents.decrypt_with_key(&attachment_key) } @@ -199,6 +200,6 @@ mod tests { .decrypt_with_key(&user_key) .unwrap(); - assert_eq!(dec, original); + assert_eq!(dec.expose(), &original); } } diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index cd61a17d8..be1574905 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::CipherCardModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -23,12 +23,12 @@ pub struct Card { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct CardView { - pub cardholder_name: Option, - pub exp_month: Option, - pub exp_year: Option, - pub code: Option, - pub brand: Option, - pub number: Option, + pub cardholder_name: Option, + pub exp_month: Option, + pub exp_year: Option, + pub code: Option, + pub brand: Option, + pub number: Option, } impl KeyEncryptable for CardView { diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index f245fdc3a..fe9bd9e0d 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -1,7 +1,7 @@ use bitwarden_api_api::models::CipherDetailsResponseModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyContainer, KeyDecryptable, KeyEncryptable, LocateKey, SensitiveVec, - SymmetricCryptoKey, + CryptoError, DecryptedString, DecryptedVec, EncString, KeyContainer, KeyDecryptable, + KeyEncryptable, LocateKey, SensitiveString, SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -86,8 +86,8 @@ pub struct CipherView { pub key: Option, - pub name: String, - pub notes: Option, + pub name: DecryptedString, + pub notes: Option, pub r#type: CipherType, pub login: Option, @@ -120,8 +120,8 @@ pub struct CipherListView { pub folder_id: Option, pub collection_ids: Vec, - pub name: String, - pub sub_title: String, + pub name: DecryptedString, + pub sub_title: DecryptedString, pub r#type: CipherType, @@ -230,77 +230,135 @@ impl Cipher { ciphers_key .as_ref() .map(|k| { - let mut key: Vec = k.decrypt_with_key(key)?; - SymmetricCryptoKey::try_from(key.as_mut_slice()) + let key: DecryptedVec = k.decrypt_with_key(key)?; + SymmetricCryptoKey::try_from(key) }) .transpose() } - fn get_decrypted_subtitle(&self, key: &SymmetricCryptoKey) -> Result { + fn get_decrypted_subtitle( + &self, + key: &SymmetricCryptoKey, + ) -> Result { Ok(match self.r#type { CipherType::Login => { let Some(login) = &self.login else { - return Ok(String::new()); + return Ok(SensitiveString::default()); }; login.username.decrypt_with_key(key)?.unwrap_or_default() } - CipherType::SecureNote => String::new(), + CipherType::SecureNote => SensitiveString::default(), CipherType::Card => { let Some(card) = &self.card else { - return Ok(String::new()); + return Ok(SensitiveString::default()); }; - let mut sub_title = String::new(); - - if let Some(brand) = &card.brand { - let brand: String = brand.decrypt_with_key(key)?; - sub_title.push_str(&brand); - } - - if let Some(number) = &card.number { - let number: String = number.decrypt_with_key(key)?; - let number_len = number.len(); - if number_len > 4 { - if !sub_title.is_empty() { - sub_title.push_str(", "); - } - - // On AMEX cards we show 5 digits instead of 4 - let digit_count = match &number[0..2] { - "34" | "37" => 5, - _ => 4, - }; - - sub_title.push_str(&number[(number_len - digit_count)..]); - } - } - - sub_title + + build_subtitle_card( + card.brand + .as_ref() + .map(|b| b.decrypt_with_key(key)) + .transpose()?, + card.number + .as_ref() + .map(|n| n.decrypt_with_key(key)) + .transpose()?, + ) } CipherType::Identity => { let Some(identity) = &self.identity else { - return Ok(String::new()); + return Ok(SensitiveString::default()); }; - let mut sub_title = String::new(); - - if let Some(first_name) = &identity.first_name { - let first_name: String = first_name.decrypt_with_key(key)?; - sub_title.push_str(&first_name); - } - - if let Some(last_name) = &identity.last_name { - if !sub_title.is_empty() { - sub_title.push(' '); - } - let last_name: String = last_name.decrypt_with_key(key)?; - sub_title.push_str(&last_name); - } - - sub_title + + build_subtitle_identity( + identity + .first_name + .as_ref() + .map(|f| f.decrypt_with_key(key)) + .transpose()?, + identity + .last_name + .as_ref() + .map(|l| l.decrypt_with_key(key)) + .transpose()?, + ) } }) } } +/// Builds the subtitle for a card cipher +/// +/// Care is taken to avoid leaking sensitive data by allocating the full size of the subtitle +fn build_subtitle_card( + brand: Option, + number: Option, +) -> SensitiveString { + let brand: Option = brand.filter(|b: &SensitiveString| !b.expose().is_empty()); + + // We only want to expose the last 4 or 5 digits of the card number + let number: Option = number + .filter(|b: &SensitiveString| b.expose().len() > 4) + .map(|n| { + // For AMEX cards show 5 digits instead of 4 + let desired_len = match &n.expose()[0..2] { + "34" | "37" => 5, + _ => 4, + }; + let start = n.expose().len() - desired_len; + + let mut str = SensitiveString::new(Box::new(String::with_capacity(desired_len + 1))); + str.expose_mut().push('*'); + str.expose_mut().push_str(&n.expose()[start..]); + + str + }); + + match (brand, number) { + (Some(brand), Some(number)) => { + let length = brand.expose().len() + 2 + number.expose().len(); + + let mut str = SensitiveString::new(Box::new(String::with_capacity(length))); + str.expose_mut().push_str(brand.expose()); + str.expose_mut().push_str(", "); + str.expose_mut().push_str(number.expose()); + + str + } + (Some(brand), None) => brand, + (None, Some(number)) => number, + _ => SensitiveString::new(Box::new("".to_owned())), + } +} + +/// Builds the subtitle for a card cipher +/// +/// Care is taken to avoid leaking sensitive data by allocating the full size of the subtitle +fn build_subtitle_identity( + first_name: Option, + last_name: Option, +) -> SensitiveString { + let first_name: Option = + first_name.filter(|f: &SensitiveString| !f.expose().is_empty()); + let last_name: Option = + last_name.filter(|l: &SensitiveString| !l.expose().is_empty()); + + match (first_name, last_name) { + (Some(first_name), Some(last_name)) => { + let length = first_name.expose().len() + 1 + last_name.expose().len(); + + let mut str = SensitiveString::new(Box::new(String::with_capacity(length))); + str.expose_mut().push_str(first_name.expose()); + str.expose_mut().push(' '); + str.expose_mut().push_str(last_name.expose()); + + str + } + (Some(first_name), None) => first_name, + (None, Some(last_name)) => last_name, + _ => SensitiveString::new(Box::new("".to_owned())), + } +} + impl CipherView { pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<()> { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; @@ -341,7 +399,7 @@ impl CipherView { .get_key(&Some(organization_id)) .ok_or(Error::VaultLocked)?; - let dec_cipher_key = SensitiveVec::new(Box::new(cipher_key.decrypt_with_key(old_key)?)); + let dec_cipher_key: DecryptedVec = cipher_key.decrypt_with_key(old_key)?; *cipher_key = dec_cipher_key.expose().encrypt_with_key(new_key)?; } @@ -474,8 +532,8 @@ mod tests { CipherView { r#type: CipherType::Login, login: Some(login::LoginView { - username: Some("test_username".to_string()), - password: Some("test_password".to_string()), + username: Some(DecryptedString::test("test_username")), + password: Some(DecryptedString::test("test_password")), password_revision_date: None, uris: None, totp: None, @@ -487,7 +545,7 @@ mod tests { folder_id: None, collection_ids: vec![], key: None, - name: "My test login".to_string(), + name: DecryptedString::test("My test login"), notes: None, identity: None, card: None, @@ -560,7 +618,7 @@ mod tests { let cipher_enc = cipher.encrypt_with_key(org_key).unwrap(); let cipher_dec: CipherView = cipher_enc.decrypt_with_key(org_key).unwrap(); - assert_eq!(cipher_dec.name, "My test login"); + assert_eq!(cipher_dec.name.expose(), "My test login"); } #[test] @@ -585,4 +643,94 @@ mod tests { let org_key = enc.get_key(&Some(org)).unwrap(); assert!(cipher.encrypt_with_key(org_key).is_err()); } + + #[test] + fn test_build_subtitle_card_visa() { + let brand = Some(DecryptedString::test("Visa")); + let number = Some(DecryptedString::test("4111111111111111")); + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "Visa, *1111"); + } + + #[test] + fn test_build_subtitle_card_mastercard() { + let brand = Some(DecryptedString::test("Mastercard")); + let number = Some(DecryptedString::test("5555555555554444")); + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "Mastercard, *4444"); + } + + #[test] + fn test_build_subtitle_card_amex() { + let brand = Some(DecryptedString::test("Amex")); + let number = Some(DecryptedString::test("378282246310005")); + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "Amex, *10005"); + } + + #[test] + fn test_build_subtitle_card_underflow() { + let brand = Some(DecryptedString::test("Mastercard")); + let number = Some(DecryptedString::test("4")); + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "Mastercard"); + } + + #[test] + fn test_build_subtitle_card_only_brand() { + let brand = Some(DecryptedString::test("Mastercard")); + let number = None; + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "Mastercard"); + } + + #[test] + fn test_build_subtitle_card_only_card() { + let brand = None; + let number = Some(DecryptedString::test("5555555555554444")); + + let subtitle = build_subtitle_card(brand, number); + assert_eq!(subtitle.expose(), "*4444"); + } + + #[test] + fn test_build_subtitle_identity() { + let first_name = Some(DecryptedString::test("John")); + let last_name = Some(DecryptedString::test("Doe")); + + let subtitle = build_subtitle_identity(first_name, last_name); + assert_eq!(subtitle.expose(), "John Doe"); + } + + #[test] + fn test_build_subtitle_identity_only_first() { + let first_name = Some(DecryptedString::test("John")); + let last_name = None; + + let subtitle = build_subtitle_identity(first_name, last_name); + assert_eq!(subtitle.expose(), "John"); + } + + #[test] + fn test_build_subtitle_identity_only_last() { + let first_name = None; + let last_name = Some(DecryptedString::test("Doe")); + + let subtitle = build_subtitle_identity(first_name, last_name); + assert_eq!(subtitle.expose(), "Doe"); + } + + #[test] + fn test_build_subtitle_identity_none() { + let first_name = None; + let last_name = None; + + let subtitle = build_subtitle_identity(first_name, last_name); + assert_eq!(subtitle.expose(), ""); + } } diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index 1f18a9537..48b31b22f 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::CipherFieldModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -34,8 +34,8 @@ pub struct Field { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FieldView { - pub(crate) name: Option, - pub(crate) value: Option, + pub(crate) name: Option, + pub(crate) value: Option, pub(crate) r#type: FieldType, pub(crate) linked_id: Option, diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index f59166eec..efb247a09 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -1,6 +1,6 @@ use bitwarden_api_api::models::CipherIdentityModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -35,24 +35,24 @@ pub struct Identity { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct IdentityView { - pub title: Option, - pub first_name: Option, - pub middle_name: Option, - pub last_name: Option, - pub address1: Option, - pub address2: Option, - pub address3: Option, - pub city: Option, - pub state: Option, - pub postal_code: Option, - pub country: Option, - pub company: Option, - pub email: Option, - pub phone: Option, - pub ssn: Option, - pub username: Option, - pub passport_number: Option, - pub license_number: Option, + pub title: Option, + pub first_name: Option, + pub middle_name: Option, + pub last_name: Option, + pub address1: Option, + pub address2: Option, + pub address3: Option, + pub city: Option, + pub state: Option, + pub postal_code: Option, + pub country: Option, + pub company: Option, + pub email: Option, + pub phone: Option, + pub ssn: Option, + pub username: Option, + pub passport_number: Option, + pub license_number: Option, } impl KeyEncryptable for IdentityView { diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index aed691d9a..bc474c573 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -1,12 +1,15 @@ -use base64::{engine::general_purpose::STANDARD, Engine}; +use base64::engine::general_purpose::STANDARD; use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel}; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, Sensitive, + SensitiveVec, SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; +use hmac::digest::generic_array::GenericArray; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use sha2::Digest; use crate::error::{require, Error, Result}; @@ -36,9 +39,9 @@ pub struct LoginUri { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct LoginUriView { - pub uri: Option, + pub uri: Option, pub r#match: Option, - pub uri_checksum: Option, + pub uri_checksum: Option, } impl LoginUriView { @@ -49,22 +52,28 @@ impl LoginUriView { let Some(cs) = &self.uri_checksum else { return false; }; - let Ok(cs) = STANDARD.decode(cs) else { + let Ok(cs) = cs.clone().decode_base64(STANDARD) else { return false; }; - use sha2::Digest; - let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize(); + let uri_hash: Sensitive> = Sensitive::new(Box::new( + sha2::Sha256::new() + .chain_update(uri.expose().as_bytes()) + .finalize(), + )); - uri_hash.as_slice() == cs + uri_hash.expose().as_slice() == cs.expose() } pub(crate) fn generate_checksum(&mut self) { if let Some(uri) = &self.uri { - use sha2::Digest; - let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize(); - let uri_hash = STANDARD.encode(uri_hash.as_slice()); - self.uri_checksum = Some(uri_hash); + let uri_hash: SensitiveVec = Sensitive::new(Box::new( + sha2::Sha256::new() + .chain_update(uri.expose().as_bytes()) + .finalize(), + )) + .into(); + self.uri_checksum = Some(uri_hash.encode_base64(STANDARD)) } } } @@ -107,12 +116,12 @@ pub struct Login { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct LoginView { - pub username: Option, - pub password: Option, + pub username: Option, + pub password: Option, pub password_revision_date: Option>, pub uris: Option>, - pub totp: Option, + pub totp: Option, pub autofill_on_page_load: Option, // TODO: Remove this once the SDK supports state @@ -245,12 +254,16 @@ impl TryFrom for Fido2Cre #[cfg(test)] mod tests { + use bitwarden_crypto::SensitiveString; + #[test] fn test_valid_checksum() { let uri = super::LoginUriView { - uri: Some("https://example.com".to_string()), + uri: Some(SensitiveString::test("https://example.com")), r#match: Some(super::UriMatchType::Domain), - uri_checksum: Some("EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk=".to_string()), + uri_checksum: Some(SensitiveString::test( + "EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk=", + )), }; assert!(uri.is_checksum_valid()); } @@ -258,9 +271,11 @@ mod tests { #[test] fn test_invalid_checksum() { let uri = super::LoginUriView { - uri: Some("https://example.com".to_string()), + uri: Some(SensitiveString::test("https://example.com")), r#match: Some(super::UriMatchType::Domain), - uri_checksum: Some("UtSgIv8LYfEdOu7yqjF7qXWhmouYGYC8RSr7/ryZg5Q=".to_string()), + uri_checksum: Some(SensitiveString::test( + "UtSgIv8LYfEdOu7yqjF7qXWhmouYGYC8RSr7/ryZg5Q=", + )), }; assert!(!uri.is_checksum_valid()); } @@ -268,7 +283,7 @@ mod tests { #[test] fn test_missing_checksum() { let uri = super::LoginUriView { - uri: Some("https://example.com".to_string()), + uri: Some(SensitiveString::test("https://example.com")), r#match: Some(super::UriMatchType::Domain), uri_checksum: None, }; @@ -278,7 +293,7 @@ mod tests { #[test] fn test_generate_checksum() { let mut uri = super::LoginUriView { - uri: Some("https://test.com".to_string()), + uri: Some(SensitiveString::test("https://test.com")), r#match: Some(super::UriMatchType::Domain), uri_checksum: None, }; @@ -286,7 +301,7 @@ mod tests { uri.generate_checksum(); assert_eq!( - uri.uri_checksum.unwrap().as_str(), + uri.uri_checksum.unwrap().expose(), "OWk2vQvwYD1nhLZdA+ltrpBWbDa2JmHyjUEWxRZSS8w=" ); } diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index ce881cb98..eed60aa1c 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -1,6 +1,7 @@ use bitwarden_api_api::models::CollectionDetailsResponseModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyContainer, KeyDecryptable, LocateKey, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyContainer, KeyDecryptable, LocateKey, + SymmetricCryptoKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -29,7 +30,7 @@ pub struct CollectionView { id: Option, organization_id: Uuid, - name: String, + name: DecryptedString, external_id: Option, hide_passwords: bool, diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index 65b18e54b..315abeca3 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -1,6 +1,7 @@ use bitwarden_api_api::models::FolderResponseModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, LocateKey, + SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -23,7 +24,7 @@ pub struct Folder { #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FolderView { pub id: Option, - pub name: String, + pub name: DecryptedString, pub revision_date: DateTime, } diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index 2ec20116b..2a67d3a2e 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -1,6 +1,7 @@ use bitwarden_api_api::models::CipherPasswordHistoryModel; use bitwarden_crypto::{ - CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, + CryptoError, DecryptedString, EncString, KeyDecryptable, KeyEncryptable, LocateKey, + SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -20,7 +21,7 @@ pub struct PasswordHistory { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct PasswordHistoryView { - password: String, + password: DecryptedString, last_used_date: DateTime, } diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index e681e5468..79fc16f85 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -4,8 +4,9 @@ use base64::{ }; use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; use bitwarden_crypto::{ - derive_shareable_key, generate_random_bytes, CryptoError, EncString, KeyDecryptable, - KeyEncryptable, LocateKey, Sensitive, SensitiveVec, SymmetricCryptoKey, + derive_shareable_key, generate_random_bytes, CryptoError, DecryptedString, DecryptedVec, + EncString, KeyDecryptable, KeyEncryptable, LocateKey, Sensitive, SensitiveVec, + SymmetricCryptoKey, }; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -33,7 +34,7 @@ pub struct SendFile { #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendFileView { pub id: Option, - pub file_name: String, + pub file_name: DecryptedString, pub size: Option, /// Readable size, ex: "4.2 KB" or "1.43 GB" pub size_name: Option, @@ -51,7 +52,7 @@ pub struct SendText { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendTextView { - pub text: Option, + pub text: Option, pub hidden: bool, } @@ -96,10 +97,10 @@ pub struct SendView { pub id: Option, pub access_id: Option, - pub name: String, - pub notes: Option, + pub name: DecryptedString, + pub notes: Option, /// Base64 encoded key - pub key: Option, + pub key: Option, /// Replace or add a password to an existing send. The SDK will always return None when /// decrypting a [Send] /// TODO: We should revisit this, one variant is to have `[Create, Update]SendView` DTOs. @@ -122,14 +123,14 @@ pub struct SendView { pub expiration_date: Option>, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendListView { pub id: Option, pub access_id: Option, - pub name: String, + pub name: DecryptedString, pub r#type: SendType, pub disabled: bool, @@ -144,14 +145,12 @@ impl Send { send_key: &EncString, enc_key: &SymmetricCryptoKey, ) -> Result { - let key: Vec = send_key.decrypt_with_key(enc_key)?; + let key: DecryptedVec = send_key.decrypt_with_key(enc_key)?; Self::derive_shareable_key(&key) } - fn derive_shareable_key(key: &[u8]) -> Result { - let key = Sensitive::new(Box::new( - key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?, - )); + fn derive_shareable_key(key: &SensitiveVec) -> Result { + let key = key.try_into()?; Ok(derive_shareable_key(key, "send", Some("send"))) } } @@ -202,7 +201,7 @@ impl KeyDecryptable for Send { // 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 // the stretched key - let k: Vec = self.key.decrypt_with_key(key)?; + let k: DecryptedVec = self.key.decrypt_with_key(key)?; let key = Send::derive_shareable_key(&k)?; Ok(SendView { @@ -211,7 +210,7 @@ impl KeyDecryptable for Send { name: self.name.decrypt_with_key(&key).ok().unwrap_or_default(), notes: self.notes.decrypt_with_key(&key).ok().flatten(), - key: Some(URL_SAFE_NO_PAD.encode(k)), + key: Some(k.encode_base64(URL_SAFE_NO_PAD)), new_password: None, has_password: self.password.is_some(), @@ -262,13 +261,13 @@ impl KeyEncryptable for SendView { // the stretched key let k = match (self.key, self.id) { // Existing send, decrypt key - (Some(k), _) => URL_SAFE_NO_PAD - .decode(k) + (Some(k), _) => k + .decode_base64(URL_SAFE_NO_PAD) .map_err(|_| CryptoError::InvalidKey)?, // New send, generate random key (None, None) => { let key: Sensitive<[u8; 16]> = generate_random_bytes(); - SensitiveVec::from(key).expose().to_owned() + key.into() } // Existing send without key _ => return Err(CryptoError::InvalidKey), @@ -281,9 +280,10 @@ impl KeyEncryptable for SendView { name: self.name.encrypt_with_key(&send_key)?, notes: self.notes.encrypt_with_key(&send_key)?, - key: k.encrypt_with_key(key)?, + key: k.expose().encrypt_with_key(key)?, password: self.new_password.map(|password| { - let password = bitwarden_crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); + let password = + bitwarden_crypto::pbkdf2(password.as_bytes(), k.expose(), SEND_ITERATIONS); STANDARD.encode(password) }), @@ -363,7 +363,9 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey, SensitiveVec}; + use bitwarden_crypto::{ + KeyDecryptable, KeyEncryptable, MasterKey, SensitiveString, SensitiveVec, + }; use super::{Send, SendText, SendTextView, SendType}; use crate::{ @@ -450,15 +452,15 @@ mod tests { let expected = SendView { id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), - name: "Test".to_string(), + name: SensitiveString::test("Test"), notes: None, - key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + key: Some(SensitiveString::test("Pgui0FK85cNhBGWHAlBHBw")), new_password: None, has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { - text: Some("This is a test".to_owned()), + text: Some(SensitiveString::test("This is a test")), hidden: false, }), max_access_count: None, @@ -481,15 +483,15 @@ mod tests { let view = SendView { id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), - name: "Test".to_string(), + name: SensitiveString::test("Test"), notes: None, - key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + key: Some(SensitiveString::test("Pgui0FK85cNhBGWHAlBHBw")), new_password: None, has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { - text: Some("This is a test".to_owned()), + text: Some(SensitiveString::test("This is a test")), hidden: false, }), max_access_count: None, @@ -519,7 +521,7 @@ mod tests { let view = SendView { id: None, access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), - name: "Test".to_string(), + name: SensitiveString::test("Test"), notes: None, key: None, new_password: None, @@ -527,7 +529,7 @@ mod tests { r#type: SendType::Text, file: None, text: Some(SendTextView { - text: Some("This is a test".to_owned()), + text: Some(SensitiveString::test("This is a test")), hidden: false, }), max_access_count: None, @@ -560,15 +562,15 @@ mod tests { let view = SendView { id: None, access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), - name: "Test".to_owned(), + name: SensitiveString::test("Test"), notes: None, - key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + key: Some(SensitiveString::test("Pgui0FK85cNhBGWHAlBHBw")), new_password: Some("abc123".to_owned()), has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { - text: Some("This is a test".to_owned()), + text: Some(SensitiveString::test("This is a test")), hidden: false, }), max_access_count: None,