From 8b0cc6d02d8da8d24cf787e61740cb693c7c2824 Mon Sep 17 00:00:00 2001 From: Hinton Date: Thu, 15 Aug 2024 14:51:04 +0200 Subject: [PATCH] Add support for initializing crypto using key connector and generating master key for key connector --- crates/bitwarden-core/src/auth/client_auth.rs | 8 ++++ .../bitwarden-core/src/auth/key_connector.rs | 41 +++++++++++++++++++ crates/bitwarden-core/src/auth/mod.rs | 2 + crates/bitwarden-core/src/mobile/crypto.rs | 41 ++++++++++--------- .../bitwarden-crypto/src/keys/master_key.rs | 18 +++++++- 5 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 crates/bitwarden-core/src/auth/key_connector.rs diff --git a/crates/bitwarden-core/src/auth/client_auth.rs b/crates/bitwarden-core/src/auth/client_auth.rs index 6f1afb135..2e210bd87 100644 --- a/crates/bitwarden-core/src/auth/client_auth.rs +++ b/crates/bitwarden-core/src/auth/client_auth.rs @@ -1,3 +1,4 @@ +use bitwarden_crypto::CryptoError; #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, DeviceKey, EncString, Kdf, TrustDeviceResponse}; @@ -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, } @@ -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 { + 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 } diff --git a/crates/bitwarden-core/src/auth/key_connector.rs b/crates/bitwarden-core/src/auth/key_connector.rs new file mode 100644 index 000000000..0ff3da35c --- /dev/null +++ b/crates/bitwarden-core/src/auth/key_connector.rs @@ -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 { + 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=" + ); + } +} diff --git a/crates/bitwarden-core/src/auth/mod.rs b/crates/bitwarden-core/src/auth/mod.rs index e52870570..6fe22bb57 100644 --- a/crates/bitwarden-core/src/auth/mod.rs +++ b/crates/bitwarden-core/src/auth/mod.rs @@ -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; diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 3f8bc562c..12e2d92af 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -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))] @@ -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))] @@ -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))] @@ -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}; @@ -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 @@ -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))] @@ -175,14 +187,12 @@ pub struct InitOrgCryptoRequest { pub organization_keys: HashMap, } -#[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 { let enc = client.internal.get_encryption_settings()?; let user_key = enc.get_key(&None)?; @@ -190,7 +200,6 @@ pub async fn get_user_encryption_key(client: &Client) -> Result { 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))] @@ -233,7 +242,6 @@ pub fn update_password(client: &Client, new_password: String) -> Result Result { let enc = client.internal.get_encryption_settings()?; let user_key = enc.get_key(&None)?; @@ -262,7 +269,6 @@ pub fn derive_pin_key(client: &Client, pin: String) -> Result Result { let enc = client.internal.get_encryption_settings()?; let user_key = enc.get_key(&None)?; @@ -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, @@ -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, @@ -495,7 +499,6 @@ mod tests { ); } - #[cfg(feature = "internal")] #[test] fn test_enroll_admin_password_reset() { use std::num::NonZeroU32; diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 83e51a1b2..fc9762c3c 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -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}; @@ -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::::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. @@ -101,6 +113,10 @@ impl MasterKey { pub fn decrypt_user_key(&self, user_key: EncString) -> Result { 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.