Skip to content

Commit

Permalink
Add support for initializing crypto using key connector and generatin…
Browse files Browse the repository at this point in the history
…g master key for key connector
  • Loading branch information
Hinton committed Aug 15, 2024
1 parent 4be1e09 commit 8b0cc6d
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 20 deletions.
8 changes: 8 additions & 0 deletions crates/bitwarden-core/src/auth/client_auth.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bitwarden_crypto::CryptoError;
#[cfg(feature = "internal")]
use bitwarden_crypto::{AsymmetricEncString, DeviceKey, EncString, Kdf, TrustDeviceResponse};

Expand All @@ -23,6 +24,8 @@ use crate::auth::{
};
use crate::{auth::renew::renew_token, error::Result, Client};

use super::key_connector::{make_key_connector_keys, KeyConnectorResponse};

pub struct ClientAuth<'a> {
pub(crate) client: &'a crate::Client,
}
Expand Down Expand Up @@ -79,6 +82,11 @@ impl<'a> ClientAuth<'a> {
make_register_tde_keys(self.client, email, org_public_key, remember_device)
}

pub fn make_key_connector_keys(&self) -> Result<KeyConnectorResponse, CryptoError> {
let mut rng = rand::thread_rng();
make_key_connector_keys(&mut rng)
}

pub async fn register(&self, input: &RegisterRequest) -> Result<()> {
register(self.client, input).await
}
Expand Down
41 changes: 41 additions & 0 deletions crates/bitwarden-core/src/auth/key_connector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use bitwarden_crypto::{CryptoError, MasterKey, RsaKeyPair};

#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct KeyConnectorResponse {
pub master_key: String,
pub encrypted_user_key: String,
pub keys: RsaKeyPair,
}

pub(super) fn make_key_connector_keys(
mut rng: impl rand::RngCore,
) -> Result<KeyConnectorResponse, CryptoError> {
let master_key = MasterKey::generate(&mut rng);
let (user_key, encrypted_user_key) = master_key.make_user_key()?;
let keys = user_key.make_key_pair()?;

Ok(KeyConnectorResponse {
master_key: master_key.to_base64(),
encrypted_user_key: encrypted_user_key.to_string(),
keys,
})
}

#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;

#[test]
fn test_make_key_connector_keys() {
let mut rng = ChaCha8Rng::from_seed([0u8; 32]);

let result = make_key_connector_keys(&mut rng).unwrap();

assert_eq!(
result.master_key,
"PgDvL4lfQNZ/W7joHwmloSyEDsPOmn87GBvhiO9xGh4="
);
}
}
2 changes: 2 additions & 0 deletions crates/bitwarden-core/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub use register::{RegisterKeyResponse, RegisterRequest};
mod tde;
#[cfg(feature = "internal")]
pub use tde::RegisterTdeKeyResponse;
#[cfg(feature = "internal")]
mod key_connector;

#[cfg(feature = "internal")]
use crate::error::Result;
Expand Down
41 changes: 22 additions & 19 deletions crates/bitwarden-core/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use std::collections::HashMap;

use bitwarden_crypto::{AsymmetricEncString, EncString};
#[cfg(feature = "internal")]
use bitwarden_crypto::{Kdf, KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey};
use bitwarden_crypto::{
AsymmetricEncString, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey,
SymmetricCryptoKey,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[cfg(feature = "internal")]
use crate::client::{LoginMethod, UserLoginMethod};
use crate::{
client::{LoginMethod, UserLoginMethod},
error::{Error, Result},
Client,
};

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
Expand All @@ -28,7 +27,6 @@ pub struct InitUserCryptoRequest {
pub method: InitUserCryptoMethod,
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
Expand Down Expand Up @@ -64,9 +62,14 @@ pub enum InitUserCryptoMethod {
/// The user's symmetric crypto key, encrypted with the Device Key.
device_protected_user_key: AsymmetricEncString,
},
KeyConnector {
/// Base64 encoded master key, retrieved from the key connector.
master_key: String,
/// The user's encrypted symmetric crypto key
user_key: String,
},
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
Expand All @@ -83,7 +86,6 @@ pub enum AuthRequestMethod {
},
}

#[cfg(feature = "internal")]
pub async fn initialize_user_crypto(client: &Client, req: InitUserCryptoRequest) -> Result<()> {
use bitwarden_crypto::{DeviceKey, PinKey};

Expand Down Expand Up @@ -151,6 +153,17 @@ pub async fn initialize_user_crypto(client: &Client, req: InitUserCryptoRequest)
.internal
.initialize_user_crypto_decrypted_key(user_key, private_key)?;
}
InitUserCryptoMethod::KeyConnector {
master_key,
user_key,
} => {
let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key)?);
let user_key: EncString = user_key.parse()?;

client
.internal
.initialize_user_crypto_master_key(master_key, user_key, private_key)?;
}
}

client
Expand All @@ -166,7 +179,6 @@ pub async fn initialize_user_crypto(client: &Client, req: InitUserCryptoRequest)
Ok(())
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
Expand All @@ -175,22 +187,19 @@ pub struct InitOrgCryptoRequest {
pub organization_keys: HashMap<uuid::Uuid, AsymmetricEncString>,
}

#[cfg(feature = "internal")]
pub async fn initialize_org_crypto(client: &Client, req: InitOrgCryptoRequest) -> Result<()> {
let organization_keys = req.organization_keys.into_iter().collect();
client.internal.initialize_org_crypto(organization_keys)?;
Ok(())
}

#[cfg(feature = "internal")]
pub async fn get_user_encryption_key(client: &Client) -> Result<String> {
let enc = client.internal.get_encryption_settings()?;
let user_key = enc.get_key(&None)?;

Ok(user_key.to_base64())
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
Expand Down Expand Up @@ -233,7 +242,6 @@ pub fn update_password(client: &Client, new_password: String) -> Result<UpdatePa
})
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
Expand All @@ -244,7 +252,6 @@ pub struct DerivePinKeyResponse {
encrypted_pin: EncString,
}

#[cfg(feature = "internal")]
pub fn derive_pin_key(client: &Client, pin: String) -> Result<DerivePinKeyResponse> {
let enc = client.internal.get_encryption_settings()?;
let user_key = enc.get_key(&None)?;
Expand All @@ -262,7 +269,6 @@ pub fn derive_pin_key(client: &Client, pin: String) -> Result<DerivePinKeyRespon
})
}

#[cfg(feature = "internal")]
pub fn derive_pin_user_key(client: &Client, encrypted_pin: EncString) -> Result<EncString> {
let enc = client.internal.get_encryption_settings()?;
let user_key = enc.get_key(&None)?;
Expand All @@ -276,7 +282,6 @@ pub fn derive_pin_user_key(client: &Client, encrypted_pin: EncString) -> Result<
derive_pin_protected_user_key(&pin, &login_method, user_key)
}

#[cfg(feature = "internal")]
fn derive_pin_protected_user_key(
pin: &str,
login_method: &LoginMethod,
Expand All @@ -296,7 +301,6 @@ fn derive_pin_protected_user_key(
Ok(derived_key.encrypt_user_key(user_key)?)
}

#[cfg(feature = "internal")]
pub(super) fn enroll_admin_password_reset(
client: &Client,
public_key: String,
Expand Down Expand Up @@ -495,7 +499,6 @@ mod tests {
);
}

#[cfg(feature = "internal")]
#[test]
fn test_enroll_admin_password_reset() {
use std::num::NonZeroU32;
Expand Down
18 changes: 17 additions & 1 deletion crates/bitwarden-crypto/src/keys/master_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::num::NonZeroU32;

use base64::{engine::general_purpose::STANDARD, Engine};
use generic_array::{typenum::U32, GenericArray};
use rand::Rng;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -64,10 +66,20 @@ pub enum HashPurpose {
pub struct MasterKey(SymmetricCryptoKey);

impl MasterKey {
pub fn new(key: SymmetricCryptoKey) -> MasterKey {
pub fn new(key: SymmetricCryptoKey) -> Self {
Self(key)
}

/// Generate a new random master key. Primarily used for KeyConnector.
pub fn generate(mut rng: impl rand::RngCore) -> Self {
let mut key = Box::pin(GenericArray::<u8, U32>::default());

rng.fill(key.as_mut_slice());

// Master Keys never contains a mac_key.
Self::new(SymmetricCryptoKey { key, mac_key: None })
}

/// Derives a users master key from their password, email and KDF.
///
/// Note: the email is trimmed and converted to lowercase before being used.
Expand Down Expand Up @@ -101,6 +113,10 @@ impl MasterKey {
pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
decrypt_user_key(&self.0, user_key)
}

pub fn to_base64(&self) -> String {
self.0.to_base64()
}
}

/// Helper function to encrypt a user key with a master or pin key.
Expand Down

0 comments on commit 8b0cc6d

Please sign in to comment.