Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions crates/bitwarden-core/src/key_management/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use bitwarden_crypto::{
AsymmetricCryptoKey, CoseSerializable, CryptoError, EncString, Kdf, KeyDecryptable,
KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, SignatureAlgorithm,
SignedPublicKey, SigningKey, SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey,
UserKey, dangerous_get_v2_rotated_account_keys, safe::PasswordProtectedKeyEnvelopeError,
UserKey, dangerous_get_v2_rotated_account_keys, derive_symmetric_key_from_prf,
safe::PasswordProtectedKeyEnvelopeError,
};
use bitwarden_encoding::B64;
use bitwarden_error::bitwarden_error;
Expand Down Expand Up @@ -498,6 +499,14 @@ fn derive_pin_protected_user_key(
Ok(derived_key.encrypt_user_key(user_key)?)
}

pub(super) fn derive_prf_key(
client: &Client,
prf: B64,
) -> Result<RotateableKeySet, CryptoClientError> {
let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())?;
create_rotateable_key_set(client, prf_key)
}

#[allow(missing_docs)]
#[bitwarden_error(flat)]
#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -611,7 +620,6 @@ pub struct RotateableKeySet {

/// Create a set of keys to allow access to the user key via the provided
/// symmetric wrapping key while allowing the user key to be rotated.
#[allow(dead_code)]
fn create_rotateable_key_set(
client: &Client,
wrapping_key: SymmetricCryptoKey,
Expand Down
13 changes: 10 additions & 3 deletions crates/bitwarden-core/src/key_management/crypto_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ use crate::{
client::encryption_settings::EncryptionSettingsError,
error::StatefulCryptoError,
key_management::crypto::{
CryptoClientError, EnrollPinResponse, UpdateKdfResponse, UserCryptoV2KeysResponse,
enroll_pin, get_v2_rotated_account_keys, make_update_kdf, make_update_password,
make_v2_keys_for_v1_user,
CryptoClientError, EnrollPinResponse, RotateableKeySet, UpdateKdfResponse,
UserCryptoV2KeysResponse, derive_prf_key, enroll_pin, get_v2_rotated_account_keys,
make_update_kdf, make_update_password, make_v2_keys_for_v1_user,
},
};

Expand Down Expand Up @@ -172,6 +172,13 @@ impl CryptoClient {
derive_pin_user_key(&self.client, encrypted_pin)
}

/// Generates a PRF-protected user key from the provided PRF secret. The result can be stored
/// and later used to initialize another client instance by using the PRF and the PRF key
/// with `initialize_user_crypto`.
pub fn derive_prf_key(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn derive_prf_key(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
pub fn derive_prf_rotateable_keyset_for_user(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {

My suggestion here is very verbose, but "PRF key" alone suggests the "external key" (prf key) not the entire keyset.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's clearer. (I'll tweak the docs to be clearer too.)

If we wanted it shorter, we could establish a convention that keyset refers to a rotateable key set and name it derive_prf_user_keyset(), which means a user [rotateable] key set protected by PRF, just like derive_pin_user_key() above refers to a user key protected by a PIN. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good to me.

derive_prf_key(&self.client, prf)
}

/// Prepares the account for being enrolled in the admin password reset feature. This encrypts
/// the users [UserKey][bitwarden_crypto::UserKey] with the organization's public key.
pub fn enroll_admin_password_reset(
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-crypto/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ pub use kdf::{
};
pub(crate) use key_id::{KEY_ID_SIZE, KeyId};
pub(crate) mod utils;

mod prf;
pub use prf::derive_symmetric_key_from_prf;
44 changes: 44 additions & 0 deletions crates/bitwarden-crypto/src/keys/prf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::{utils::stretch_key, CryptoError, SymmetricCryptoKey};

/// Takes the output of a PRF and derives a symmetric key
pub fn derive_symmetric_key_from_prf(prf: &[u8]) -> Result<SymmetricCryptoKey, CryptoError> {
let (secret, _) = prf
.split_at_checked(32)
.ok_or_else(|| CryptoError::InvalidKeyLen)?;
let secret: [u8; 32] = secret.try_into().unwrap();
if secret.iter().all(|b| *b == b'\0') {
return Err(CryptoError::ZeroNumber);
}
Ok(SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(
&Box::pin(secret.into()),
)?))
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prf_succeeds() {
let prf = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
];
derive_symmetric_key_from_prf(&prf).unwrap();
}

#[test]
fn test_zero_key_fails() {
let prf = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
let err = derive_symmetric_key_from_prf(&prf).unwrap_err();
assert!(matches!(err, CryptoError::ZeroNumber));
}
#[test]
fn test_short_prf_fails() {
let prf = [0, 1, 2, 3, 4, 5, 6, 7, 8];
let err = derive_symmetric_key_from_prf(&prf).unwrap_err();
assert!(matches!(err, CryptoError::InvalidKeyLen));
}
}
6 changes: 5 additions & 1 deletion crates/bitwarden-uniffi/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitwarden_core::key_management::crypto::{
DeriveKeyConnectorRequest, DerivePinKeyResponse, EnrollPinResponse, InitOrgCryptoRequest,
InitUserCryptoRequest, UpdateKdfResponse, UpdatePasswordResponse,
InitUserCryptoRequest, RotateableKeySet, UpdateKdfResponse, UpdatePasswordResponse,
};
use bitwarden_crypto::{EncString, Kdf, UnsignedSharedKey};
use bitwarden_encoding::B64;
Expand Down Expand Up @@ -67,6 +67,10 @@ impl CryptoClient {
Ok(self.0.enroll_pin(pin)?)
}

pub fn derive_prf_key(&self, prf: B64) -> Result<RotateableKeySet> {
Ok(self.0.derive_prf_key(prf)?)
}

/// Protects the current user key with the provided PIN. The result can be stored and later
/// used to initialize another client instance by using the PIN and the PIN key with
/// `initialize_user_crypto`. The provided pin is encrypted with the user key.
Expand Down
Loading