Skip to content

Commit

Permalink
Merge branch 'master' into ps/export-scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton authored Sep 4, 2023
2 parents 2a62534 + 1d3bae3 commit 4b71b15
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 199 deletions.
2 changes: 1 addition & 1 deletion crates/bitwarden/src/auth/login/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async fn determine_password_hash(
) -> Result<String> {
let pre_login = request_prelogin(client, email.to_owned()).await?;
let auth_settings = AuthSettings::new(pre_login, email.to_owned());
let password_hash = auth_settings.make_user_password_hash(password)?;
let password_hash = auth_settings.derive_user_password_hash(password)?;
client.set_auth_settings(auth_settings);

Ok(password_hash)
Expand Down
79 changes: 8 additions & 71 deletions crates/bitwarden/src/client/auth_settings.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use std::num::NonZeroU32;

use base64::Engine;
#[cfg(feature = "internal")]
use bitwarden_api_identity::models::{KdfType, PreloginResponseModel};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[cfg(feature = "internal")]
use crate::{
crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE},
crypto::{HashPurpose, MasterKey},
error::Result,
util::BASE64_ENGINE,
};

#[derive(Debug)]
pub(crate) struct AuthSettings {
#[cfg(feature = "internal")]
pub email: String,
#[cfg(feature = "internal")]
pub(crate) kdf: Kdf,
}

Expand Down Expand Up @@ -66,73 +67,9 @@ impl AuthSettings {
Self { email, kdf }
}

pub fn make_user_password_hash(&self, password: &str) -> Result<String> {
self.make_password_hash(password, &self.email)
}

pub fn make_password_hash(&self, password: &str, salt: &str) -> Result<String> {
let hash: [u8; 32] =
crate::crypto::hash_kdf(password.as_bytes(), salt.as_bytes(), &self.kdf)?;

// Server expects hash + 1 iteration
let login_hash = pbkdf2::pbkdf2_array::<PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE>(
&hash,
password.as_bytes(),
1,
)
.expect("hash is a valid fixed size");

Ok(BASE64_ENGINE.encode(login_hash))
}
}

#[cfg(test)]
mod tests {
use bitwarden_api_identity::models::{KdfType, PreloginResponseModel};

use super::AuthSettings;

#[test]
fn test_password_hash_pbkdf2() {
let res = PreloginResponseModel {
kdf: Some(KdfType::Variant0),
kdf_iterations: Some(100_000),
kdf_memory: None,
kdf_parallelism: None,
};
let settings = AuthSettings::new(res, "test@bitwarden.com".into());

assert_eq!(
settings
.make_password_hash("asdfasdf", "test_salt")
.unwrap(),
"ZF6HjxUTSyBHsC+HXSOhZoXN+UuMnygV5YkWXCY4VmM="
);
assert_eq!(
settings.make_user_password_hash("asdfasdf").unwrap(),
"wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw="
);
}

#[test]
fn test_password_hash_argon2id() {
let res = PreloginResponseModel {
kdf: Some(KdfType::Variant1),
kdf_iterations: Some(4),
kdf_memory: Some(32),
kdf_parallelism: Some(2),
};
let settings = AuthSettings::new(res, "test@bitwarden.com".into());

assert_eq!(
settings
.make_password_hash("asdfasdf", "test_salt")
.unwrap(),
"PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ="
);
assert_eq!(
settings.make_user_password_hash("asdfasdf").unwrap(),
"ImYMPyd/X7FPrWzbt+wRfmlICWTA25yZrOob4TBMEZw="
);
#[cfg(feature = "internal")]
pub fn derive_user_password_hash(&self, password: &str) -> Result<String> {
let master_key = MasterKey::derive(password.as_bytes(), self.email.as_bytes(), &self.kdf)?;
master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
}
}
24 changes: 6 additions & 18 deletions crates/bitwarden/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,13 @@ impl EncryptionSettings {
user_key: EncString,
private_key: EncString,
) -> Result<Self> {
use crate::crypto::decrypt_aes256;

// Stretch keys from the provided password
let (key, mac_key) = crate::crypto::stretch_key_password(
password.as_bytes(),
auth.email.as_bytes(),
&auth.kdf,
)?;

// Decrypt the user key with the stretched key
let user_key = {
let (iv, mac, data) = match user_key {
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => (iv, mac, data),
_ => return Err(CryptoError::InvalidKey.into()),
};
use crate::crypto::MasterKey;

let dec = decrypt_aes256(&iv, &mac, data, Some(mac_key), key)?;
SymmetricCryptoKey::try_from(dec.as_slice())?
};
// Derive master key from password
let master_key = MasterKey::derive(password.as_bytes(), auth.email.as_bytes(), &auth.kdf)?;

// Decrypt the user key
let user_key = master_key.decrypt_user_key(user_key)?;

// Decrypt the private key with the user key
let private_key = {
Expand Down
1 change: 0 additions & 1 deletion crates/bitwarden/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pub(crate) use client::*;
pub(crate) mod access_token;
#[cfg(any(feature = "internal", feature = "mobile"))]
pub mod auth_settings;
#[allow(clippy::module_inception)]
mod client;
Expand Down
33 changes: 15 additions & 18 deletions crates/bitwarden/src/crypto/aes_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,7 @@ use crate::{
error::{CryptoError, Result},
};

pub fn decrypt_aes256(
iv: &[u8; 16],
mac: &[u8; 32],
data: Vec<u8>,
mac_key: Option<GenericArray<u8, U32>>,
key: GenericArray<u8, U32>,
) -> Result<Vec<u8>> {
let mac_key = match mac_key {
Some(k) => k,
None => return Err(CryptoError::InvalidMac.into()),
};

// Validate HMAC
let res = validate_mac(&mac_key, iv, &data)?;
if res != *mac {
return Err(CryptoError::InvalidMac.into());
}

pub fn decrypt_aes256(iv: &[u8; 16], data: Vec<u8>, key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
// Decrypt data
let iv = GenericArray::from_slice(iv);
let mut data = data;
Expand All @@ -42,6 +25,20 @@ pub fn decrypt_aes256(
Ok(data)
}

pub fn decrypt_aes256_hmac(
iv: &[u8; 16],
mac: &[u8; 32],
data: Vec<u8>,
mac_key: GenericArray<u8, U32>,
key: GenericArray<u8, U32>,
) -> Result<Vec<u8>> {
let res = validate_mac(&mac_key, iv, &data)?;
if res != *mac {
return Err(CryptoError::InvalidMac.into());
}
decrypt_aes256(iv, data, key)
}

pub fn encrypt_aes256(
data_dec: &[u8],
mac_key: Option<GenericArray<u8, U32>>,
Expand Down
65 changes: 20 additions & 45 deletions crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use uuid::Uuid;

use crate::{
client::encryption_settings::EncryptionSettings,
crypto::{decrypt_aes256, Decryptable, Encryptable, SymmetricCryptoKey},
crypto::{decrypt_aes256_hmac, Decryptable, Encryptable, SymmetricCryptoKey},
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
};
Expand Down Expand Up @@ -184,48 +184,22 @@ impl EncString {

impl Display for EncString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.", self.enc_type())?;

let mut parts = Vec::<&[u8]>::new();

match self {
EncString::AesCbc256_B64 { iv, data } => {
parts.push(iv);
parts.push(data);
}
EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => {
parts.push(iv);
parts.push(data);
parts.push(mac);
}
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => {
parts.push(iv);
parts.push(data);
parts.push(mac);
}
EncString::Rsa2048_OaepSha256_B64 { data } => {
parts.push(data);
}
EncString::Rsa2048_OaepSha1_B64 { data } => {
parts.push(data);
}
EncString::Rsa2048_OaepSha256_HmacSha256_B64 { mac, data } => {
parts.push(data);
parts.push(mac);
}
EncString::Rsa2048_OaepSha1_HmacSha256_B64 { mac, data } => {
parts.push(data);
parts.push(mac);
}
}

for i in 0..parts.len() {
if i == parts.len() - 1 {
write!(f, "{}", BASE64_ENGINE.encode(parts[i]))?;
} else {
write!(f, "{}|", BASE64_ENGINE.encode(parts[i]))?;
}
}
let parts: Vec<&[u8]> = match self {
EncString::AesCbc256_B64 { iv, data } => vec![iv, data],
EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac],
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac],
EncString::Rsa2048_OaepSha256_B64 { data } => vec![data],
EncString::Rsa2048_OaepSha1_B64 { data } => vec![data],
EncString::Rsa2048_OaepSha256_HmacSha256_B64 { mac, data } => vec![data, mac],
EncString::Rsa2048_OaepSha1_HmacSha256_B64 { mac, data } => vec![data, mac],
};

let encoded_parts: Vec<String> = parts
.iter()
.map(|part| BASE64_ENGINE.encode(part))
.collect();

write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?;

Ok(())
}
Expand All @@ -241,7 +215,7 @@ impl<'de> Deserialize<'de> for EncString {
type Value = EncString;

fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "A valid string")
write!(f, "a valid string")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
Expand Down Expand Up @@ -281,7 +255,8 @@ impl EncString {
pub fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
match self {
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => {
let dec = decrypt_aes256(iv, mac, data.clone(), key.mac_key, key.key)?;
let mac_key = key.mac_key.ok_or(CryptoError::InvalidMac)?;
let dec = decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, key.key)?;
Ok(dec)
}
_ => Err(CryptoError::InvalidKey.into()),
Expand Down
Loading

0 comments on commit 4b71b15

Please sign in to comment.