Skip to content

Commit

Permalink
Create a separate NewType, PinKey for pins
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Feb 6, 2024
1 parent 301c6f4 commit facce07
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 100 deletions.
102 changes: 7 additions & 95 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,31 +57,21 @@ 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(),
stretched_key.mac_key.as_ref().unwrap(),
&stretched_key.key,
)
}

pub fn encrypt(&self, data: &[u8]) -> Result<EncString> {
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.
Expand All @@ -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<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 @@ -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();
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;
38 changes: 38 additions & 0 deletions crates/bitwarden-crypto/src/keys/pin_key.rs
Original file line number Diff line number Diff line change
@@ -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)
}

Check warning on line 16 in crates/bitwarden-crypto/src/keys/pin_key.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/keys/pin_key.rs#L14-L16

Added lines #L14 - L16 were not covered by tests

/// Derives a users master 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()
);
}
}
8 changes: 4 additions & 4 deletions crates/bitwarden-exporters/src/encrypted_json.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -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)?)
Expand Down

0 comments on commit facce07

Please sign in to comment.