diff --git a/crates/bitwarden-uniffi/README.md b/crates/bitwarden-uniffi/README.md index 7dff0b291..1be7706bb 100644 --- a/crates/bitwarden-uniffi/README.md +++ b/crates/bitwarden-uniffi/README.md @@ -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 ``` diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs new file mode 100644 index 000000000..1792a5bbc --- /dev/null +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -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); + +#[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?) + } +} diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index bc47a19c3..45fabb0b7 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -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, @@ -24,7 +24,8 @@ pub enum DocRef { SendListView(SendListView), // Crypto - InitCryptoRequest(InitCryptoRequest), + InitUserCryptoRequest(InitUserCryptoRequest), + InitOrgCryptoRequest(InitOrgCryptoRequest), // Generators PasswordGeneratorRequest(PasswordGeneratorRequest), diff --git a/crates/bitwarden-uniffi/src/lib.rs b/crates/bitwarden-uniffi/src/lib.rs index 6e5415d38..5035f22b9 100644 --- a/crates/bitwarden-uniffi/src/lib.rs +++ b/crates/bitwarden-uniffi/src/lib.rs @@ -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; @@ -15,6 +16,7 @@ pub mod vault; #[cfg(feature = "docs")] pub mod docs; +use crypto::ClientCrypto; use error::Result; use tool::ClientGenerators; use vault::ClientVault; @@ -22,9 +24,6 @@ use vault::ClientVault; #[derive(uniffi::Object)] pub struct Client(RwLock); -#[derive(uniffi::Object)] -pub struct ClientCrypto(Arc); - #[uniffi::export] impl Client { /// Initialize a new instance of the SDK client @@ -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?) - } -} diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index da5398a12..6533c7d2e 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -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 { diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 67f69d8f1..c0edb1982 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -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> { @@ -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 } } diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index f4d26dc6c..2cf850279 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -9,23 +9,32 @@ 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, + /// 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, @@ -33,18 +42,30 @@ pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> R }); client.set_login_method(login_method); - let user_key = req.user_key.parse::()?; - let private_key = req.private_key.parse::()?; + 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::()?))) - .collect::>>()?; + 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, +} +#[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(()) } diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden/tests/register.rs index d4e632e55..956fe86ce 100644 --- a/crates/bitwarden/tests/register.rs +++ b/crates/bitwarden/tests/register.rs @@ -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); @@ -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(); diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index 2d204e075..13dec7d0e 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -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 @@ -117,7 +119,7 @@ class MainActivity : ComponentActivity() { }.body() 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, @@ -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 ) ) diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 6174df750..70446bd8f 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -182,14 +182,26 @@ Decrypt collection list ## ClientCrypto -### `initialize_crypto` +### `initialize_user_crypto` -Initialization method for the crypto. Needs to be called before any other crypto operations. +Initialization method for the user crypto. Needs to be called before any other crypto operations. **Arguments**: - self: -- req: [InitCryptoRequest](#initcryptorequest) +- req: [InitUserCryptoRequest](#initusercryptorequest) + +**Output**: std::result::Result<,BitwardenError> + +### `initialize_org_crypto` + +Initialization method for the organization crypto. Needs to be called after +`initialize_user_crypto` but before any other crypto operations. + +**Arguments**: + +- self: +- req: [InitOrgCryptoRequest](#initorgcryptorequest) **Output**: std::result::Result<,BitwardenError> @@ -492,6 +504,11 @@ implementations. array + + key + + More recent ciphers uses individual encryption keys to encrypt the other fields of the Cipher. + name @@ -617,6 +634,11 @@ implementations. array + + key + + + name string @@ -835,7 +857,7 @@ implementations. -## `InitCryptoRequest` +## `InitOrgCryptoRequest` @@ -844,34 +866,75 @@ implementations. - - - + + + +
Description
kdfParamsThe user's KDF parameters, as received from the prelogin requestorganizationKeysobjectThe encryption keys for all the organizations the user is a part of
+ +## `InitUserCryptoMethod` + + - - - + + + - - + + + + + + +
emailstringThe user's email addressKeyTypeDescription
passwordstringThe user's master passwordobject
+ + + + + + + + + + + + + + + + +
KeyTypeDescription
passwordstringThe user's master password
user_keystringThe user's encrypted symmetric crypto key
+
+ +## `InitUserCryptoRequest` + + + + + + + + + + + - + - + - + - - - + + +
KeyTypeDescription
kdfParamsThe user's KDF parameters, as received from the prelogin request
userKeyemail stringThe user's encrypted symmetric crypto keyThe user's email address
privateKey stringThe user's encryptred private keyThe user's encrypted private key
organizationKeysobjectThe encryption keys for all the organizations the user is a part ofmethodThe initialization method to use
diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 4b3555403..484caf14f 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -136,13 +136,19 @@ struct ContentView: View { ///////////////////////////// Initialize crypto ///////////////////////////// - try await client.crypto().initializeCrypto( - req: InitCryptoRequest( + try await client.crypto().initializeUserCrypto( + req: InitUserCryptoRequest( kdfParams: kdf, email: EMAIL, - password: PASSWORD, - userKey: loginData.Key, privateKey: loginData.PrivateKey, + method: InitUserCryptoMethod.password( + password: PASSWORD, + userKey: loginData.Key + ) + )) + + try await client.crypto().initializeOrgCrypto( + req: InitOrgCryptoRequest( organizationKeys: Dictionary.init( uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } )