Skip to content

Commit e6ecfe2

Browse files
authored
Merge branch 'main' into ps/PM-29492-api-middleware-support
2 parents 5edd78a + 6ad384a commit e6ecfe2

File tree

2 files changed

+121
-14
lines changed

2 files changed

+121
-14
lines changed

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,6 @@ pub(super) fn make_update_kdf(
323323
) -> Result<UpdateKdfResponse, CryptoClientError> {
324324
let key_store = client.internal.get_key_store();
325325
let ctx = key_store.context();
326-
// FIXME: [PM-18099] Once MasterKey deals with KeyIds, this should be updated
327-
#[allow(deprecated)]
328-
let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
329326

330327
let login_method = client
331328
.internal
@@ -341,8 +338,9 @@ pub(super) fn make_update_kdf(
341338

342339
let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email)
343340
.map_err(|_| CryptoClientError::InvalidKdfSettings)?;
344-
let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key)
345-
.map_err(|_| CryptoClientError::InvalidKdfSettings)?;
341+
let unlock_data =
342+
MasterPasswordUnlockData::derive(password, new_kdf, email, SymmetricKeyId::User, &ctx)
343+
.map_err(|_| CryptoClientError::InvalidKdfSettings)?;
346344
let old_authentication_data = MasterPasswordAuthenticationData::derive(
347345
password,
348346
&client
@@ -986,7 +984,7 @@ pub(crate) fn make_user_jit_master_password_registration(
986984
let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
987985

988986
let master_password_unlock_data =
989-
MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, &user_key)
987+
MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
990988
.map_err(MakeKeysError::MasterPasswordDerivation)?;
991989

992990
let master_password_authentication_data =

crates/bitwarden-core/src/key_management/master_password.rs

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use bitwarden_api_api::models::{
55
MasterPasswordUnlockDataRequestModel,
66
master_password_unlock_response_model::MasterPasswordUnlockResponseModel,
77
};
8-
use bitwarden_crypto::{EncString, Kdf, MasterKey, SymmetricCryptoKey};
8+
use bitwarden_crypto::{EncString, Kdf, KeyIds, KeyStoreContext, MasterKey, SymmetricCryptoKey};
99
use bitwarden_encoding::B64;
1010
use bitwarden_error::bitwarden_error;
1111
use serde::{Deserialize, Serialize};
12+
use tracing::Level;
1213
#[cfg(feature = "wasm")]
1314
use wasm_bindgen::prelude::*;
1415

@@ -34,6 +35,9 @@ pub enum MasterPasswordError {
3435
/// Generic crypto error
3536
#[error(transparent)]
3637
Crypto(#[from] bitwarden_crypto::CryptoError),
38+
/// The provided password is incorrect
39+
#[error("Wrong password")]
40+
WrongPassword,
3741
}
3842

3943
/// Represents the data required to unlock with the master password.
@@ -55,7 +59,21 @@ pub struct MasterPasswordUnlockData {
5559
}
5660

5761
impl MasterPasswordUnlockData {
58-
pub(crate) fn derive(
62+
/// Unwrap the user key into the key store context using the provided password.
63+
pub fn unwrap_to_context<Ids: KeyIds>(
64+
&self,
65+
password: &str,
66+
ctx: &mut KeyStoreContext<Ids>,
67+
) -> Result<Ids::Symmetric, MasterPasswordError> {
68+
let master_key = MasterKey::derive(password, &self.salt, &self.kdf)
69+
.map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
70+
let user_key = master_key
71+
.decrypt_user_key(self.master_key_wrapped_user_key.clone())
72+
.map_err(|_| MasterPasswordError::WrongPassword)?;
73+
Ok(ctx.add_local_symmetric_key(user_key))
74+
}
75+
76+
pub(crate) fn derive_ref(
5977
password: &str,
6078
kdf: &Kdf,
6179
salt: &str,
@@ -73,6 +91,22 @@ impl MasterPasswordUnlockData {
7391
master_key_wrapped_user_key,
7492
})
7593
}
94+
95+
/// Derive master password unlock data from a password and user key in the key store.
96+
#[tracing::instrument(skip(password, salt, ctx))]
97+
pub fn derive<Ids: KeyIds>(
98+
password: &str,
99+
kdf: &Kdf,
100+
salt: &str,
101+
user_key_id: Ids::Symmetric,
102+
ctx: &KeyStoreContext<Ids>,
103+
) -> Result<Self, MasterPasswordError> {
104+
tracing::event!(Level::INFO, "deriving master password unlock data");
105+
// Temporary workaround until lower level functions also work on the key context
106+
#[expect(deprecated)]
107+
let key = ctx.dangerous_get_symmetric_key(user_key_id)?;
108+
Self::derive_ref(password, kdf, salt, key)
109+
}
76110
}
77111

78112
impl TryFrom<&MasterPasswordUnlockResponseModel> for MasterPasswordUnlockData {
@@ -138,11 +172,10 @@ pub struct MasterPasswordAuthenticationData {
138172
}
139173

140174
impl MasterPasswordAuthenticationData {
141-
pub(crate) fn derive(
142-
password: &str,
143-
kdf: &Kdf,
144-
salt: &str,
145-
) -> Result<Self, MasterPasswordError> {
175+
/// Derive master password authentication data from a password, KDF, and salt.
176+
#[tracing::instrument(skip(password, kdf, salt))]
177+
pub fn derive(password: &str, kdf: &Kdf, salt: &str) -> Result<Self, MasterPasswordError> {
178+
tracing::event!(Level::INFO, "deriving master password authentication data");
146179
let master_key = MasterKey::derive(password, salt, kdf)
147180
.map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
148181
let hash = master_key.derive_master_key_hash(
@@ -194,8 +227,10 @@ fn kdf_to_kdf_request_model(kdf: &Kdf) -> KdfRequestModel {
194227
#[cfg(test)]
195228
mod tests {
196229
use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
230+
use bitwarden_crypto::KeyStore;
197231

198232
use super::*;
233+
use crate::key_management::{KeyIds, SymmetricKeyId};
199234

200235
const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
201236
const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
@@ -211,7 +246,7 @@ mod tests {
211246
};
212247
let salt = TEST_SALT.to_string();
213248
let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
214-
let data = MasterPasswordUnlockData::derive(TEST_PASSWORD, &kdf, &salt, &user_key)
249+
let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, &salt, &user_key)
215250
.expect("Failed to derive master password unlock data");
216251
assert_eq!(data.salt, salt);
217252
assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
@@ -463,4 +498,78 @@ mod tests {
463498
let result = MasterPasswordUnlockData::try_from(&response);
464499
assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
465500
}
501+
502+
#[test]
503+
fn test_unwrap_to_context_success() {
504+
// Derive master password unlock data from a known password and user key
505+
let kdf = Kdf::PBKDF2 {
506+
iterations: NonZeroU32::new(600_000).expect("non-zero"),
507+
};
508+
let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
509+
let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
510+
.expect("Failed to derive master password unlock data");
511+
512+
// Create a key store and unwrap the user key into the context
513+
let store: KeyStore<KeyIds> = KeyStore::default();
514+
let mut ctx = store.context_mut();
515+
let key_id = data
516+
.unwrap_to_context::<KeyIds>(TEST_PASSWORD, &mut ctx)
517+
.expect("Failed to unwrap to context");
518+
519+
// Verify that the key was added to the context
520+
assert!(ctx.has_symmetric_key(key_id));
521+
522+
// Verify the unwrapped key matches the original
523+
#[expect(deprecated)]
524+
let unwrapped_key = ctx
525+
.dangerous_get_symmetric_key(key_id)
526+
.expect("Failed to get symmetric key");
527+
assert_eq!(*unwrapped_key, user_key);
528+
}
529+
530+
#[test]
531+
fn test_unwrap_to_context_wrong_password() {
532+
// Derive master password unlock data from a known password and user key
533+
let kdf = Kdf::PBKDF2 {
534+
iterations: NonZeroU32::new(600_000).expect("non-zero"),
535+
};
536+
let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
537+
let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
538+
.expect("Failed to derive master password unlock data");
539+
540+
// Attempt to unwrap with wrong password
541+
let store: KeyStore<KeyIds> = KeyStore::default();
542+
let mut ctx = store.context_mut();
543+
let result = data.unwrap_to_context::<KeyIds>("wrong_password", &mut ctx);
544+
545+
assert!(matches!(result, Err(MasterPasswordError::WrongPassword)));
546+
}
547+
548+
#[test]
549+
fn test_unwrap_to_context_persists_key() {
550+
// Derive master password unlock data from a known password and user key
551+
let kdf = Kdf::PBKDF2 {
552+
iterations: NonZeroU32::new(600_000).expect("non-zero"),
553+
};
554+
let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
555+
let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
556+
.expect("Failed to derive master password unlock data");
557+
558+
// Create a key store and unwrap the user key into the context
559+
let store: KeyStore<KeyIds> = KeyStore::default();
560+
{
561+
let mut ctx = store.context_mut();
562+
let local_key_id = data
563+
.unwrap_to_context::<KeyIds>(TEST_PASSWORD, &mut ctx)
564+
.expect("Failed to unwrap to context");
565+
566+
// Persist the local key to the User key slot
567+
ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
568+
.expect("Failed to persist symmetric key");
569+
}
570+
571+
// Verify the key is accessible with the User key id in a new context
572+
let ctx = store.context();
573+
assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
574+
}
466575
}

0 commit comments

Comments
 (0)