From 4c91270873d3d4591828abbd0506a10af93badcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 12 Jan 2024 10:52:43 +0100 Subject: [PATCH] Implement KeyEncryptable support for Asymmetric keys (#447) --- .../src/client/encryption_settings.rs | 22 ++-- .../src/crypto/asymmetric_crypto_key.rs | 107 +++++++++++++++++ .../src/crypto/enc_string/asymmetric.rs | 110 ++++++++++++------ .../src/crypto/enc_string/symmetric.rs | 8 +- crates/bitwarden/src/crypto/encryptable.rs | 4 +- .../bitwarden/src/crypto/key_encryptable.rs | 58 +++++---- crates/bitwarden/src/crypto/mod.rs | 7 +- .../src/crypto/symmetric_crypto_key.rs | 13 ++- .../src/platform/generate_fingerprint.rs | 9 +- .../bitwarden/src/vault/cipher/attachment.rs | 4 +- crates/bitwarden/src/vault/cipher/card.rs | 4 +- crates/bitwarden/src/vault/cipher/cipher.rs | 6 +- crates/bitwarden/src/vault/cipher/field.rs | 4 +- crates/bitwarden/src/vault/cipher/identity.rs | 4 +- .../bitwarden/src/vault/cipher/local_data.rs | 4 +- crates/bitwarden/src/vault/cipher/login.rs | 8 +- .../bitwarden/src/vault/cipher/secure_note.rs | 4 +- crates/bitwarden/src/vault/collection.rs | 2 +- crates/bitwarden/src/vault/folder.rs | 4 +- .../bitwarden/src/vault/password_history.rs | 4 +- crates/bitwarden/src/vault/send.rs | 14 +-- 21 files changed, 276 insertions(+), 124 deletions(-) create mode 100644 crates/bitwarden/src/crypto/asymmetric_crypto_key.rs diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index ebfbba2b4..1a662adfb 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -1,22 +1,18 @@ use std::collections::HashMap; -use rsa::RsaPrivateKey; -use uuid::Uuid; #[cfg(feature = "internal")] -use { - crate::{ - client::UserLoginMethod, - crypto::{AsymmEncString, EncString, KeyDecryptable}, - error::{CryptoError, Result}, - }, - rsa::pkcs8::DecodePrivateKey, +use crate::{ + client::UserLoginMethod, + crypto::{AsymmEncString, EncString, KeyDecryptable}, + error::Result, }; +use uuid::Uuid; -use crate::crypto::SymmetricCryptoKey; +use crate::crypto::{AsymmetricCryptoKey, SymmetricCryptoKey}; pub struct EncryptionSettings { user_key: SymmetricCryptoKey, - pub(crate) private_key: Option, + pub(crate) private_key: Option, org_keys: HashMap, } @@ -61,7 +57,7 @@ impl EncryptionSettings { ) -> Result { let private_key = { let dec: Vec = private_key.decrypt_with_key(&user_key)?; - Some(rsa::RsaPrivateKey::from_pkcs8_der(&dec).map_err(|_| CryptoError::InvalidKey)?) + Some(AsymmetricCryptoKey::from_der(&dec)?) }; Ok(EncryptionSettings { @@ -96,7 +92,7 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - let dec = org_enc_key.decrypt(private_key)?; + let dec: Vec = org_enc_key.decrypt_with_key(private_key)?; let org_key = SymmetricCryptoKey::try_from(dec.as_slice())?; diff --git a/crates/bitwarden/src/crypto/asymmetric_crypto_key.rs b/crates/bitwarden/src/crypto/asymmetric_crypto_key.rs new file mode 100644 index 000000000..93098faa4 --- /dev/null +++ b/crates/bitwarden/src/crypto/asymmetric_crypto_key.rs @@ -0,0 +1,107 @@ +use rsa::RsaPrivateKey; + +use crate::{ + crypto::CryptoKey, + error::{CryptoError, Result}, +}; + +/// An asymmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) +pub struct AsymmetricCryptoKey { + pub(in crate::crypto) key: RsaPrivateKey, +} + +impl AsymmetricCryptoKey { + pub fn from_pem(pem: &str) -> Result { + use rsa::pkcs8::DecodePrivateKey; + Ok(Self { + key: rsa::RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?, + }) + } + + pub fn from_der(der: &[u8]) -> Result { + use rsa::pkcs8::DecodePrivateKey; + Ok(Self { + key: rsa::RsaPrivateKey::from_pkcs8_der(der).map_err(|_| CryptoError::InvalidKey)?, + }) + } + + pub fn to_der(&self) -> Result> { + use rsa::pkcs8::EncodePrivateKey; + Ok(self + .key + .to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey)? + .as_bytes() + .to_owned()) + } + + pub fn to_public_der(&self) -> Result> { + use rsa::pkcs8::EncodePublicKey; + Ok(self + .key + .to_public_key() + .to_public_key_der() + .map_err(|_| CryptoError::InvalidKey)? + .as_bytes() + .to_owned()) + } +} + +impl CryptoKey for AsymmetricCryptoKey {} + +// We manually implement these to make sure we don't print any sensitive data +impl std::fmt::Debug for AsymmetricCryptoKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsymmetricCryptoKey").finish() + } +} + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + + use super::AsymmetricCryptoKey; + + #[test] + fn test_asymmetric_crypto_key() { + let pem_key_str = "-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5 +qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc +afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm +qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv +b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw +P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam +rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi +szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx +0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+ +8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR +jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach +vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI +1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR +J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7 +l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ +TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9 +ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye +KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN +wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ +UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA +kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W +pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN +Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi +CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup +PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf +DnqOsltgPomWZ7xVfMkm9niL2OA= +-----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(); + + // 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(); + 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); + } +} diff --git a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs index 3c67c166a..a2bbee8cb 100644 --- a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs +++ b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs @@ -1,14 +1,13 @@ use std::{fmt::Display, str::FromStr}; use base64::{engine::general_purpose::STANDARD, Engine}; -#[cfg(feature = "internal")] -use rsa::{Oaep, RsaPrivateKey}; +use rsa::Oaep; use serde::Deserialize; -use crate::error::{EncStringParseError, Error, Result}; - -#[cfg(feature = "internal")] -use crate::error::CryptoError; +use crate::{ + crypto::{AsymmetricCryptoKey, KeyDecryptable}, + error::{CryptoError, EncStringParseError, Error, Result}, +}; use super::{from_b64_vec, split_enc_string}; @@ -16,7 +15,7 @@ use super::{from_b64_vec, split_enc_string}; /// /// [AsymmEncString] is a Bitwarden specific primitive that represents an asymmetrically encrypted string. They are /// are used together with the KeyDecryptable and KeyEncryptable traits to encrypt and decrypt -/// data using AsymmetricCryptoKeys. +/// data using [AsymmetricCryptoKey]s. /// /// The flexibility of the [AsymmEncString] type allows for different encryption algorithms to be used /// which is represented by the different variants of the enum. @@ -100,27 +99,6 @@ impl FromStr for AsymmEncString { } } -#[allow(unused)] -impl AsymmEncString { - /// TODO: Convert this to a trait method - #[cfg(feature = "internal")] - pub(crate) fn decrypt(&self, key: &RsaPrivateKey) -> Result> { - Ok(match self { - Self::Rsa2048_OaepSha256_B64 { data } => key.decrypt(Oaep::new::(), data), - Self::Rsa2048_OaepSha1_B64 { data } => key.decrypt(Oaep::new::(), data), - #[allow(deprecated)] - Self::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac: _ } => { - key.decrypt(Oaep::new::(), data) - } - #[allow(deprecated)] - Self::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac: _ } => { - key.decrypt(Oaep::new::(), data) - } - } - .map_err(|_| CryptoError::KeyDecrypt)?) - } -} - impl Display for AsymmEncString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let parts: Vec<&[u8]> = match self { @@ -172,6 +150,32 @@ impl AsymmEncString { } } +impl KeyDecryptable> for AsymmEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result> { + use AsymmEncString::*; + Ok(match self { + Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), + Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::(), data), + #[allow(deprecated)] + Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => { + key.key.decrypt(Oaep::new::(), data) + } + #[allow(deprecated)] + Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => { + key.key.decrypt(Oaep::new::(), data) + } + } + .map_err(|_| CryptoError::KeyDecrypt)?) + } +} + +impl KeyDecryptable for AsymmEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { + let dec: Vec = self.decrypt_with_key(key)?; + String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into()) + } +} + /// Usually we wouldn't want to expose AsymmEncStrings in the API or the schemas. /// But during the transition phase we will expose endpoints using the AsymmEncString type. impl schemars::JsonSchema for AsymmEncString { @@ -188,12 +192,7 @@ impl schemars::JsonSchema for AsymmEncString { mod tests { use super::AsymmEncString; - #[cfg(feature = "internal")] - #[test] - fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { - use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; - - let rsa_private_key: &str = "-----BEGIN PRIVATE KEY----- + const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS 8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 @@ -221,15 +220,50 @@ Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz XKZBokBGnjFnTnKcs7nv/O8= -----END PRIVATE KEY-----"; - let private_key = RsaPrivateKey::from_pkcs8_pem(rsa_private_key).unwrap(); + + #[cfg(feature = "internal")] + #[test] + fn test_enc_string_rsa2048_oaep_sha256_b64() { + use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; + + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "3.YFqzW9LL/uLjCnl0RRLtndzGJ1FV27mcwQwGjfJPOVrgCX9nJSUYCCDd0iTIyOZ/zRxG47b6L1Z3qgkEfcxjmrSBq60gijc3E2TBMAg7OCLVcjORZ+i1sOVOudmOPWro6uA8refMrg4lqbieDlbLMzjVEwxfi5WpcL876cD0vYyRwvLO3bzFrsE7x33HHHtZeOPW79RqMn5efsB5Dj9wVheC9Ix9AYDjbo+rjg9qR6guwKmS7k2MSaIQlrDR7yu8LP+ePtiSjx+gszJV5jQGfcx60dtiLQzLS/mUD+RmU7B950Bpx0H7x56lT5yXZbWK5YkoP6qd8B8D2aKbP68Ywg=="; + let enc_string: AsymmEncString = 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!"); + } + + #[cfg(feature = "internal")] + #[test] + fn test_enc_string_rsa2048_oaep_sha1_b64() { + use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; + + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; + let enc_string: AsymmEncString = 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!"); + } + + #[cfg(feature = "internal")] + #[test] + fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { + use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; + + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; let enc_string: AsymmEncString = enc_str.parse().unwrap(); assert_eq!(enc_string.enc_type(), 6); - let res = enc_string.decrypt(&private_key).unwrap(); - - assert_eq!(std::str::from_utf8(&res).unwrap(), "EncryptMe!"); + let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!"); } #[test] diff --git a/crates/bitwarden/src/crypto/enc_string/symmetric.rs b/crates/bitwarden/src/crypto/enc_string/symmetric.rs index 3fe0e8ef8..801aa9c43 100644 --- a/crates/bitwarden/src/crypto/enc_string/symmetric.rs +++ b/crates/bitwarden/src/crypto/enc_string/symmetric.rs @@ -227,13 +227,13 @@ impl EncString { } impl LocateKey for EncString {} -impl KeyEncryptable for &[u8] { +impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { EncString::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key) } } -impl KeyDecryptable> for EncString { +impl KeyDecryptable> for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { match self { EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { @@ -246,13 +246,13 @@ impl KeyDecryptable> for EncString { } } -impl KeyEncryptable for String { +impl KeyEncryptable for String { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { self.as_bytes().encrypt_with_key(key) } } -impl KeyDecryptable for EncString { +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.into()) diff --git a/crates/bitwarden/src/crypto/encryptable.rs b/crates/bitwarden/src/crypto/encryptable.rs index f1bc78f15..8a8d24d7d 100644 --- a/crates/bitwarden/src/crypto/encryptable.rs +++ b/crates/bitwarden/src/crypto/encryptable.rs @@ -29,14 +29,14 @@ pub trait Decryptable { fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result; } -impl + LocateKey, Output> Encryptable for T { +impl + LocateKey, Output> Encryptable for T { fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?; self.encrypt_with_key(key) } } -impl + LocateKey, Output> Decryptable for T { +impl + LocateKey, Output> Decryptable for T { fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?; self.decrypt_with_key(key) diff --git a/crates/bitwarden/src/crypto/key_encryptable.rs b/crates/bitwarden/src/crypto/key_encryptable.rs index 99c610bb2..852b55c62 100644 --- a/crates/bitwarden/src/crypto/key_encryptable.rs +++ b/crates/bitwarden/src/crypto/key_encryptable.rs @@ -2,66 +2,78 @@ use std::{collections::HashMap, hash::Hash}; use crate::error::Result; -use super::SymmetricCryptoKey; +pub trait CryptoKey {} -pub trait KeyEncryptable { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result; +pub trait KeyEncryptable { + fn encrypt_with_key(self, key: &Key) -> Result; } -pub trait KeyDecryptable { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result; +pub trait KeyDecryptable { + fn decrypt_with_key(&self, key: &Key) -> Result; } -impl, Output> KeyEncryptable> for Option { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result> { +impl, Key: CryptoKey, Output> KeyEncryptable> + for Option +{ + fn encrypt_with_key(self, key: &Key) -> Result> { self.map(|e| e.encrypt_with_key(key)).transpose() } } -impl, Output> KeyDecryptable> for Option { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { +impl, Key: CryptoKey, Output> KeyDecryptable> + for Option +{ + fn decrypt_with_key(&self, key: &Key) -> Result> { self.as_ref().map(|e| e.decrypt_with_key(key)).transpose() } } -impl, Output> KeyEncryptable for Box { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { +impl, Key: CryptoKey, Output> KeyEncryptable + for Box +{ + fn encrypt_with_key(self, key: &Key) -> Result { (*self).encrypt_with_key(key) } } -impl, Output> KeyDecryptable for Box { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { +impl, Key: CryptoKey, Output> KeyDecryptable + for Box +{ + fn decrypt_with_key(&self, key: &Key) -> Result { (**self).decrypt_with_key(key) } } -impl, Output> KeyEncryptable> for Vec { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result> { +impl, Key: CryptoKey, Output> KeyEncryptable> + for Vec +{ + fn encrypt_with_key(self, key: &Key) -> Result> { self.into_iter().map(|e| e.encrypt_with_key(key)).collect() } } -impl, Output> KeyDecryptable> for Vec { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { +impl, Key: CryptoKey, Output> KeyDecryptable> + for Vec +{ + fn decrypt_with_key(&self, key: &Key) -> Result> { self.iter().map(|e| e.decrypt_with_key(key)).collect() } } -impl, Output, Id: Hash + Eq> KeyEncryptable> - for HashMap +impl, Key: CryptoKey, Output, Id: Hash + Eq> + KeyEncryptable> for HashMap { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result> { + fn encrypt_with_key(self, key: &Key) -> Result> { self.into_iter() .map(|(id, e)| Ok((id, e.encrypt_with_key(key)?))) .collect() } } -impl, Output, Id: Hash + Eq + Copy> KeyDecryptable> - for HashMap +impl, Key: CryptoKey, Output, Id: Hash + Eq + Copy> + KeyDecryptable> for HashMap { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { + fn decrypt_with_key(&self, key: &Key) -> Result> { self.iter() .map(|(id, e)| Ok((*id, e.decrypt_with_key(key)?))) .collect() diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index 5355a337a..f21fdb256 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -2,7 +2,7 @@ //! //! This module contains the cryptographic primitives used throughout the SDK. The module makes a //! best effort to abstract away cryptographic concepts into concepts such as -//! [`EncString`] and [`SymmetricCryptoKey`]. +//! [`EncString`], [`SymmetricCryptoKey`] and [`AsymmetricCryptoKey`]. //! //! ## Conventions: //! @@ -36,11 +36,14 @@ pub use enc_string::{AsymmEncString, EncString}; mod encryptable; pub use encryptable::{Decryptable, Encryptable, LocateKey}; mod key_encryptable; -pub use key_encryptable::{KeyDecryptable, KeyEncryptable}; +pub use key_encryptable::{CryptoKey, KeyDecryptable, KeyEncryptable}; mod aes_ops; use aes_ops::{decrypt_aes256_hmac, encrypt_aes256_hmac}; mod symmetric_crypto_key; pub use symmetric_crypto_key::SymmetricCryptoKey; +mod asymmetric_crypto_key; +pub use asymmetric_crypto_key::AsymmetricCryptoKey; + mod shareable_key; pub(crate) use shareable_key::derive_shareable_key; diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index 5487dfd98..6f93d47f6 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -4,12 +4,15 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; use rand::Rng; -use crate::error::{CryptoError, Error}; +use crate::{ + crypto::CryptoKey, + error::{CryptoError, Error}, +}; /// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) pub struct SymmetricCryptoKey { - pub key: GenericArray, - pub mac_key: Option>, + pub(super) key: GenericArray, + pub(super) mac_key: Option>, } impl SymmetricCryptoKey { @@ -81,10 +84,12 @@ impl TryFrom<&[u8]> for SymmetricCryptoKey { } } +impl CryptoKey for SymmetricCryptoKey {} + // We manually implement these to make sure we don't print any sensitive data impl std::fmt::Debug for SymmetricCryptoKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Key").finish() + f.debug_struct("SymmetricCryptoKey").finish() } } diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index cd763b765..1826e195f 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -1,6 +1,5 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use log::info; -use rsa::pkcs8::EncodePublicKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -44,12 +43,8 @@ pub(crate) fn generate_user_fingerprint( .as_ref() .ok_or("Missing private key")?; - let public_key = private_key - .to_public_key() - .to_public_key_der() - .map_err(|_| "Invalid key")?; - - let fingerprint = fingerprint(&fingerprint_material, public_key.as_bytes())?; + let public_key = private_key.to_public_der()?; + let fingerprint = fingerprint(&fingerprint_material, &public_key)?; Ok(fingerprint) } diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index e2d11f592..c22997c83 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -31,7 +31,7 @@ pub struct AttachmentView { pub key: Option, } -impl KeyEncryptable for AttachmentView { +impl KeyEncryptable for AttachmentView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Attachment { id: self.id, @@ -44,7 +44,7 @@ impl KeyEncryptable for AttachmentView { } } -impl KeyDecryptable for Attachment { +impl KeyDecryptable for Attachment { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(AttachmentView { id: self.id.clone(), diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index 6f96041e9..c0f97e1df 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -31,7 +31,7 @@ pub struct CardView { pub number: Option, } -impl KeyEncryptable for CardView { +impl KeyEncryptable for CardView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Card { cardholder_name: self.cardholder_name.encrypt_with_key(key)?, @@ -44,7 +44,7 @@ impl KeyEncryptable for CardView { } } -impl KeyDecryptable for Card { +impl KeyDecryptable for Card { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CardView { cardholder_name: self.cardholder_name.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index e44a3757a..d36855a19 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -135,7 +135,7 @@ pub struct CipherListView { pub revision_date: DateTime, } -impl KeyEncryptable for CipherView { +impl KeyEncryptable for CipherView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); @@ -169,7 +169,7 @@ impl KeyEncryptable for CipherView { } } -impl KeyDecryptable for Cipher { +impl KeyDecryptable for Cipher { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); @@ -286,7 +286,7 @@ impl Cipher { } } -impl KeyDecryptable for Cipher { +impl KeyDecryptable for Cipher { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index dee71872b..bf9352d68 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -41,7 +41,7 @@ pub struct FieldView { linked_id: Option, } -impl KeyEncryptable for FieldView { +impl KeyEncryptable for FieldView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Field { name: self.name.encrypt_with_key(key)?, @@ -52,7 +52,7 @@ impl KeyEncryptable for FieldView { } } -impl KeyDecryptable for Field { +impl KeyDecryptable for Field { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FieldView { name: self.name.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index 922d2aee6..c3082ccc9 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -55,7 +55,7 @@ pub struct IdentityView { pub license_number: Option, } -impl KeyEncryptable for IdentityView { +impl KeyEncryptable for IdentityView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Identity { title: self.title.encrypt_with_key(key)?, @@ -80,7 +80,7 @@ impl KeyEncryptable for IdentityView { } } -impl KeyDecryptable for Identity { +impl KeyDecryptable for Identity { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(IdentityView { title: self.title.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/local_data.rs b/crates/bitwarden/src/vault/cipher/local_data.rs index 8d5fe8694..a3d0bf519 100644 --- a/crates/bitwarden/src/vault/cipher/local_data.rs +++ b/crates/bitwarden/src/vault/cipher/local_data.rs @@ -22,7 +22,7 @@ pub struct LocalDataView { last_launched: Option, } -impl KeyEncryptable for LocalDataView { +impl KeyEncryptable for LocalDataView { fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalData { last_used_date: self.last_used_date, @@ -31,7 +31,7 @@ impl KeyEncryptable for LocalDataView { } } -impl KeyDecryptable for LocalData { +impl KeyDecryptable for LocalData { fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalDataView { last_used_date: self.last_used_date, diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index da71f963f..8342fba53 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -64,7 +64,7 @@ pub struct LoginView { pub autofill_on_page_load: Option, } -impl KeyEncryptable for LoginUriView { +impl KeyEncryptable for LoginUriView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUri { uri: self.uri.encrypt_with_key(key)?, @@ -73,7 +73,7 @@ impl KeyEncryptable for LoginUriView { } } -impl KeyEncryptable for LoginView { +impl KeyEncryptable for LoginView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Login { username: self.username.encrypt_with_key(key)?, @@ -86,7 +86,7 @@ impl KeyEncryptable for LoginView { } } -impl KeyDecryptable for LoginUri { +impl KeyDecryptable for LoginUri { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUriView { uri: self.uri.decrypt_with_key(key)?, @@ -95,7 +95,7 @@ impl KeyDecryptable for LoginUri { } } -impl KeyDecryptable for Login { +impl KeyDecryptable for Login { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginView { username: self.username.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden/src/vault/cipher/secure_note.rs index 5fbb81811..ee24b74b3 100644 --- a/crates/bitwarden/src/vault/cipher/secure_note.rs +++ b/crates/bitwarden/src/vault/cipher/secure_note.rs @@ -29,7 +29,7 @@ pub struct SecureNoteView { r#type: SecureNoteType, } -impl KeyEncryptable for SecureNoteView { +impl KeyEncryptable for SecureNoteView { fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNote { r#type: self.r#type, @@ -37,7 +37,7 @@ impl KeyEncryptable for SecureNoteView { } } -impl KeyDecryptable for SecureNote { +impl KeyDecryptable for SecureNote { fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNoteView { r#type: self.r#type, diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index 3ff5d1350..8b39a9d22 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -46,7 +46,7 @@ impl LocateKey for Collection { enc.get_key(&Some(self.organization_id)) } } -impl KeyDecryptable for Collection { +impl KeyDecryptable for Collection { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CollectionView { id: self.id, diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index 04a4bebf7..f41e0e2b0 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -28,7 +28,7 @@ pub struct FolderView { } impl LocateKey for FolderView {} -impl KeyEncryptable for FolderView { +impl KeyEncryptable for FolderView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Folder { id: self.id, @@ -39,7 +39,7 @@ impl KeyEncryptable for FolderView { } impl LocateKey for Folder {} -impl KeyDecryptable for Folder { +impl KeyDecryptable for Folder { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FolderView { id: self.id, diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index 786c7a8f2..4475f1cff 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -25,7 +25,7 @@ pub struct PasswordHistoryView { } impl LocateKey for PasswordHistoryView {} -impl KeyEncryptable for PasswordHistoryView { +impl KeyEncryptable for PasswordHistoryView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(PasswordHistory { password: self.password.encrypt_with_key(key)?, @@ -35,7 +35,7 @@ impl KeyEncryptable for PasswordHistoryView { } impl LocateKey for PasswordHistory {} -impl KeyDecryptable for PasswordHistory { +impl KeyDecryptable for PasswordHistory { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(PasswordHistoryView { password: self.password.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index d8a18a8f6..742d3c592 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -156,7 +156,7 @@ impl Send { } } -impl KeyDecryptable for SendText { +impl KeyDecryptable for SendText { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendTextView { text: self.text.decrypt_with_key(key)?, @@ -165,7 +165,7 @@ impl KeyDecryptable for SendText { } } -impl KeyEncryptable for SendTextView { +impl KeyEncryptable for SendTextView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendText { text: self.text.encrypt_with_key(key)?, @@ -174,7 +174,7 @@ impl KeyEncryptable for SendTextView { } } -impl KeyDecryptable for SendFile { +impl KeyDecryptable for SendFile { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendFileView { id: self.id.clone(), @@ -185,7 +185,7 @@ impl KeyDecryptable for SendFile { } } -impl KeyEncryptable for SendFileView { +impl KeyEncryptable for SendFileView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendFile { id: self.id.clone(), @@ -197,7 +197,7 @@ impl KeyEncryptable for SendFileView { } impl LocateKey for Send {} -impl KeyDecryptable for Send { +impl KeyDecryptable for Send { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key @@ -230,7 +230,7 @@ impl KeyDecryptable for Send { } } -impl KeyDecryptable for Send { +impl KeyDecryptable for Send { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key @@ -253,7 +253,7 @@ impl KeyDecryptable for Send { } impl LocateKey for SendView {} -impl KeyEncryptable for SendView { +impl KeyEncryptable for SendView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key