Skip to content

Commit b091af6

Browse files
quextendani-garciaHinton
authored
[PM-17370] Refactor symmetric keys to use enum (#127)
## 🎟️ Tracking https://bitwarden.atlassian.net/browse/PM-17370 ## 📔 Objective Symmetric crypto keys (in general) are just random arrays of data - in contrast to asymmetric keys which require specific structure. Still, it is cryptographic best practice to split these keys apart entirely, and not have them confused. An algorithm switch should include a change to a new key type. For the upcoming xchacha cipher, we will introduce a symmetric key that is the same size as the old hmac-less cbc type 0 cipher. In the sdk we currently just represent this as one key with two byte arrays, "key" and "mac key" which is confusing and could lead to mis-usage, which can lead to bugs or even cryptographic flaws. For this reason, it is paramount to enforce the key types in the type system. This PR refactors the current key usage to represent symmetric keys as an enum, with the two types being the current encstring type 0 and type 2, each of which has it's own struct with the data it needs. In a later PR, the serialization format for symmetric keys will be adjusted to include the cipher type, so keys are not categorized based on the size alone. ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## 🦮 Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - 👍 (`:+1:`) or similar for great changes - 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info - ❓ (`:question:`) for questions - 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - 🎨 (`:art:`) for suggestions / improvements - ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention - 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt - ⛏ (`:pick:`) for minor or nitpick changes --------- Co-authored-by: Daniel García <[email protected]> Co-authored-by: Oscar Hinton <[email protected]>
1 parent 8bb4d03 commit b091af6

File tree

17 files changed

+518
-448
lines changed

17 files changed

+518
-448
lines changed

crates/bitwarden-core/src/auth/access_token.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl FromStr for AccessToken {
7575
Ok(AccessToken {
7676
access_token_id,
7777
client_secret: client_secret.to_owned(),
78-
encryption_key,
78+
encryption_key: SymmetricCryptoKey::Aes256CbcHmacKey(encryption_key),
7979
})
8080
}
8181
}

crates/bitwarden-core/src/auth/auth_request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub(crate) fn auth_request_decrypt_master_key(
7070

7171
let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?;
7272
let mut master_key: Vec<u8> = master_key.decrypt_with_key(&key)?;
73-
let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key.as_mut_slice())?);
73+
let master_key = MasterKey::try_from(master_key.as_mut_slice())?;
7474

7575
Ok(master_key.decrypt_user_key(user_key)?)
7676
}

crates/bitwarden-core/src/mobile/crypto.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ pub async fn initialize_user_crypto(
177177
master_key,
178178
user_key,
179179
} => {
180-
let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key)?);
180+
let mut master_key_bytes = STANDARD
181+
.decode(master_key)
182+
.map_err(|_| CryptoError::InvalidKey)?;
183+
let master_key = MasterKey::try_from(master_key_bytes.as_mut_slice())?;
181184
let user_key: EncString = user_key.parse()?;
182185

183186
client
@@ -484,7 +487,7 @@ pub fn verify_asymmetric_keys(
484487
.to_public_der()
485488
.map_err(VerifyError::PublicFailed)?;
486489

487-
let derived_public_key = STANDARD.encode(&derived_public_key_vec);
490+
let derived_public_key = STANDARD.encode(derived_public_key_vec);
488491

489492
if derived_public_key != request.user_public_key {
490493
return Err(VerifyError::KeyMismatch);

crates/bitwarden-crypto/src/aes.rs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,8 @@ pub(crate) fn decrypt_aes256_hmac(
5757
decrypt_aes256(iv, data, key)
5858
}
5959

60-
/// Encrypt using AES-256 in CBC mode.
61-
///
62-
/// Behaves similar to [encrypt_aes256_hmac], but does't generate a MAC.
63-
///
64-
/// ## Returns
65-
///
66-
/// A AesCbc256_B64 EncString
67-
#[allow(unused)]
68-
pub(crate) fn encrypt_aes256(data_dec: &[u8], key: &GenericArray<u8, U32>) -> ([u8; 16], Vec<u8>) {
69-
let rng = rand::thread_rng();
70-
let (iv, data) = encrypt_aes256_internal(rng, data_dec, key);
71-
72-
(iv, data)
73-
}
74-
7560
/// Encrypt using AES-256 in CBC mode with MAC.
7661
///
77-
/// Behaves similar to [encrypt_aes256], but also generate a MAC.
78-
///
7962
/// ## Returns
8063
///
8164
/// A AesCbc256_HmacSha256_B64 EncString
@@ -94,7 +77,6 @@ pub(crate) fn encrypt_aes256_hmac(
9477
/// Encrypt using AES-256 in CBC mode.
9578
///
9679
/// Used internally by:
97-
/// - [encrypt_aes256]
9880
/// - [encrypt_aes256_hmac]
9981
fn encrypt_aes256_internal(
10082
mut rng: impl rand::RngCore,
@@ -185,15 +167,4 @@ mod tests {
185167

186168
assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!");
187169
}
188-
189-
#[test]
190-
fn test_encrypt_decrypt_aes256() {
191-
let key = generate_generic_array(0, 1);
192-
let data = "EncryptMe!";
193-
194-
let (iv, encrypted) = encrypt_aes256(data.as_bytes(), &key);
195-
let decrypted = decrypt_aes256(&iv, encrypted, &key).unwrap();
196-
197-
assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!");
198-
}
199170
}

crates/bitwarden-crypto/src/enc_string/symmetric.rs

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use std::{fmt::Display, str::FromStr};
22

3-
use aes::cipher::typenum::U32;
43
use base64::{engine::general_purpose::STANDARD, Engine};
5-
use generic_array::GenericArray;
64
use serde::Deserialize;
75

86
use super::{check_length, from_b64, from_b64_vec, split_enc_string};
97
use crate::{
10-
error::{CryptoError, EncStringParseError, Result},
11-
KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
8+
error::{CryptoError, EncStringParseError, Result, UnsupportedOperation},
9+
Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
1210
};
1311

1412
#[cfg(feature = "wasm")]
@@ -195,10 +193,10 @@ impl serde::Serialize for EncString {
195193
impl EncString {
196194
pub(crate) fn encrypt_aes256_hmac(
197195
data_dec: &[u8],
198-
mac_key: &GenericArray<u8, U32>,
199-
key: &GenericArray<u8, U32>,
196+
key: &Aes256CbcHmacKey,
200197
) -> Result<EncString> {
201-
let (iv, mac, data) = crate::aes::encrypt_aes256_hmac(data_dec, mac_key, key)?;
198+
let (iv, mac, data) =
199+
crate::aes::encrypt_aes256_hmac(data_dec, &key.mac_key, &key.enc_key)?;
202200
Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data })
203201
}
204202

@@ -213,31 +211,26 @@ impl EncString {
213211

214212
impl KeyEncryptable<SymmetricCryptoKey, EncString> for &[u8] {
215213
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
216-
EncString::encrypt_aes256_hmac(
217-
self,
218-
key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?,
219-
&key.key,
220-
)
214+
match key {
215+
SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key),
216+
SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
217+
UnsupportedOperation::EncryptionNotImplementedForKey,
218+
)),
219+
}
221220
}
222221
}
223222

224223
impl KeyDecryptable<SymmetricCryptoKey, Vec<u8>> for EncString {
225224
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
226-
match self {
227-
EncString::AesCbc256_B64 { iv, data } => {
228-
if key.mac_key.is_some() {
229-
return Err(CryptoError::MacNotProvided);
230-
}
231-
232-
let dec = crate::aes::decrypt_aes256(iv, data.clone(), &key.key)?;
233-
Ok(dec)
234-
}
235-
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => {
236-
let mac_key = key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?;
237-
let dec =
238-
crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, &key.key)?;
239-
Ok(dec)
225+
match (self, key) {
226+
(EncString::AesCbc256_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => {
227+
crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key)
240228
}
229+
(
230+
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data },
231+
SymmetricCryptoKey::Aes256CbcHmacKey(key),
232+
) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key),
233+
_ => Err(CryptoError::WrongKeyType),
241234
}
242235
}
243236
}
@@ -284,7 +277,7 @@ mod tests {
284277

285278
#[test]
286279
fn test_enc_string_roundtrip() {
287-
let key = derive_symmetric_key("test");
280+
let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
288281

289282
let test_string = "encrypted_test_string";
290283
let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap();
@@ -295,7 +288,7 @@ mod tests {
295288

296289
#[test]
297290
fn test_enc_string_ref_roundtrip() {
298-
let key = derive_symmetric_key("test");
291+
let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
299292

300293
let test_string = "encrypted_test_string";
301294
let cipher = test_string.encrypt_with_key(&key).unwrap();
@@ -390,7 +383,7 @@ mod tests {
390383
assert_eq!(enc_string.enc_type(), 0);
391384

392385
let result: Result<String, CryptoError> = enc_string.decrypt_with_key(&key);
393-
assert!(matches!(result, Err(CryptoError::MacNotProvided)));
386+
assert!(matches!(result, Err(CryptoError::WrongKeyType)));
394387
}
395388

396389
#[test]

crates/bitwarden-crypto/src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ pub enum CryptoError {
4747

4848
#[error("Number is zero")]
4949
ZeroNumber,
50+
51+
#[error("Unsupported operation, {0}")]
52+
OperationNotSupported(UnsupportedOperation),
53+
54+
#[error("Key algorithm does not match encrypted data type")]
55+
WrongKeyType,
56+
}
57+
58+
#[derive(Debug, Error)]
59+
pub enum UnsupportedOperation {
60+
#[error("Encryption is not implemented for key")]
61+
EncryptionNotImplementedForKey,
5062
}
5163

5264
#[derive(Debug, Error)]

crates/bitwarden-crypto/src/keys/device_key.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ mod tests {
9191

9292
#[test]
9393
fn test_trust_device() {
94-
let key = derive_symmetric_key("test");
94+
let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
9595

9696
let result = DeviceKey::trust_device(&key).unwrap();
9797

@@ -103,8 +103,8 @@ mod tests {
103103
)
104104
.unwrap();
105105

106-
assert_eq!(key.key, decrypted.key);
107-
assert_eq!(key.mac_key, decrypted.mac_key);
106+
assert_eq!(key, decrypted);
107+
assert_eq!(key, decrypted);
108108
}
109109

110110
#[test]
@@ -134,7 +134,6 @@ mod tests {
134134
.decrypt_user_key(protected_device_private_key, protected_user_key)
135135
.unwrap();
136136

137-
assert_eq!(decrypted.key, user_key.key);
138-
assert_eq!(decrypted.mac_key, user_key.mac_key);
137+
assert_eq!(decrypted, user_key);
139138
}
140139
}

0 commit comments

Comments
 (0)