Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split Encryptable/Decryptable trait into encryption and key retrieval #297

Merged
merged 5 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bitwarden/src/auth/login/access_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse},
},
client::{AccessToken, LoginMethod, ServiceAccountLoginMethod},
crypto::{EncString, SymmetricCryptoKey},
crypto::{EncString, KeyDecryptable, SymmetricCryptoKey},
error::{Error, Result},
util::{decode_token, BASE64_ENGINE},
Client,
Expand All @@ -29,7 +29,7 @@ pub(crate) async fn access_token_login(
// Extract the encrypted payload and use the access token encryption key to decrypt it
let payload: EncString = r.encrypted_payload.parse()?;

let decrypted_payload = payload.decrypt_with_key(&access_token.encryption_key)?;
let decrypted_payload: Vec<u8> = payload.decrypt_with_key(&access_token.encryption_key)?;

// Once decrypted, we have to JSON decode to extract the organization encryption key
#[derive(serde::Deserialize)]
Expand Down
38 changes: 3 additions & 35 deletions crates/bitwarden/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rsa::RsaPrivateKey;
use uuid::Uuid;
#[cfg(feature = "internal")]
use {
crate::client::UserLoginMethod,
crate::{client::UserLoginMethod, crypto::KeyDecryptable},
rsa::{pkcs8::DecodePrivateKey, Oaep},
};

Expand Down Expand Up @@ -46,7 +46,7 @@ impl EncryptionSettings {

// Decrypt the private key with the user key
let private_key = {
let dec = private_key.decrypt_with_key(&user_key)?;
let dec: Vec<u8> = private_key.decrypt_with_key(&user_key)?;
Some(
rsa::RsaPrivateKey::from_pkcs8_der(&dec)
.map_err(|_| CryptoError::InvalidKey)?,
Expand Down Expand Up @@ -98,7 +98,7 @@ impl EncryptionSettings {
Ok(self)
}

fn get_key(&self, org_id: &Option<Uuid>) -> Option<&SymmetricCryptoKey> {
pub(crate) fn get_key(&self, org_id: &Option<Uuid>) -> Option<&SymmetricCryptoKey> {
// If we don't have a private key set (to decode multiple org keys), we just use the main user key
if self.private_key.is_none() {
return Some(&self.user_key);
Expand All @@ -110,42 +110,10 @@ impl EncryptionSettings {
}
}

pub(crate) fn decrypt_bytes(
&self,
cipher: &EncString,
org_id: &Option<Uuid>,
) -> Result<Vec<u8>> {
let key = self.get_key(org_id).ok_or(CryptoError::NoKeyForOrg)?;
cipher.decrypt_with_key(key)
}

pub(crate) fn decrypt(&self, cipher: &EncString, org_id: &Option<Uuid>) -> Result<String> {
let dec = self.decrypt_bytes(cipher, org_id)?;
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into())
}

pub(crate) fn encrypt(&self, data: &[u8], org_id: &Option<Uuid>) -> Result<EncString> {
let key = self.get_key(org_id).ok_or(CryptoError::NoKeyForOrg)?;

let dec = encrypt_aes256_hmac(data, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key)?;
Ok(dec)
}
}

#[cfg(test)]
mod tests {
use super::{EncryptionSettings, SymmetricCryptoKey};
use crate::crypto::{Decryptable, Encryptable};

#[test]
fn test_encryption_settings() {
let key = SymmetricCryptoKey::generate("test");
let settings = EncryptionSettings::new_single_key(key);

let test_string = "encrypted_test_string".to_string();
let cipher = test_string.clone().encrypt(&settings, &None).unwrap();

let decrypted_str = cipher.decrypt(&settings, &None).unwrap();
assert_eq!(decrypted_str, test_string);
}
}
57 changes: 40 additions & 17 deletions crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use std::{fmt::Display, str::FromStr};

use base64::Engine;
use serde::{de::Visitor, Deserialize};
use uuid::Uuid;

use crate::{
client::encryption_settings::EncryptionSettings,
crypto::{decrypt_aes256_hmac, Decryptable, Encryptable, SymmetricCryptoKey},
crypto::{decrypt_aes256_hmac, SymmetricCryptoKey},
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
};

use super::{KeyDecryptable, KeyEncryptable, LocateKey};

#[derive(Clone)]
#[allow(unused, non_camel_case_types)]
pub enum EncString {
Expand Down Expand Up @@ -304,8 +304,24 @@ impl EncString {
EncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6,
}
}
}

pub fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
fn invalid_len_error(expected: usize) -> impl Fn(Vec<u8>) -> EncStringParseError {
move |e: Vec<_>| EncStringParseError::InvalidLength {
expected,
got: e.len(),
}
}

impl LocateKey for EncString {}
impl KeyEncryptable<EncString> for &[u8] {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
super::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key)
}
}

impl KeyDecryptable<Vec<u8>> for EncString {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
match self {
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => {
let mac_key = key.mac_key.ok_or(CryptoError::InvalidMac)?;
Expand All @@ -317,29 +333,36 @@ impl EncString {
}
}

fn invalid_len_error(expected: usize) -> impl Fn(Vec<u8>) -> EncStringParseError {
move |e: Vec<_>| EncStringParseError::InvalidLength {
expected,
got: e.len(),
}
}

impl Encryptable<EncString> for String {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<EncString> {
enc.encrypt(self.as_bytes(), org_id)
impl KeyEncryptable<EncString> for String {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
self.as_bytes().encrypt_with_key(key)
}
}

impl Decryptable<String> for EncString {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<String> {
enc.decrypt(self, org_id)
impl KeyDecryptable<String> for EncString {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<String> {
let dec: Vec<u8> = self.decrypt_with_key(key)?;
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into())
}
}

#[cfg(test)]
mod tests {
use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey};

use super::EncString;

#[test]
fn test_enc_string_roundtrip() {
let key = SymmetricCryptoKey::generate("test");

let test_string = "encrypted_test_string".to_string();
let cipher = test_string.clone().encrypt_with_key(&key).unwrap();

let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
assert_eq!(decrypted_str, test_string);
}

#[test]
fn test_enc_string_serialization() {
#[derive(serde::Serialize, serde::Deserialize)]
Expand Down
47 changes: 26 additions & 21 deletions crates/bitwarden/src/crypto/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@ use std::{collections::HashMap, hash::Hash};

use uuid::Uuid;

use crate::{client::encryption_settings::EncryptionSettings, error::Result};
use crate::{
client::encryption_settings::EncryptionSettings,
error::{Error, Result},
};

use super::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey};

pub trait LocateKey {
fn locate_key<'a>(
&self,
enc: &'a EncryptionSettings,
org_id: &Option<Uuid>,
) -> Option<&'a SymmetricCryptoKey> {
enc.get_key(org_id)
}
}

pub trait Encryptable<Output> {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output>;
Expand All @@ -12,15 +27,17 @@ pub trait Decryptable<Output> {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output>;
}

impl<T: Encryptable<Output>, Output> Encryptable<Option<Output>> for Option<T> {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Option<Output>> {
self.map(|e| e.encrypt(enc, org_id)).transpose()
impl<T: KeyEncryptable<Output> + LocateKey, Output> Encryptable<Output> for T {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?;
self.encrypt_with_key(key)
}
}

impl<T: Decryptable<Output>, Output> Decryptable<Option<Output>> for Option<T> {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Option<Output>> {
self.as_ref().map(|e| e.decrypt(enc, org_id)).transpose()
impl<T: KeyDecryptable<Output> + LocateKey, Output> Decryptable<Output> for T {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?;
self.decrypt_with_key(key)
}
}

Expand All @@ -46,7 +63,7 @@ impl<T: Encryptable<Output>, Output, Id: Hash + Eq> Encryptable<HashMap<Id, Outp
) -> Result<HashMap<Id, Output>> {
self.into_iter()
.map(|(id, e)| Ok((id, e.encrypt(enc, org_id)?)))
.collect::<Result<HashMap<_, _>>>()
.collect()
}
}

Expand All @@ -60,18 +77,6 @@ impl<T: Decryptable<Output>, Output, Id: Hash + Eq + Copy> Decryptable<HashMap<I
) -> Result<HashMap<Id, Output>> {
self.iter()
.map(|(id, e)| Ok((*id, e.decrypt(enc, org_id)?)))
.collect::<Result<HashMap<_, _>>>()
}
}

impl<T: Encryptable<Output>, Output> Encryptable<Output> for Box<T> {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
(*self).encrypt(enc, org_id)
}
}

impl<T: Decryptable<Output>, Output> Decryptable<Output> for Box<T> {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
(**self).decrypt(enc, org_id)
.collect()
}
}
69 changes: 69 additions & 0 deletions crates/bitwarden/src/crypto/key_encryptable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::{collections::HashMap, hash::Hash};

use crate::error::Result;

use super::SymmetricCryptoKey;

pub trait KeyEncryptable<Output> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Output>;
}

pub trait KeyDecryptable<Output> {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Output>;
}

impl<T: KeyEncryptable<Output>, Output> KeyEncryptable<Option<Output>> for Option<T> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Option<Output>> {
self.map(|e| e.encrypt_with_key(key)).transpose()
}
}

impl<T: KeyDecryptable<Output>, Output> KeyDecryptable<Option<Output>> for Option<T> {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Option<Output>> {
self.as_ref().map(|e| e.decrypt_with_key(key)).transpose()
}
}

impl<T: KeyEncryptable<Output>, Output> KeyEncryptable<Output> for Box<T> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Output> {
(*self).encrypt_with_key(key)
}
}

impl<T: KeyDecryptable<Output>, Output> KeyDecryptable<Output> for Box<T> {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Output> {
(**self).decrypt_with_key(key)
}
}

impl<T: KeyEncryptable<Output>, Output> KeyEncryptable<Vec<Output>> for Vec<T> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Vec<Output>> {
self.into_iter().map(|e| e.encrypt_with_key(key)).collect()
}
}

impl<T: KeyDecryptable<Output>, Output> KeyDecryptable<Vec<Output>> for Vec<T> {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<Output>> {
self.iter().map(|e| e.decrypt_with_key(key)).collect()
}
}

impl<T: KeyEncryptable<Output>, Output, Id: Hash + Eq> KeyEncryptable<HashMap<Id, Output>>
for HashMap<Id, T>
{
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<HashMap<Id, Output>> {
self.into_iter()
.map(|(id, e)| Ok((id, e.encrypt_with_key(key)?)))
.collect()
}
}

impl<T: KeyDecryptable<Output>, Output, Id: Hash + Eq + Copy> KeyDecryptable<HashMap<Id, Output>>
for HashMap<Id, T>
{
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<HashMap<Id, Output>> {
self.iter()
.map(|(id, e)| Ok((*id, e.decrypt_with_key(key)?)))
.collect()
}
}
6 changes: 3 additions & 3 deletions crates/bitwarden/src/crypto/master_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use rand::Rng;
use sha2::Digest;

use super::{
encrypt_aes256, hkdf_expand, EncString, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey,
PBKDF_SHA256_HMAC_OUT_SIZE,
encrypt_aes256, hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, SymmetricCryptoKey,
UserKey, PBKDF_SHA256_HMAC_OUT_SIZE,
};
use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE};

Expand Down Expand Up @@ -53,7 +53,7 @@ impl MasterKey {
pub(crate) fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
let stretched_key = stretch_master_key(self)?;

let dec = user_key.decrypt_with_key(&stretched_key)?;
let dec: Vec<u8> = user_key.decrypt_with_key(&stretched_key)?;
SymmetricCryptoKey::try_from(dec.as_slice())
}
}
Expand Down
4 changes: 3 additions & 1 deletion crates/bitwarden/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ use crate::error::{Error, Result};
mod enc_string;
pub use enc_string::EncString;
mod encryptable;
pub use encryptable::{Decryptable, Encryptable};
pub use encryptable::{Decryptable, Encryptable, LocateKey};
mod key_encryptable;
pub use key_encryptable::{KeyDecryptable, KeyEncryptable};
mod aes_ops;
pub use aes_ops::{decrypt_aes256, decrypt_aes256_hmac, encrypt_aes256, encrypt_aes256_hmac};
mod symmetric_crypto_key;
Expand Down
Loading