Skip to content

Commit

Permalink
Split initializeCrypto (#329)
Browse files Browse the repository at this point in the history
## Type of change
```
- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective
Split initializeCrypto into separate user and organization methods, so
clients won't need to hold on to the master password until after sync is
done to initialize the organization keys.
  • Loading branch information
dani-garcia authored Nov 20, 2023
1 parent 9fab1fa commit 8788f18
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 75 deletions.
1 change: 1 addition & 0 deletions crates/bitwarden-uniffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
```bash
cargo +nightly rustdoc -p bitwarden -- -Zunstable-options --output-format json
cargo +nightly rustdoc -p bitwarden-uniffi -- -Zunstable-options --output-format json
npm run schemas

npx ts-node ./support/docs/docs.ts > languages/kotlin/doc.md
```
35 changes: 35 additions & 0 deletions crates/bitwarden-uniffi/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::sync::Arc;

use bitwarden::mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest};

use crate::{error::Result, Client};

#[derive(uniffi::Object)]
pub struct ClientCrypto(pub(crate) Arc<Client>);

#[uniffi::export]
impl ClientCrypto {
/// Initialization method for the user crypto. Needs to be called before any other crypto operations.
pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> {
Ok(self
.0
.0
.write()
.await
.crypto()
.initialize_user_crypto(req)
.await?)
}

/// Initialization method for the organization crypto. Needs to be called after `initialize_user_crypto` but before any other crypto operations.
pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> {
Ok(self
.0
.0
.write()
.await
.crypto()
.initialize_org_crypto(req)
.await?)
}
}
5 changes: 3 additions & 2 deletions crates/bitwarden-uniffi/src/docs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bitwarden::{
auth::password::MasterPasswordPolicyOptions,
client::kdf::Kdf,
mobile::crypto::InitCryptoRequest,
mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest},
tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest},
vault::{
Cipher, CipherView, Collection, Folder, FolderView, Send, SendListView, SendView,
Expand All @@ -24,7 +24,8 @@ pub enum DocRef {
SendListView(SendListView),

// Crypto
InitCryptoRequest(InitCryptoRequest),
InitUserCryptoRequest(InitUserCryptoRequest),
InitOrgCryptoRequest(InitOrgCryptoRequest),

// Generators
PasswordGeneratorRequest(PasswordGeneratorRequest),
Expand Down
22 changes: 3 additions & 19 deletions crates/bitwarden-uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use std::sync::Arc;

use async_lock::RwLock;
use auth::ClientAuth;
use bitwarden::{client::client_settings::ClientSettings, mobile::crypto::InitCryptoRequest};
use bitwarden::client::client_settings::ClientSettings;

pub mod auth;
pub mod crypto;
mod error;
pub mod tool;
mod uniffi_support;
Expand All @@ -15,16 +16,14 @@ pub mod vault;
#[cfg(feature = "docs")]
pub mod docs;

use crypto::ClientCrypto;
use error::Result;
use tool::ClientGenerators;
use vault::ClientVault;

#[derive(uniffi::Object)]
pub struct Client(RwLock<bitwarden::Client>);

#[derive(uniffi::Object)]
pub struct ClientCrypto(Arc<Client>);

#[uniffi::export]
impl Client {
/// Initialize a new instance of the SDK client
Expand Down Expand Up @@ -58,18 +57,3 @@ impl Client {
msg
}
}

#[uniffi::export]
impl ClientCrypto {
/// Initialization method for the crypto. Needs to be called before any other crypto operations.
pub async fn initialize_crypto(&self, req: InitCryptoRequest) -> Result<()> {
Ok(self
.0
.0
.write()
.await
.crypto()
.initialize_crypto(req)
.await?)
}
}
4 changes: 4 additions & 0 deletions crates/bitwarden/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ impl EncryptionSettings {

let private_key = self.private_key.as_ref().ok_or(Error::VaultLocked)?;

// Make sure we only keep the keys given in the arguments and not any of the previous
// ones, which might be from organizations that the user is no longer a part of anymore
self.org_keys.clear();

// Decrypt the org keys with the private key
for (org_id, org_enc_key) in org_enc_keys {
let data = match org_enc_key {
Expand Down
13 changes: 10 additions & 3 deletions crates/bitwarden/src/mobile/client_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use crate::Client;
#[cfg(feature = "internal")]
use crate::{
error::Result,
mobile::crypto::{initialize_crypto, InitCryptoRequest},
mobile::crypto::{
initialize_org_crypto, initialize_user_crypto, InitOrgCryptoRequest, InitUserCryptoRequest,
},
};

pub struct ClientCrypto<'a> {
Expand All @@ -11,8 +13,13 @@ pub struct ClientCrypto<'a> {

impl<'a> ClientCrypto<'a> {
#[cfg(feature = "internal")]
pub async fn initialize_crypto(&mut self, req: InitCryptoRequest) -> Result<()> {
initialize_crypto(self.client, req).await
pub async fn initialize_user_crypto(&mut self, req: InitUserCryptoRequest) -> Result<()> {
initialize_user_crypto(self.client, req).await
}

#[cfg(feature = "internal")]
pub async fn initialize_org_crypto(&mut self, req: InitOrgCryptoRequest) -> Result<()> {
initialize_org_crypto(self.client, req).await
}
}

Expand Down
57 changes: 39 additions & 18 deletions crates/bitwarden/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,63 @@ use crate::{client::kdf::Kdf, crypto::EncString, error::Result, Client};
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct InitCryptoRequest {
pub struct InitUserCryptoRequest {
/// The user's KDF parameters, as received from the prelogin request
pub kdf_params: Kdf,
/// The user's email address
pub email: String,
/// The user's master password
pub password: String,
/// The user's encrypted symmetric crypto key
pub user_key: String,
/// The user's encryptred private key
/// The user's encrypted private key
pub private_key: String,
/// The encryption keys for all the organizations the user is a part of
pub organization_keys: HashMap<uuid::Uuid, String>,
/// The initialization method to use
pub method: InitUserCryptoMethod,
}

#[cfg(feature = "internal")]
pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> Result<()> {
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Enum))]
pub enum InitUserCryptoMethod {
Password {
/// The user's master password
password: String,
/// The user's encrypted symmetric crypto key
user_key: String,
},
}

#[cfg(feature = "internal")]
pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> {
let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username {
client_id: "".to_string(),
email: req.email,
kdf: req.kdf_params,
});
client.set_login_method(login_method);

let user_key = req.user_key.parse::<EncString>()?;
let private_key = req.private_key.parse::<EncString>()?;
let private_key: EncString = req.private_key.parse()?;

client.initialize_user_crypto(&req.password, user_key, private_key)?;
match req.method {
InitUserCryptoMethod::Password { password, user_key } => {
let user_key: EncString = user_key.parse()?;
client.initialize_user_crypto(&password, user_key, private_key)?;
}
}

let organization_keys = req
.organization_keys
.into_iter()
.map(|(k, v)| Ok((k, v.parse::<EncString>()?)))
.collect::<Result<Vec<_>>>()?;
Ok(())
}

client.initialize_org_crypto(organization_keys)?;
#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct InitOrgCryptoRequest {
/// The encryption keys for all the organizations the user is a part of
pub organization_keys: HashMap<uuid::Uuid, EncString>,
}

#[cfg(feature = "internal")]
pub async fn initialize_org_crypto(client: &mut Client, req: InitOrgCryptoRequest) -> Result<()> {
let organization_keys = req.organization_keys.into_iter().collect();
client.initialize_org_crypto(organization_keys)?;
Ok(())
}
16 changes: 11 additions & 5 deletions crates/bitwarden/tests/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
async fn test_register_initialize_crypto() {
use std::num::NonZeroU32;

use bitwarden::{client::kdf::Kdf, mobile::crypto::InitCryptoRequest, Client};
use bitwarden::{
client::kdf::Kdf,
mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest},
Client,
};

let mut client = Client::new(None);

Expand All @@ -22,13 +26,15 @@ async fn test_register_initialize_crypto() {
// Ensure we can initialize the crypto with the new keys
client
.crypto()
.initialize_crypto(InitCryptoRequest {
.initialize_user_crypto(InitUserCryptoRequest {
kdf_params: kdf,
email: email.to_owned(),
password: password.to_owned(),
user_key: register_response.encrypted_user_key,
private_key: register_response.keys.private.to_string(),
organization_keys: Default::default(),

method: InitUserCryptoMethod::Password {
password: password.to_owned(),
user_key: register_response.encrypted_user_key,
},
})
.await
.unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.bitwarden.core.DateTime
import com.bitwarden.core.Folder
import com.bitwarden.core.InitCryptoRequest
import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.InitUserCryptoRequest
import com.bitwarden.core.Kdf
import com.bitwarden.core.Uuid
import com.bitwarden.myapplication.ui.theme.MyApplicationTheme
Expand Down Expand Up @@ -117,7 +119,7 @@ class MainActivity : ComponentActivity() {
}.body<JsonObject>()

val folders = (syncBody["folders"] as JsonArray).map {
val o = it as JsonObject;
val o = it as JsonObject
Folder(
(o["id"] as JsonPrimitive).content,
(o["name"] as JsonPrimitive).content,
Expand All @@ -136,13 +138,20 @@ class MainActivity : ComponentActivity() {
orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content
}

client.crypto().initializeCrypto(
InitCryptoRequest(
client.crypto().initializeUserCrypto(
InitUserCryptoRequest(
kdfParams = kdf,
email = EMAIL,
password = PASSWORD,
userKey = loginBody.Key,
privateKey = loginBody.PrivateKey,
method = InitUserCryptoMethod.Password(
password = PASSWORD,
userKey = loginBody.Key
)
)
)

client.crypto().initializeOrgCrypto(
InitOrgCryptoRequest(
organizationKeys = orgKeys
)
)
Expand Down
Loading

0 comments on commit 8788f18

Please sign in to comment.