diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 097e41d38..aff403c59 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -1,16 +1,11 @@ -use std::{num::NonZeroU32, pin::Pin}; +use std::num::NonZeroU32; -use aes::cipher::typenum::U32; use base64::{engine::general_purpose::STANDARD, Engine}; -use generic_array::GenericArray; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sha2::Digest; -use crate::{ - util::{self, hkdf_expand}, - EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey, -}; +use super::utils::{derive_kdf_key, stretch_kdf_key}; +use crate::{util, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -45,7 +40,7 @@ impl MasterKey { /// Derives a users master key from their password, email and KDF. pub fn derive(password: &[u8], email: &[u8], kdf: &Kdf) -> Result { - derive_key(password, email, kdf).map(Self) + derive_kdf_key(password, email, kdf).map(Self) } /// Derive the master key hash, used for local and remote password validation. @@ -62,14 +57,14 @@ impl MasterKey { /// Decrypt the users user key pub fn decrypt_user_key(&self, user_key: EncString) -> Result { - let stretched_key = stretch_master_key(self)?; + 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()) } pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { - let stretched_key = stretch_master_key(self)?; + let stretched_key = stretch_kdf_key(&self.0)?; EncString::encrypt_aes256_hmac( user_key.to_vec().as_slice(), @@ -77,16 +72,6 @@ impl MasterKey { &stretched_key.key, ) } - - pub fn encrypt(&self, data: &[u8]) -> Result { - let stretched_key = stretch_master_key(self)?; - - EncString::encrypt_aes256_hmac( - data, - stretched_key.mac_key.as_ref().unwrap(), - &stretched_key.key, - ) - } } /// Generate a new random user key and encrypt it with the master key. @@ -99,55 +84,13 @@ fn make_user_key( Ok((UserKey::new(user_key), protected)) } -/// Derive a generic key from a secret and salt using the provided KDF. -fn derive_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { - let mut hash = match kdf { - Kdf::PBKDF2 { iterations } => crate::util::pbkdf2(secret, salt, iterations.get()), - - Kdf::Argon2id { - iterations, - memory, - parallelism, - } => { - use argon2::*; - - let argon = Argon2::new( - Algorithm::Argon2id, - Version::V0x13, - Params::new( - memory.get() * 1024, // Convert MiB to KiB - iterations.get(), - parallelism.get(), - Some(32), - ) - .unwrap(), - ); - - let salt_sha = sha2::Sha256::new().chain_update(salt).finalize(); - - let mut hash = [0u8; 32]; - argon - .hash_password_into(secret, &salt_sha, &mut hash) - .unwrap(); - hash - } - }; - SymmetricCryptoKey::try_from(hash.as_mut_slice()) -} - -fn stretch_master_key(master_key: &MasterKey) -> Result { - let key: Pin>> = hkdf_expand(&master_key.0.key, Some("enc"))?; - let mac_key: Pin>> = hkdf_expand(&master_key.0.key, Some("mac"))?; - Ok(SymmetricCryptoKey::new(key, Some(mac_key))) -} - #[cfg(test)] mod tests { use std::num::NonZeroU32; use rand::SeedableRng; - use super::{make_user_key, stretch_master_key, HashPurpose, Kdf, MasterKey}; + use super::{make_user_key, HashPurpose, Kdf, MasterKey}; use crate::{keys::symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}; #[test] @@ -194,37 +137,6 @@ mod tests { assert_eq!(None, master_key.0.mac_key); } - #[test] - fn test_stretch_master_key() { - let master_key = MasterKey(SymmetricCryptoKey::new( - Box::pin( - [ - 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, - 167, 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, - ] - .into(), - ), - None, - )); - - let stretched = stretch_master_key(&master_key).unwrap(); - - assert_eq!( - [ - 111, 31, 178, 45, 238, 152, 37, 114, 143, 215, 124, 83, 135, 173, 195, 23, 142, - 134, 120, 249, 61, 132, 163, 182, 113, 197, 189, 204, 188, 21, 237, 96 - ], - stretched.key.as_slice() - ); - assert_eq!( - [ - 221, 127, 206, 234, 101, 27, 202, 38, 86, 52, 34, 28, 78, 28, 185, 16, 48, 61, 127, - 166, 209, 247, 194, 87, 232, 26, 48, 85, 193, 249, 179, 155 - ], - stretched.mac_key.as_ref().unwrap().as_slice() - ); - } - #[test] fn test_password_hash_pbkdf2() { let password = "asdfasdf".as_bytes(); diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 285e58b55..5bfd32b8b 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -12,8 +12,10 @@ mod asymmetric_crypto_key; pub use asymmetric_crypto_key::{ AsymmetricCryptoKey, AsymmetricEncryptable, AsymmetricPublicCryptoKey, }; - mod user_key; pub use user_key::UserKey; mod device_key; pub use device_key::{DeviceKey, TrustDeviceResponse}; +mod pin_key; +pub use pin_key::PinKey; +mod utils; diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs new file mode 100644 index 000000000..c5af424d8 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -0,0 +1,38 @@ +use crate::{ + keys::{key_encryptable::CryptoKey, utils::derive_kdf_key}, + EncString, Kdf, KeyEncryptable, Result, SymmetricCryptoKey, +}; + +use super::utils::stretch_kdf_key; + +/// Pin Key. +/// +/// Derived from a specific password, used for pin encryption and exports. +pub struct PinKey(SymmetricCryptoKey); + +impl PinKey { + pub fn new(key: SymmetricCryptoKey) -> Self { + Self(key) + } + + /// Derives a users master key from their password, email and KDF. + pub fn derive(password: &[u8], salt: &[u8], kdf: &Kdf) -> Result { + derive_kdf_key(password, salt, kdf).map(Self) + } +} + +impl CryptoKey for PinKey {} + +impl KeyEncryptable for &[u8] { + fn encrypt_with_key(self, key: &PinKey) -> Result { + let stretched_key = stretch_kdf_key(&key.0)?; + + self.encrypt_with_key(&stretched_key) + } +} + +impl KeyEncryptable for String { + fn encrypt_with_key(self, key: &PinKey) -> Result { + self.as_bytes().encrypt_with_key(key) + } +} diff --git a/crates/bitwarden-crypto/src/keys/utils.rs b/crates/bitwarden-crypto/src/keys/utils.rs new file mode 100644 index 000000000..d83e212d0 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/utils.rs @@ -0,0 +1,85 @@ +use std::pin::Pin; + +use generic_array::{typenum::U32, GenericArray}; +use sha2::Digest; + +use crate::{util::hkdf_expand, Kdf, Result, SymmetricCryptoKey}; + +/// Derive a generic key from a secret and salt using the provided KDF. +pub(super) fn derive_kdf_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { + let mut hash = match kdf { + Kdf::PBKDF2 { iterations } => crate::util::pbkdf2(secret, salt, iterations.get()), + + Kdf::Argon2id { + iterations, + memory, + parallelism, + } => { + use argon2::*; + + let argon = Argon2::new( + Algorithm::Argon2id, + Version::V0x13, + Params::new( + memory.get() * 1024, // Convert MiB to KiB + iterations.get(), + parallelism.get(), + Some(32), + ) + .unwrap(), + ); + + let salt_sha = sha2::Sha256::new().chain_update(salt).finalize(); + + let mut hash = [0u8; 32]; + argon + .hash_password_into(secret, &salt_sha, &mut hash) + .unwrap(); + hash + } + }; + SymmetricCryptoKey::try_from(hash.as_mut_slice()) +} + +pub(super) fn stretch_kdf_key(k: &SymmetricCryptoKey) -> Result { + let key: Pin>> = hkdf_expand(&k.key, Some("enc"))?; + let mac_key: Pin>> = hkdf_expand(&k.key, Some("mac"))?; + + Ok(SymmetricCryptoKey::new(key, Some(mac_key))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stretch_kdf_key() { + let key = SymmetricCryptoKey::new( + Box::pin( + [ + 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, + 167, 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, + ] + .into(), + ), + None, + ); + + let stretched = stretch_kdf_key(&key).unwrap(); + + assert_eq!( + [ + 111, 31, 178, 45, 238, 152, 37, 114, 143, 215, 124, 83, 135, 173, 195, 23, 142, + 134, 120, 249, 61, 132, 163, 182, 113, 197, 189, 204, 188, 21, 237, 96 + ], + stretched.key.as_slice() + ); + assert_eq!( + [ + 221, 127, 206, 234, 101, 27, 202, 38, 86, 52, 34, 28, 78, 28, 185, 16, 48, 61, 127, + 166, 209, 247, 194, 87, 232, 26, 48, 85, 193, 249, 179, 155 + ], + stretched.mac_key.as_ref().unwrap().as_slice() + ); + } +} diff --git a/crates/bitwarden-exporters/src/encrypted_json.rs b/crates/bitwarden-exporters/src/encrypted_json.rs index 29aeeffb0..1bbfd2660 100644 --- a/crates/bitwarden-exporters/src/encrypted_json.rs +++ b/crates/bitwarden-exporters/src/encrypted_json.rs @@ -1,5 +1,5 @@ use base64::{engine::general_purpose::STANDARD, Engine}; -use bitwarden_crypto::{generate_random_bytes, Kdf, MasterKey}; +use bitwarden_crypto::{generate_random_bytes, Kdf, KeyEncryptable, PinKey}; use serde::Serialize; use thiserror::Error; use uuid::Uuid; @@ -45,7 +45,7 @@ pub(crate) fn export_encrypted_json( let salt: [u8; 16] = generate_random_bytes(); let salt = STANDARD.encode(salt); - let key = MasterKey::derive(password.as_bytes(), salt.as_bytes(), &kdf)?; + let key = PinKey::derive(password.as_bytes(), salt.as_bytes(), &kdf)?; let enc_key_validation = Uuid::new_v4().to_string(); @@ -57,8 +57,8 @@ pub(crate) fn export_encrypted_json( kdf_iterations, kdf_memory, kdf_parallelism, - enc_key_validation: key.encrypt(enc_key_validation.as_bytes())?.to_string(), - data: key.encrypt(decrypted_export.as_bytes())?.to_string(), + enc_key_validation: enc_key_validation.encrypt_with_key(&key)?.to_string(), + data: decrypted_export.encrypt_with_key(&key)?.to_string(), }; Ok(serde_json::to_string_pretty(&encrypted_export)?)