Skip to content

Commit

Permalink
[PM-3438] Encrypted export (#588)
Browse files Browse the repository at this point in the history
Implement encrypted export
  • Loading branch information
Hinton authored Feb 8, 2024
1 parent 2f9a9f6 commit aa30b65
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 106 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 7 additions & 85 deletions crates/bitwarden-crypto/src/keys/master_key.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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<Self> {
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.
Expand All @@ -62,14 +57,14 @@ impl MasterKey {

/// Decrypt the users user key
pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
let stretched_key = stretch_master_key(self)?;
let stretched_key = stretch_kdf_key(&self.0)?;

let mut dec: Vec<u8> = user_key.decrypt_with_key(&stretched_key)?;
SymmetricCryptoKey::try_from(dec.as_mut_slice())
}

pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> {
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(),
Expand All @@ -89,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<SymmetricCryptoKey> {
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<SymmetricCryptoKey> {
let key: Pin<Box<GenericArray<u8, U32>>> = hkdf_expand(&master_key.0.key, Some("enc"))?;
let mac_key: Pin<Box<GenericArray<u8, U32>>> = 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]
Expand Down Expand Up @@ -184,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();
Expand Down
4 changes: 3 additions & 1 deletion crates/bitwarden-crypto/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
39 changes: 39 additions & 0 deletions crates/bitwarden-crypto/src/keys/pin_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::{
keys::{
key_encryptable::CryptoKey,
utils::{derive_kdf_key, stretch_kdf_key},
},
EncString, Kdf, KeyEncryptable, Result, SymmetricCryptoKey,
};

/// 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 pin key from their password, email and KDF.
pub fn derive(password: &[u8], salt: &[u8], kdf: &Kdf) -> Result<Self> {
derive_kdf_key(password, salt, kdf).map(Self)
}
}

impl CryptoKey for PinKey {}

impl KeyEncryptable<PinKey, EncString> for &[u8] {
fn encrypt_with_key(self, key: &PinKey) -> Result<EncString> {
let stretched_key = stretch_kdf_key(&key.0)?;

self.encrypt_with_key(&stretched_key)
}
}

impl KeyEncryptable<PinKey, EncString> for String {
fn encrypt_with_key(self, key: &PinKey) -> Result<EncString> {
self.as_bytes().encrypt_with_key(key)
}
}
85 changes: 85 additions & 0 deletions crates/bitwarden-crypto/src/keys/utils.rs
Original file line number Diff line number Diff line change
@@ -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<SymmetricCryptoKey> {
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<SymmetricCryptoKey> {
let key: Pin<Box<GenericArray<u8, U32>>> = hkdf_expand(&k.key, Some("enc"))?;
let mac_key: Pin<Box<GenericArray<u8, U32>>> = 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()
);
}
}
4 changes: 3 additions & 1 deletion crates/bitwarden-exporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ rust-version = "1.57"
exclude = ["/resources"]

[dependencies]
base64 = ">=0.21.2, <0.22"
bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0" }
chrono = { version = ">=0.4.26, <0.5", features = [
"clock",
"serde",
Expand All @@ -23,4 +25,4 @@ csv = "1.3.0"
serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
thiserror = ">=1.0.40, <2.0"
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }
uuid = { version = ">=1.3.3, <2.0", features = ["serde", "v4"] }
Loading

0 comments on commit aa30b65

Please sign in to comment.