From 6fb114b297e1bc7dd975baafb1bd03e3155afa53 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 10:31:54 +0200 Subject: [PATCH 01/21] feat(wip): add client crypto to client --- Cargo.lock | 1 + crates/bitwarden-core/src/mobile/crypto.rs | 5 +++ crates/bitwarden-crypto/Cargo.toml | 1 + crates/bitwarden-wasm-internal/Cargo.toml | 1 + crates/bitwarden-wasm-internal/src/client.rs | 8 ++++- crates/bitwarden-wasm-internal/src/crypto.rs | 33 ++++++++++++++++++++ crates/bitwarden-wasm-internal/src/lib.rs | 4 +++ 7 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 crates/bitwarden-wasm-internal/src/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index 4e0d14119..ffb4a0977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,6 +690,7 @@ name = "bitwarden-wasm-internal" version = "0.1.0" dependencies = [ "bitwarden", + "bitwarden-crypto", "console_error_panic_hook", "console_log", "js-sys", diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 2aae903c5..c5511dbb5 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -13,9 +13,13 @@ use crate::{ Client, }; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitUserCryptoRequest { /// The user's KDF parameters, as received from the prelogin request pub kdf_params: Kdf, @@ -185,6 +189,7 @@ pub async fn initialize_user_crypto( #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitOrgCryptoRequest { /// The encryption keys for all the organizations the user is a part of pub organization_keys: HashMap, diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 3254640fe..c1ffbb227 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -15,6 +15,7 @@ keywords.workspace = true [features] default = [] +wasm = [] uniffi = ["dep:uniffi"] # Uniffi bindings no-memory-hardening = [] # Disable memory hardening features diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index df69015cf..0b78256bd 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["cdylib"] [dependencies] bitwarden = { workspace = true, features = ["internal", "wasm"] } +bitwarden-crypto = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.68" diff --git a/crates/bitwarden-wasm-internal/src/client.rs b/crates/bitwarden-wasm-internal/src/client.rs index 994af1fcf..e6fb51cec 100644 --- a/crates/bitwarden-wasm-internal/src/client.rs +++ b/crates/bitwarden-wasm-internal/src/client.rs @@ -5,6 +5,8 @@ use bitwarden::{Client, ClientSettings}; use log::{set_max_level, Level}; use wasm_bindgen::prelude::*; +use crate::ClientCrypto; + #[wasm_bindgen] pub enum LogLevel { Trace, @@ -27,7 +29,7 @@ fn convert_level(level: LogLevel) -> Level { // Rc<...> is to avoid needing to take ownership of the Client during our async run_command // function https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401 #[wasm_bindgen] -pub struct BitwardenClient(Rc); +pub struct BitwardenClient(pub(crate) Rc); #[wasm_bindgen] impl BitwardenClient { @@ -54,4 +56,8 @@ impl BitwardenClient { res.text().await.map_err(|e| e.to_string()) } + + pub fn crypto(&self) -> ClientCrypto { + ClientCrypto(self.0.clone()) + } } diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs new file mode 100644 index 000000000..d7c02ac16 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -0,0 +1,33 @@ +use std::rc::Rc; + +use bitwarden::{ + mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + Client, +}; +use wasm_bindgen::prelude::*; + +// use crate::{error::Result, Client}; + +#[wasm_bindgen] +pub struct ClientCrypto(pub(crate) Rc); + +#[wasm_bindgen] +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) { + // Ok( + self.0.crypto().initialize_user_crypto(req).await.unwrap() + // ); + // .map_err(Error::EncryptionSettings)?) + } + + /// 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) { + // Ok( + self.0.crypto().initialize_org_crypto(req).await.unwrap() + // ); + // .map_err(Error::EncryptionSettings)?) + } +} diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index b79c47fca..3a132b126 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -1 +1,5 @@ mod client; +mod crypto; + +pub use client::BitwardenClient; +pub use crypto::ClientCrypto; From 0857aa60c050892c942d330b9a0594802f97b86f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 11:43:47 +0200 Subject: [PATCH 02/21] feat: implement wasm error handling --- crates/bitwarden-wasm-internal/src/error.js | 11 +++++++++ crates/bitwarden-wasm-internal/src/error.rs | 27 +++++++++++++++++++++ crates/bitwarden-wasm-internal/src/lib.rs | 1 + languages/js/sdk-internal/.gitignore | 1 + 4 files changed, 40 insertions(+) create mode 100644 crates/bitwarden-wasm-internal/src/error.js create mode 100644 crates/bitwarden-wasm-internal/src/error.rs diff --git a/crates/bitwarden-wasm-internal/src/error.js b/crates/bitwarden-wasm-internal/src/error.js new file mode 100644 index 000000000..c214d61ab --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/error.js @@ -0,0 +1,11 @@ +/** + * Error thrown by the WASM module. + * @param {string} message - Error message. + * @extends Error + */ +export class WasmError extends Error { + constructor(message) { + super(message); + this.name = 'WasmError'; + } +} diff --git a/crates/bitwarden-wasm-internal/src/error.rs b/crates/bitwarden-wasm-internal/src/error.rs new file mode 100644 index 000000000..51b054e1c --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/error.rs @@ -0,0 +1,27 @@ +use wasm_bindgen::prelude::*; + +// Importing an error class defined in JavaScript instead of defining it in Rust +// allows us to extend the `Error` class. It also provides much better console output. +#[wasm_bindgen(module = "/src/error.js")] +extern "C" { + type WasmError; + + #[wasm_bindgen(constructor)] + fn new(message: String) -> WasmError; +} + +pub type Result = std::result::Result; + +pub struct Error(bitwarden::error::Error); + +impl From for Error { + fn from(error: bitwarden::error::Error) -> Self { + Self(error) + } +} + +impl From for JsValue { + fn from(error: Error) -> Self { + WasmError::new(error.0.to_string()).into() + } +} diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index 3a132b126..a3da0bdc3 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -1,5 +1,6 @@ mod client; mod crypto; +mod error; pub use client::BitwardenClient; pub use crypto::ClientCrypto; diff --git a/languages/js/sdk-internal/.gitignore b/languages/js/sdk-internal/.gitignore index ef69b9de9..99cddcd21 100644 --- a/languages/js/sdk-internal/.gitignore +++ b/languages/js/sdk-internal/.gitignore @@ -1,3 +1,4 @@ +**/snippets/**/*.js bitwarden_wasm_internal_bg.js bitwarden_wasm_internal_bg.wasm bitwarden_wasm_internal_bg.wasm.d.ts From 86707570db7fece4b28dd95f7aa8ec7eb8e0b921 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 12:10:43 +0200 Subject: [PATCH 03/21] feat: add support for converting core errors --- Cargo.lock | 1 + crates/bitwarden-wasm-internal/Cargo.toml | 1 + crates/bitwarden-wasm-internal/src/error.rs | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ffb4a0977..84fec6960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,6 +690,7 @@ name = "bitwarden-wasm-internal" version = "0.1.0" dependencies = [ "bitwarden", + "bitwarden-core", "bitwarden-crypto", "console_error_panic_hook", "console_log", diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index 0b78256bd..3ead75b1b 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["cdylib"] [dependencies] bitwarden = { workspace = true, features = ["internal", "wasm"] } +bitwarden-core = { workspace = true, features = ["wasm"] } bitwarden-crypto = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } diff --git a/crates/bitwarden-wasm-internal/src/error.rs b/crates/bitwarden-wasm-internal/src/error.rs index 51b054e1c..35f0d46a5 100644 --- a/crates/bitwarden-wasm-internal/src/error.rs +++ b/crates/bitwarden-wasm-internal/src/error.rs @@ -10,7 +10,7 @@ extern "C" { fn new(message: String) -> WasmError; } -pub type Result = std::result::Result; +pub type Result = std::result::Result; pub struct Error(bitwarden::error::Error); @@ -20,6 +20,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: bitwarden::Error) -> Self { + Self(error.into()) + } +} + impl From for JsValue { fn from(error: Error) -> Self { WasmError::new(error.0.to_string()).into() From 8860dfdfe876b6f0539e2b56c0e65b3ac07699f3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 12:11:57 +0200 Subject: [PATCH 04/21] feat: add error handling to `ClientCrypto` calls --- crates/bitwarden-wasm-internal/src/crypto.rs | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs index d7c02ac16..3a5c972b6 100644 --- a/crates/bitwarden-wasm-internal/src/crypto.rs +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -6,7 +6,7 @@ use bitwarden::{ }; use wasm_bindgen::prelude::*; -// use crate::{error::Result, Client}; +use crate::error::Result; #[wasm_bindgen] pub struct ClientCrypto(pub(crate) Rc); @@ -15,19 +15,23 @@ pub struct ClientCrypto(pub(crate) Rc); 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) { - // Ok( - self.0.crypto().initialize_user_crypto(req).await.unwrap() - // ); - // .map_err(Error::EncryptionSettings)?) + pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> { + Ok(self + .0 + .crypto() + .initialize_user_crypto(req) + .await + .map_err(bitwarden_core::Error::EncryptionSettings)?) } /// 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) { - // Ok( - self.0.crypto().initialize_org_crypto(req).await.unwrap() - // ); - // .map_err(Error::EncryptionSettings)?) + pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> { + Ok(self + .0 + .crypto() + .initialize_org_crypto(req) + .await + .map_err(bitwarden_core::Error::EncryptionSettings)?) } } From bbaccba0abb0eda9944e65a046a508b1c9b47240 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 15:48:27 +0200 Subject: [PATCH 05/21] fix: most of the missing bitwarden-crypto types remaining: UUID --- Cargo.lock | 2 ++ crates/bitwarden-core/src/mobile/crypto.rs | 4 ++++ crates/bitwarden-crypto/Cargo.toml | 4 +++- crates/bitwarden-crypto/src/enc_string/asymmetric.rs | 7 ++++++- crates/bitwarden-crypto/src/enc_string/symmetric.rs | 7 +++++++ crates/bitwarden-crypto/src/keys/master_key.rs | 11 +++++++++++ 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84fec6960..07b8fed77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -482,8 +482,10 @@ dependencies = [ "sha2", "subtle", "thiserror", + "tsify-next", "uniffi", "uuid", + "wasm-bindgen", "zeroize", ] diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index c5511dbb5..ee340d6e0 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -34,6 +34,7 @@ pub struct InitUserCryptoRequest { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum InitUserCryptoMethod { Password { /// The user's master password @@ -50,6 +51,7 @@ pub enum InitUserCryptoMethod { pin: String, /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain /// this. + // #[tsify(type = "string")] pin_protected_user_key: EncString, }, AuthRequest { @@ -62,8 +64,10 @@ pub enum InitUserCryptoMethod { /// The device's DeviceKey device_key: String, /// The Device Private Key + // #[tsify(type = "string")] protected_device_private_key: EncString, /// The user's symmetric crypto key, encrypted with the Device Key. + // #[tsify(type = "string")] device_protected_user_key: AsymmetricEncString, }, KeyConnector { diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index c1ffbb227..7671a7762 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -15,7 +15,7 @@ keywords.workspace = true [features] default = [] -wasm = [] +wasm = ["dep:tsify-next", "dep:wasm-bindgen"] # WASM support uniffi = ["dep:uniffi"] # Uniffi bindings no-memory-hardening = [] # Disable memory hardening features @@ -43,8 +43,10 @@ sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" subtle = ">=2.5.0, <3.0" thiserror = { workspace = true } +tsify-next = { workspace = true, optional = true } uniffi = { workspace = true, optional = true } uuid = { workspace = true } +wasm-bindgen = { workspace = true, optional = true } zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } [dev-dependencies] diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index f9bda838a..9d4f0d635 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -11,11 +11,16 @@ use crate::{ rsa::encrypt_rsa2048_oaep_sha1, AsymmetricCryptoKey, AsymmetricEncryptable, KeyDecryptable, }; - // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop // macro expansion #[allow(deprecated)] mod internal { + #[cfg(feature = "wasm")] + #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] + const TS_CUSTOM_TYPES: &'static str = r#" + export type AsymmetricEncString = string; + "#; + /// # Encrypted string primitive /// /// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 1fc9b3a9d..de8ceda1a 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -11,6 +11,12 @@ use crate::{ KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }; +#[cfg(feature = "wasm")] +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export type EncString = string; +"#; + /// # Encrypted string primitive /// /// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. @@ -46,6 +52,7 @@ use crate::{ /// - `[mac]`: (optional) is the MAC used to validate the integrity of the data. #[derive(Clone, zeroize::ZeroizeOnDrop, PartialEq)] #[allow(unused, non_camel_case_types)] +// #[cfg_attr(feature = "wasm", derive(Tsify), tsify(type = "string"))] pub enum EncString { /// 0 AesCbc256_B64 { iv: [u8; 16], data: Vec }, diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index fc9762c3c..0602f45b7 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -9,6 +9,16 @@ use serde::{Deserialize, Serialize}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; + +#[cfg(feature = "wasm")] +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +/** An integer that is known not to equal zero. */ +export type NonZeroU32 = number; +"#; + /// Key Derivation Function for Bitwarden Account /// /// In Bitwarden accounts can use multiple KDFs to derive their master key from their password. This @@ -16,6 +26,7 @@ use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCrypt #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum Kdf { PBKDF2 { iterations: NonZeroU32, From 94e95106ecf870dcaa42bfff2026a79b89d604ac Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 15:55:48 +0200 Subject: [PATCH 06/21] fix: add type for uuid, not sure if this will work --- crates/bitwarden-core/src/mobile/crypto.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index ee340d6e0..a71f0c28f 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -196,6 +196,7 @@ pub async fn initialize_user_crypto( #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitOrgCryptoRequest { /// The encryption keys for all the organizations the user is a part of + #[cfg_attr(feature = "wasm", tsify(type = "Map"))] pub organization_keys: HashMap, } From 30dac652b8d64ed9dc3a0734aca5d2f4efd27abd Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 17:02:06 +0200 Subject: [PATCH 07/21] fix: `AuthRequestMethod` missing in types --- crates/bitwarden-core/src/mobile/crypto.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index a71f0c28f..c03d8ef66 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -81,6 +81,7 @@ pub enum InitUserCryptoMethod { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum AuthRequestMethod { UserKey { /// User Key protected by the private key provided in `AuthRequestResponse`. From 1d1644fb9f39476480d6d4f3cf01c3086a572a0f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 09:21:39 +0200 Subject: [PATCH 08/21] fix: editorconfig not being picked up by vsocde --- .editorconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 28ef180e8..aafdc4298 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,10 +12,10 @@ insert_final_newline = true indent_style = space indent_size = 2 -[*.{ts}] +[*.ts] quote_type = single -[*.{rs}] +[*.rs] indent_style = space indent_size = 4 @@ -23,6 +23,6 @@ indent_size = 4 indent_style = space indent_size = 4 -[*.{xml}] +[*.xml] # VS Code XML extension removes the final newline insert_final_newline = false From 02ec62182a1bc65f65e21418dc818b8a1a1f4dd1 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 11:24:27 +0200 Subject: [PATCH 09/21] refactor: move external TS types into wasm crate --- crates/bitwarden-core/src/mobile/crypto.rs | 4 ---- .../src/enc_string/symmetric.rs | 1 - .../bitwarden-wasm-internal/src/custom_types.rs | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 crates/bitwarden-wasm-internal/src/custom_types.rs diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index c03d8ef66..0f6122da9 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -51,7 +51,6 @@ pub enum InitUserCryptoMethod { pin: String, /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain /// this. - // #[tsify(type = "string")] pin_protected_user_key: EncString, }, AuthRequest { @@ -64,10 +63,8 @@ pub enum InitUserCryptoMethod { /// The device's DeviceKey device_key: String, /// The Device Private Key - // #[tsify(type = "string")] protected_device_private_key: EncString, /// The user's symmetric crypto key, encrypted with the Device Key. - // #[tsify(type = "string")] device_protected_user_key: AsymmetricEncString, }, KeyConnector { @@ -197,7 +194,6 @@ pub async fn initialize_user_crypto( #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitOrgCryptoRequest { /// The encryption keys for all the organizations the user is a part of - #[cfg_attr(feature = "wasm", tsify(type = "Map"))] pub organization_keys: HashMap, } diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index de8ceda1a..69711f74a 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -52,7 +52,6 @@ export type EncString = string; /// - `[mac]`: (optional) is the MAC used to validate the integrity of the data. #[derive(Clone, zeroize::ZeroizeOnDrop, PartialEq)] #[allow(unused, non_camel_case_types)] -// #[cfg_attr(feature = "wasm", derive(Tsify), tsify(type = "string"))] pub enum EncString { /// 0 AesCbc256_B64 { iv: [u8; 16], data: Vec }, diff --git a/crates/bitwarden-wasm-internal/src/custom_types.rs b/crates/bitwarden-wasm-internal/src/custom_types.rs new file mode 100644 index 000000000..b31c9a5ea --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/custom_types.rs @@ -0,0 +1,17 @@ +/// This file contains custom TypeScript for types defined by external crates. +/// Everything in the string below is appended to the generated TypeScript definition file. +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export type Uuid = string; + +/** + * RFC3339 compliant date-time string. + * @typeParam T - Not used in JavaScript. + */ +export type DateTime = string; + +/** + * UTC date-time string. Not used in JavaScript. + */ +export type Utc = unknown; +"#; From a1201f1ee0b9051c916ebdbc49629f1690b81762 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 11:25:06 +0200 Subject: [PATCH 10/21] feat: add support for decrypting folders --- Cargo.lock | 3 +++ crates/bitwarden-vault/Cargo.toml | 3 +++ crates/bitwarden-vault/src/folder.rs | 5 +++++ crates/bitwarden-wasm-internal/Cargo.toml | 1 + crates/bitwarden-wasm-internal/src/client.rs | 6 +++++- crates/bitwarden-wasm-internal/src/lib.rs | 4 ++++ .../src/vault/folders.rs | 20 +++++++++++++++++++ .../bitwarden-wasm-internal/src/vault/mod.rs | 18 +++++++++++++++++ 8 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 crates/bitwarden-wasm-internal/src/vault/folders.rs create mode 100644 crates/bitwarden-wasm-internal/src/vault/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 07b8fed77..397a34230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -666,8 +666,10 @@ dependencies = [ "sha2", "thiserror", "tokio", + "tsify-next", "uniffi", "uuid", + "wasm-bindgen", ] [[package]] @@ -694,6 +696,7 @@ dependencies = [ "bitwarden", "bitwarden-core", "bitwarden-crypto", + "bitwarden-vault", "console_error_panic_hook", "console_log", "js-sys", diff --git a/crates/bitwarden-vault/Cargo.toml b/crates/bitwarden-vault/Cargo.toml index 4c41e139c..3c7757e2f 100644 --- a/crates/bitwarden-vault/Cargo.toml +++ b/crates/bitwarden-vault/Cargo.toml @@ -19,6 +19,7 @@ uniffi = [ "bitwarden-crypto/uniffi", "dep:uniffi", ] # Uniffi bindings +wasm = ["dep:tsify-next", "dep:wasm-bindgen"] # WASM support [dependencies] base64 = ">=0.22.1, <0.23" @@ -38,6 +39,8 @@ sha2 = ">=0.10.6, <0.11" thiserror = { workspace = true } uniffi = { version = "=0.28.1", optional = true } uuid = { workspace = true } +tsify-next = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } [dev-dependencies] tokio = { workspace = true, features = ["rt"] } diff --git a/crates/bitwarden-vault/src/folder.rs b/crates/bitwarden-vault/src/folder.rs index c5ab1401b..a1ef65601 100644 --- a/crates/bitwarden-vault/src/folder.rs +++ b/crates/bitwarden-vault/src/folder.rs @@ -10,9 +10,13 @@ use uuid::Uuid; use crate::VaultParseError; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct Folder { id: Option, name: EncString, @@ -22,6 +26,7 @@ pub struct Folder { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct FolderView { pub id: Option, pub name: String, diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index 3ead75b1b..522526984 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["cdylib"] bitwarden = { workspace = true, features = ["internal", "wasm"] } bitwarden-core = { workspace = true, features = ["wasm"] } bitwarden-crypto = { workspace = true, features = ["wasm"] } +bitwarden-vault = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.68" diff --git a/crates/bitwarden-wasm-internal/src/client.rs b/crates/bitwarden-wasm-internal/src/client.rs index e6fb51cec..ea3210c68 100644 --- a/crates/bitwarden-wasm-internal/src/client.rs +++ b/crates/bitwarden-wasm-internal/src/client.rs @@ -5,7 +5,7 @@ use bitwarden::{Client, ClientSettings}; use log::{set_max_level, Level}; use wasm_bindgen::prelude::*; -use crate::ClientCrypto; +use crate::{vault::ClientVault, ClientCrypto}; #[wasm_bindgen] pub enum LogLevel { @@ -60,4 +60,8 @@ impl BitwardenClient { pub fn crypto(&self) -> ClientCrypto { ClientCrypto(self.0.clone()) } + + pub fn vault(&self) -> ClientVault { + ClientVault(self.0.clone()) + } } diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index a3da0bdc3..e055b9373 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -1,6 +1,10 @@ mod client; mod crypto; +mod custom_types; mod error; +mod vault; pub use client::BitwardenClient; pub use crypto::ClientCrypto; +pub use vault::folders::ClientFolders; +pub use vault::ClientVault; diff --git a/crates/bitwarden-wasm-internal/src/vault/folders.rs b/crates/bitwarden-wasm-internal/src/vault/folders.rs new file mode 100644 index 000000000..a85709e0f --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/vault/folders.rs @@ -0,0 +1,20 @@ +use std::rc::Rc; + +use bitwarden::{ + vault::{ClientVaultExt, Folder, FolderView}, + Client, +}; +use wasm_bindgen::prelude::*; + +use crate::error::Result; + +#[wasm_bindgen] +pub struct ClientFolders(pub(crate) Rc); + +#[wasm_bindgen] +impl ClientFolders { + /// Decrypt folder + pub fn decrypt(&self, folder: Folder) -> Result { + Ok(self.0.vault().folders().decrypt(folder)?) + } +} diff --git a/crates/bitwarden-wasm-internal/src/vault/mod.rs b/crates/bitwarden-wasm-internal/src/vault/mod.rs new file mode 100644 index 000000000..0619ab8c2 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/vault/mod.rs @@ -0,0 +1,18 @@ +pub mod folders; + +use std::rc::Rc; + +use bitwarden::Client; +use wasm_bindgen::prelude::*; + +use crate::ClientFolders; + +#[wasm_bindgen] +pub struct ClientVault(pub(crate) Rc); + +#[wasm_bindgen] +impl ClientVault { + pub fn folders(&self) -> ClientFolders { + ClientFolders(self.0.clone()) + } +} From e23e30b86399ff07db98e5e07878536651dffc24 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 10 Oct 2024 09:33:41 +0200 Subject: [PATCH 11/21] lint: run cargo fmt --- crates/bitwarden-core/src/mobile/crypto.rs | 5 ++--- crates/bitwarden-crypto/src/keys/master_key.rs | 5 ++--- crates/bitwarden-vault/src/folder.rs | 5 ++--- crates/bitwarden-wasm-internal/src/lib.rs | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 0f6122da9..1b027df43 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -6,6 +6,8 @@ use bitwarden_crypto::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; use crate::{ client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod}, @@ -13,9 +15,6 @@ use crate::{ Client, }; -#[cfg(feature = "wasm")] -use {tsify_next::Tsify, wasm_bindgen::prelude::*}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 0602f45b7..f6c31ddd2 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -5,13 +5,12 @@ use generic_array::{typenum::U32, GenericArray}; use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; -#[cfg(feature = "wasm")] -use {tsify_next::Tsify, wasm_bindgen::prelude::*}; - #[cfg(feature = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] const TS_CUSTOM_TYPES: &'static str = r#" diff --git a/crates/bitwarden-vault/src/folder.rs b/crates/bitwarden-vault/src/folder.rs index a1ef65601..f57b8ee15 100644 --- a/crates/bitwarden-vault/src/folder.rs +++ b/crates/bitwarden-vault/src/folder.rs @@ -7,12 +7,11 @@ use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; - -use crate::VaultParseError; - #[cfg(feature = "wasm")] use {tsify_next::Tsify, wasm_bindgen::prelude::*}; +use crate::VaultParseError; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index e055b9373..6367ff317 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -6,5 +6,4 @@ mod vault; pub use client::BitwardenClient; pub use crypto::ClientCrypto; -pub use vault::folders::ClientFolders; -pub use vault::ClientVault; +pub use vault::{folders::ClientFolders, ClientVault}; From 79505d44a4b52e7e0e59a1f96839dc18bec06103 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 10 Oct 2024 10:40:57 +0200 Subject: [PATCH 12/21] fix: node linting I really shouldn't need to be fixing this --- crates/bitwarden-napi/src-ts/bitwarden_client/index.ts | 10 +++++----- crates/bitwarden-napi/src-ts/index.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts index 3a3765a12..2e0fa63cc 100644 --- a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts +++ b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts @@ -1,5 +1,5 @@ -import * as rust from "../../binding"; -import { LogLevel } from "../../binding"; +import * as rust from '../../binding'; +import { LogLevel } from '../../binding'; import { Convert, ClientSettings, @@ -11,7 +11,7 @@ import { SecretsDeleteResponse, SecretsResponse, SecretsSyncResponse, -} from "./schemas"; +} from './schemas'; function handleResponse(response: { success: boolean; @@ -19,11 +19,11 @@ function handleResponse(response: { data?: T | null; }): T { if (!response.success) { - throw new Error(response.errorMessage || ""); + throw new Error(response.errorMessage || ''); } if (response.data === null) { - throw new Error(response.errorMessage || "SDK response data is null"); + throw new Error(response.errorMessage || 'SDK response data is null'); } return response.data as T; diff --git a/crates/bitwarden-napi/src-ts/index.ts b/crates/bitwarden-napi/src-ts/index.ts index 36b0649ed..f0bcb892b 100644 --- a/crates/bitwarden-napi/src-ts/index.ts +++ b/crates/bitwarden-napi/src-ts/index.ts @@ -1,3 +1,3 @@ -export * from "./bitwarden_client/index"; -export * from "./bitwarden_client/schemas"; -export { LogLevel } from "../binding"; +export * from './bitwarden_client/index'; +export * from './bitwarden_client/schemas'; +export { LogLevel } from '../binding'; From 01056723f0ce19934add248e6ff0c7ce97fbc3a3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 10 Oct 2024 10:49:01 +0200 Subject: [PATCH 13/21] lint: more prettier linting...... --- crates/bitwarden-wasm-internal/src/error.js | 2 +- support/docs/docs.ts | 52 +++++++------- support/scripts/schemas.ts | 80 ++++++++++----------- 3 files changed, 67 insertions(+), 67 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/error.js b/crates/bitwarden-wasm-internal/src/error.js index c214d61ab..092040ca5 100644 --- a/crates/bitwarden-wasm-internal/src/error.js +++ b/crates/bitwarden-wasm-internal/src/error.js @@ -6,6 +6,6 @@ export class WasmError extends Error { constructor(message) { super(message); - this.name = 'WasmError'; + this.name = "WasmError"; } } diff --git a/support/docs/docs.ts b/support/docs/docs.ts index 067ff0827..ca8385dde 100644 --- a/support/docs/docs.ts +++ b/support/docs/docs.ts @@ -2,36 +2,36 @@ // // Do note that this script follows no best practices and will not handle anything many edge cases. -import fs from "fs"; -import path from "path"; -import Handlebars from "handlebars"; +import fs from 'fs'; +import path from 'path'; +import Handlebars from 'handlebars'; -import { Input, InputType } from "./rustdoc"; +import { Input, InputType } from './rustdoc'; -const doc = JSON.parse(fs.readFileSync("./target/doc/bitwarden_uniffi.json", "utf8")); +const doc = JSON.parse(fs.readFileSync('./target/doc/bitwarden_uniffi.json', 'utf8')); const command = JSON.parse( - fs.readFileSync("./support/schemas/bitwarden_uniffi/DocRef.json", "utf8"), + fs.readFileSync('./support/schemas/bitwarden_uniffi/DocRef.json', 'utf8'), ); const template = Handlebars.compile( - fs.readFileSync(path.resolve(__dirname, "template.hbs"), "utf8"), + fs.readFileSync(path.resolve(__dirname, 'template.hbs'), 'utf8'), ); // Modify this to include more root elements const rootElements = [ - "Client", - "ClientAuth", - "ClientAttachments", - "ClientCiphers", - "ClientCollections", - "ClientCrypto", - "ClientExporters", - "ClientFolders", - "ClientGenerators", - "ClientPasswordHistory", - "ClientPlatform", - "ClientSends", - "ClientVault", + 'Client', + 'ClientAuth', + 'ClientAttachments', + 'ClientCiphers', + 'ClientCollections', + 'ClientCrypto', + 'ClientExporters', + 'ClientFolders', + 'ClientGenerators', + 'ClientPasswordHistory', + 'ClientPlatform', + 'ClientSends', + 'ClientVault', ]; const localIndexArray = Object.values(doc.index).filter((entry: any) => entry.crate_id == 0); @@ -66,10 +66,10 @@ const out = rootElements.map((rootElement) => { }); function stripDef(str: string) { - return str.replace(/#\/definitions\//g, ""); + return str.replace(/#\/definitions\//g, ''); } -Handlebars.registerHelper("stripDef", (str: string) => { +Handlebars.registerHelper('stripDef', (str: string) => { return stripDef(str); }); @@ -83,7 +83,7 @@ for (let i = 0; i < usedDefinitions.length; i++) { Object.entries(cmd.properties ?? {}).forEach((prop: any) => { prop[1].allOf?.forEach((e: any) => { - usedDefinitions.push(stripDef(e["$ref"] as string)); + usedDefinitions.push(stripDef(e['$ref'] as string)); }); }); } @@ -112,7 +112,7 @@ function map_type(t: InputType) { const args = t.resolved_path?.args; const name = t.resolved_path?.name; - let out = ""; + let out = ''; if (name) { usedDefinitions.push(name); @@ -125,7 +125,7 @@ function map_type(t: InputType) { } if (args != null && args.angle_bracketed.args.length > 0) { - out += "<"; + out += '<'; out += args.angle_bracketed.args.map((t: any) => { if (t.type.generic) { return t.type.generic; @@ -133,7 +133,7 @@ function map_type(t: InputType) { return t.type.resolved_path.name; } }); - out += ">"; + out += '>'; } return out; } diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 3eaad1903..accbf788b 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -4,10 +4,10 @@ import { InputData, JSONSchemaInput, FetchingJSONSchemaStore, -} from "quicktype-core"; +} from 'quicktype-core'; -import fs from "fs"; -import path from "path"; +import fs from 'fs'; +import path from 'path'; async function* walk(dir: string): AsyncIterable { for await (const d of await fs.promises.opendir(dir)) { @@ -25,92 +25,92 @@ async function main() { const inputData = new InputData(); inputData.addInput(schemaInput); inputData.addSource( - "schema", + 'schema', { - name: "SchemaTypes", - uris: ["support/schemas/schema_types/SchemaTypes.json#/definitions/"], + name: 'SchemaTypes', + uris: ['support/schemas/schema_types/SchemaTypes.json#/definitions/'], }, () => new JSONSchemaInput(new FetchingJSONSchemaStore()), ); const ts = await quicktype({ inputData, - lang: "typescript", + lang: 'typescript', rendererOptions: {}, }); - writeToFile("./languages/js/sdk-client/src/schemas.ts", ts.lines); - writeToFile("./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts", ts.lines); + writeToFile('./languages/js/sdk-client/src/schemas.ts', ts.lines); + writeToFile('./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts', ts.lines); const python = await quicktype({ inputData, - lang: "python", + lang: 'python', rendererOptions: { - "python-version": "3.7", + 'python-version': '3.7', }, }); - writeToFile("./languages/python/bitwarden_sdk/schemas.py", python.lines); + writeToFile('./languages/python/bitwarden_sdk/schemas.py', python.lines); const ruby = await quicktype({ inputData, - lang: "ruby", + lang: 'ruby', rendererOptions: { - "ruby-version": "3.0", + 'ruby-version': '3.0', }, }); - writeToFile("./languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb", ruby.lines); + writeToFile('./languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb', ruby.lines); const csharp = await quicktype({ inputData, - lang: "csharp", + lang: 'csharp', rendererOptions: { - namespace: "Bitwarden.Sdk", - framework: "SystemTextJson", - "csharp-version": "6", + namespace: 'Bitwarden.Sdk', + framework: 'SystemTextJson', + 'csharp-version': '6', }, }); - writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); + writeToFile('./languages/csharp/Bitwarden.Sdk/schemas.cs', csharp.lines); const cpp = await quicktype({ inputData, - lang: "cpp", + lang: 'cpp', rendererOptions: { - namespace: "Bitwarden::Sdk", - "include-location": "global-include", + namespace: 'Bitwarden::Sdk', + 'include-location': 'global-include', }, }); cpp.lines.forEach((line, idx) => { // Replace DOMAIN for URI_DOMAIN, because DOMAIN is an already defined macro - cpp.lines[idx] = line.replace(/DOMAIN/g, "URI_DOMAIN"); + cpp.lines[idx] = line.replace(/DOMAIN/g, 'URI_DOMAIN'); }); - writeToFile("./languages/cpp/include/schemas.hpp", cpp.lines); + writeToFile('./languages/cpp/include/schemas.hpp', cpp.lines); const go = await quicktype({ inputData, - lang: "go", + lang: 'go', rendererOptions: { - package: "sdk", - "omit-empty": true, + package: 'sdk', + 'omit-empty': true, }, }); - writeToFile("./languages/go/schema.go", go.lines); + writeToFile('./languages/go/schema.go', go.lines); const java = await quicktypeMultiFile({ inputData, - lang: "java", + lang: 'java', rendererOptions: { - package: "com.bitwarden.sdk.schema", - "java-version": "8", + package: 'com.bitwarden.sdk.schema', + 'java-version': '8', }, }); - const javaDir = "./languages/java/src/main/java/com/bitwarden/sdk/schema/"; + const javaDir = './languages/java/src/main/java/com/bitwarden/sdk/schema/'; if (!fs.existsSync(javaDir)) { fs.mkdirSync(javaDir); } @@ -120,23 +120,23 @@ async function main() { const php = await quicktype({ inputData, - lang: "php", + lang: 'php', inferUuids: false, inferDateTimes: false, rendererOptions: { - "acronym-style": "camel", - "with-get": false, + 'acronym-style': 'camel', + 'with-get': false, }, }); - const phpDir = "./languages/php/src/Schemas/"; + const phpDir = './languages/php/src/Schemas/'; if (!fs.existsSync(phpDir)) { fs.mkdirSync(phpDir); } - php.lines.splice(1, 0, "namespace Bitwarden\\Sdk\\Schemas;", "use stdClass;", "use Exception;"); + php.lines.splice(1, 0, 'namespace Bitwarden\\Sdk\\Schemas;', 'use stdClass;', 'use Exception;'); - writeToFile("./languages/php/src/Schemas/Schemas.php", php.lines); + writeToFile('./languages/php/src/Schemas/Schemas.php', php.lines); } main(); @@ -144,7 +144,7 @@ main(); function writeToFile(filename: string, lines: string[]) { const output = fs.createWriteStream(filename); lines.forEach((line) => { - output.write(line + "\n"); + output.write(line + '\n'); }); output.close(); } From 8452d66373571319ea2781696977e7040379e6ef Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 10 Oct 2024 11:16:54 +0200 Subject: [PATCH 14/21] fix: move NonZeroU32 to custom_types --- crates/bitwarden-crypto/src/keys/master_key.rs | 7 ------- crates/bitwarden-wasm-internal/src/custom_types.rs | 5 +++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index f6c31ddd2..7a1e49596 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -11,13 +11,6 @@ use {tsify_next::Tsify, wasm_bindgen::prelude::*}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; -#[cfg(feature = "wasm")] -#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] -const TS_CUSTOM_TYPES: &'static str = r#" -/** An integer that is known not to equal zero. */ -export type NonZeroU32 = number; -"#; - /// Key Derivation Function for Bitwarden Account /// /// In Bitwarden accounts can use multiple KDFs to derive their master key from their password. This diff --git a/crates/bitwarden-wasm-internal/src/custom_types.rs b/crates/bitwarden-wasm-internal/src/custom_types.rs index b31c9a5ea..8d20bf85d 100644 --- a/crates/bitwarden-wasm-internal/src/custom_types.rs +++ b/crates/bitwarden-wasm-internal/src/custom_types.rs @@ -14,4 +14,9 @@ export type DateTime = string; * UTC date-time string. Not used in JavaScript. */ export type Utc = unknown; + +/** + * An integer that is known not to equal zero. + */ +export type NonZeroU32 = number; "#; From 5c6bcdac113b024e5d969e75026fbab7f2dbe806 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 10 Oct 2024 13:34:38 +0200 Subject: [PATCH 15/21] revert: .editorconfig changes --- .editorconfig | 6 +- .../src-ts/bitwarden_client/index.ts | 10 +-- crates/bitwarden-napi/src-ts/index.ts | 6 +- support/docs/docs.ts | 52 ++++++------ support/scripts/schemas.ts | 80 +++++++++---------- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/.editorconfig b/.editorconfig index aafdc4298..28ef180e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,10 +12,10 @@ insert_final_newline = true indent_style = space indent_size = 2 -[*.ts] +[*.{ts}] quote_type = single -[*.rs] +[*.{rs}] indent_style = space indent_size = 4 @@ -23,6 +23,6 @@ indent_size = 4 indent_style = space indent_size = 4 -[*.xml] +[*.{xml}] # VS Code XML extension removes the final newline insert_final_newline = false diff --git a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts index 2e0fa63cc..3a3765a12 100644 --- a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts +++ b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts @@ -1,5 +1,5 @@ -import * as rust from '../../binding'; -import { LogLevel } from '../../binding'; +import * as rust from "../../binding"; +import { LogLevel } from "../../binding"; import { Convert, ClientSettings, @@ -11,7 +11,7 @@ import { SecretsDeleteResponse, SecretsResponse, SecretsSyncResponse, -} from './schemas'; +} from "./schemas"; function handleResponse(response: { success: boolean; @@ -19,11 +19,11 @@ function handleResponse(response: { data?: T | null; }): T { if (!response.success) { - throw new Error(response.errorMessage || ''); + throw new Error(response.errorMessage || ""); } if (response.data === null) { - throw new Error(response.errorMessage || 'SDK response data is null'); + throw new Error(response.errorMessage || "SDK response data is null"); } return response.data as T; diff --git a/crates/bitwarden-napi/src-ts/index.ts b/crates/bitwarden-napi/src-ts/index.ts index f0bcb892b..36b0649ed 100644 --- a/crates/bitwarden-napi/src-ts/index.ts +++ b/crates/bitwarden-napi/src-ts/index.ts @@ -1,3 +1,3 @@ -export * from './bitwarden_client/index'; -export * from './bitwarden_client/schemas'; -export { LogLevel } from '../binding'; +export * from "./bitwarden_client/index"; +export * from "./bitwarden_client/schemas"; +export { LogLevel } from "../binding"; diff --git a/support/docs/docs.ts b/support/docs/docs.ts index ca8385dde..067ff0827 100644 --- a/support/docs/docs.ts +++ b/support/docs/docs.ts @@ -2,36 +2,36 @@ // // Do note that this script follows no best practices and will not handle anything many edge cases. -import fs from 'fs'; -import path from 'path'; -import Handlebars from 'handlebars'; +import fs from "fs"; +import path from "path"; +import Handlebars from "handlebars"; -import { Input, InputType } from './rustdoc'; +import { Input, InputType } from "./rustdoc"; -const doc = JSON.parse(fs.readFileSync('./target/doc/bitwarden_uniffi.json', 'utf8')); +const doc = JSON.parse(fs.readFileSync("./target/doc/bitwarden_uniffi.json", "utf8")); const command = JSON.parse( - fs.readFileSync('./support/schemas/bitwarden_uniffi/DocRef.json', 'utf8'), + fs.readFileSync("./support/schemas/bitwarden_uniffi/DocRef.json", "utf8"), ); const template = Handlebars.compile( - fs.readFileSync(path.resolve(__dirname, 'template.hbs'), 'utf8'), + fs.readFileSync(path.resolve(__dirname, "template.hbs"), "utf8"), ); // Modify this to include more root elements const rootElements = [ - 'Client', - 'ClientAuth', - 'ClientAttachments', - 'ClientCiphers', - 'ClientCollections', - 'ClientCrypto', - 'ClientExporters', - 'ClientFolders', - 'ClientGenerators', - 'ClientPasswordHistory', - 'ClientPlatform', - 'ClientSends', - 'ClientVault', + "Client", + "ClientAuth", + "ClientAttachments", + "ClientCiphers", + "ClientCollections", + "ClientCrypto", + "ClientExporters", + "ClientFolders", + "ClientGenerators", + "ClientPasswordHistory", + "ClientPlatform", + "ClientSends", + "ClientVault", ]; const localIndexArray = Object.values(doc.index).filter((entry: any) => entry.crate_id == 0); @@ -66,10 +66,10 @@ const out = rootElements.map((rootElement) => { }); function stripDef(str: string) { - return str.replace(/#\/definitions\//g, ''); + return str.replace(/#\/definitions\//g, ""); } -Handlebars.registerHelper('stripDef', (str: string) => { +Handlebars.registerHelper("stripDef", (str: string) => { return stripDef(str); }); @@ -83,7 +83,7 @@ for (let i = 0; i < usedDefinitions.length; i++) { Object.entries(cmd.properties ?? {}).forEach((prop: any) => { prop[1].allOf?.forEach((e: any) => { - usedDefinitions.push(stripDef(e['$ref'] as string)); + usedDefinitions.push(stripDef(e["$ref"] as string)); }); }); } @@ -112,7 +112,7 @@ function map_type(t: InputType) { const args = t.resolved_path?.args; const name = t.resolved_path?.name; - let out = ''; + let out = ""; if (name) { usedDefinitions.push(name); @@ -125,7 +125,7 @@ function map_type(t: InputType) { } if (args != null && args.angle_bracketed.args.length > 0) { - out += '<'; + out += "<"; out += args.angle_bracketed.args.map((t: any) => { if (t.type.generic) { return t.type.generic; @@ -133,7 +133,7 @@ function map_type(t: InputType) { return t.type.resolved_path.name; } }); - out += '>'; + out += ">"; } return out; } diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index accbf788b..3eaad1903 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -4,10 +4,10 @@ import { InputData, JSONSchemaInput, FetchingJSONSchemaStore, -} from 'quicktype-core'; +} from "quicktype-core"; -import fs from 'fs'; -import path from 'path'; +import fs from "fs"; +import path from "path"; async function* walk(dir: string): AsyncIterable { for await (const d of await fs.promises.opendir(dir)) { @@ -25,92 +25,92 @@ async function main() { const inputData = new InputData(); inputData.addInput(schemaInput); inputData.addSource( - 'schema', + "schema", { - name: 'SchemaTypes', - uris: ['support/schemas/schema_types/SchemaTypes.json#/definitions/'], + name: "SchemaTypes", + uris: ["support/schemas/schema_types/SchemaTypes.json#/definitions/"], }, () => new JSONSchemaInput(new FetchingJSONSchemaStore()), ); const ts = await quicktype({ inputData, - lang: 'typescript', + lang: "typescript", rendererOptions: {}, }); - writeToFile('./languages/js/sdk-client/src/schemas.ts', ts.lines); - writeToFile('./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts', ts.lines); + writeToFile("./languages/js/sdk-client/src/schemas.ts", ts.lines); + writeToFile("./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts", ts.lines); const python = await quicktype({ inputData, - lang: 'python', + lang: "python", rendererOptions: { - 'python-version': '3.7', + "python-version": "3.7", }, }); - writeToFile('./languages/python/bitwarden_sdk/schemas.py', python.lines); + writeToFile("./languages/python/bitwarden_sdk/schemas.py", python.lines); const ruby = await quicktype({ inputData, - lang: 'ruby', + lang: "ruby", rendererOptions: { - 'ruby-version': '3.0', + "ruby-version": "3.0", }, }); - writeToFile('./languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb', ruby.lines); + writeToFile("./languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb", ruby.lines); const csharp = await quicktype({ inputData, - lang: 'csharp', + lang: "csharp", rendererOptions: { - namespace: 'Bitwarden.Sdk', - framework: 'SystemTextJson', - 'csharp-version': '6', + namespace: "Bitwarden.Sdk", + framework: "SystemTextJson", + "csharp-version": "6", }, }); - writeToFile('./languages/csharp/Bitwarden.Sdk/schemas.cs', csharp.lines); + writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); const cpp = await quicktype({ inputData, - lang: 'cpp', + lang: "cpp", rendererOptions: { - namespace: 'Bitwarden::Sdk', - 'include-location': 'global-include', + namespace: "Bitwarden::Sdk", + "include-location": "global-include", }, }); cpp.lines.forEach((line, idx) => { // Replace DOMAIN for URI_DOMAIN, because DOMAIN is an already defined macro - cpp.lines[idx] = line.replace(/DOMAIN/g, 'URI_DOMAIN'); + cpp.lines[idx] = line.replace(/DOMAIN/g, "URI_DOMAIN"); }); - writeToFile('./languages/cpp/include/schemas.hpp', cpp.lines); + writeToFile("./languages/cpp/include/schemas.hpp", cpp.lines); const go = await quicktype({ inputData, - lang: 'go', + lang: "go", rendererOptions: { - package: 'sdk', - 'omit-empty': true, + package: "sdk", + "omit-empty": true, }, }); - writeToFile('./languages/go/schema.go', go.lines); + writeToFile("./languages/go/schema.go", go.lines); const java = await quicktypeMultiFile({ inputData, - lang: 'java', + lang: "java", rendererOptions: { - package: 'com.bitwarden.sdk.schema', - 'java-version': '8', + package: "com.bitwarden.sdk.schema", + "java-version": "8", }, }); - const javaDir = './languages/java/src/main/java/com/bitwarden/sdk/schema/'; + const javaDir = "./languages/java/src/main/java/com/bitwarden/sdk/schema/"; if (!fs.existsSync(javaDir)) { fs.mkdirSync(javaDir); } @@ -120,23 +120,23 @@ async function main() { const php = await quicktype({ inputData, - lang: 'php', + lang: "php", inferUuids: false, inferDateTimes: false, rendererOptions: { - 'acronym-style': 'camel', - 'with-get': false, + "acronym-style": "camel", + "with-get": false, }, }); - const phpDir = './languages/php/src/Schemas/'; + const phpDir = "./languages/php/src/Schemas/"; if (!fs.existsSync(phpDir)) { fs.mkdirSync(phpDir); } - php.lines.splice(1, 0, 'namespace Bitwarden\\Sdk\\Schemas;', 'use stdClass;', 'use Exception;'); + php.lines.splice(1, 0, "namespace Bitwarden\\Sdk\\Schemas;", "use stdClass;", "use Exception;"); - writeToFile('./languages/php/src/Schemas/Schemas.php', php.lines); + writeToFile("./languages/php/src/Schemas/Schemas.php", php.lines); } main(); @@ -144,7 +144,7 @@ main(); function writeToFile(filename: string, lines: string[]) { const output = fs.createWriteStream(filename); lines.forEach((line) => { - output.write(line + '\n'); + output.write(line + "\n"); }); output.close(); } From 5ee793a668e6d83165b0b210dd87510e5342c10e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 11 Oct 2024 09:30:45 +0200 Subject: [PATCH 16/21] fix: snippet node compatiblity --- crates/bitwarden-wasm-internal/src/error.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-wasm-internal/src/error.js b/crates/bitwarden-wasm-internal/src/error.js index 092040ca5..cd3d3a346 100644 --- a/crates/bitwarden-wasm-internal/src/error.js +++ b/crates/bitwarden-wasm-internal/src/error.js @@ -3,9 +3,11 @@ * @param {string} message - Error message. * @extends Error */ -export class WasmError extends Error { +class WasmError extends Error { constructor(message) { super(message); this.name = "WasmError"; } } + +exports.WasmError = WasmError; From ac0059ba8b8a8188256868d2afc77981ae9e0504 Mon Sep 17 00:00:00 2001 From: Thomas Avery Date: Fri, 11 Oct 2024 16:41:37 -0500 Subject: [PATCH 17/21] Add private key regen SDK methods --- .../src/mobile/client_crypto.rs | 16 +- crates/bitwarden-core/src/mobile/crypto.rs | 213 +++++++++++++++++- crates/bitwarden-wasm-internal/src/crypto.rs | 16 +- 3 files changed, 241 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-core/src/mobile/client_crypto.rs b/crates/bitwarden-core/src/mobile/client_crypto.rs index 0175f2712..75a50f3a3 100644 --- a/crates/bitwarden-core/src/mobile/client_crypto.rs +++ b/crates/bitwarden-core/src/mobile/client_crypto.rs @@ -1,7 +1,10 @@ #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, EncString}; -use super::crypto::{derive_key_connector, DeriveKeyConnectorRequest}; +use super::crypto::{ + derive_key_connector, make_key_pair, verify_asymmetric_keys, DeriveKeyConnectorRequest, + MakeKeyPairResponse, VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse, +}; use crate::{client::encryption_settings::EncryptionSettingsError, Client}; #[cfg(feature = "internal")] use crate::{ @@ -56,6 +59,17 @@ impl<'a> ClientCrypto<'a> { pub fn derive_key_connector(&self, request: DeriveKeyConnectorRequest) -> Result { derive_key_connector(request) } + + pub fn make_key_pair(&self) -> Result { + make_key_pair(self.client) + } + + pub fn verify_asymmetric_keys( + &self, + request: VerifyAsymmetricKeysRequest, + ) -> Result { + verify_asymmetric_keys(self.client, request) + } } impl<'a> Client { diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 1b027df43..9db698216 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; +use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ - AsymmetricEncString, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, - SymmetricCryptoKey, + AsymmetricCryptoKey, AsymmetricEncString, EncString, Kdf, KeyDecryptable, KeyEncryptable, + MasterKey, SymmetricCryptoKey, UserKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -254,6 +255,111 @@ pub fn update_password(client: &Client, new_password: String) -> Result Result { + let enc = client.internal.get_encryption_settings()?; + let user_key = UserKey::new(enc.get_key(&None)?.clone()); + + let key_pair = user_key.make_key_pair()?; + + Ok(MakeKeyPairResponse { + user_public_key: key_pair.public, + user_key_encrypted_private_key: key_pair.private, + }) +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct VerifyAsymmetricKeysRequest { + /// The user's public key + user_public_key: String, + + /// User's private key, encrypted with the user key + user_key_encrypted_private_key: EncString, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct VerifyAsymmetricKeysResponse { + /// Whether the user's private key was decryptable by the user key. + private_key_decryptable: bool, + /// Whether the user's private key was a valid RSA key and matched the public key provided. + valid_private_key: bool, +} + +pub fn verify_asymmetric_keys( + client: &Client, + request: VerifyAsymmetricKeysRequest, +) -> Result { + #[derive(Debug, thiserror::Error)] + enum VerifyError { + #[error("Failed to decrypt private key: {0:?}")] + DecryptFailed(bitwarden_crypto::CryptoError), + #[error("Failed to parse decrypted private key: {0:?}")] + ParseFailed(bitwarden_crypto::CryptoError), + #[error("Failed to derive a public key: {0:?}")] + PublicFailed(bitwarden_crypto::CryptoError), + #[error("Derived public key doesn't match")] + KeyMismatch, + } + + fn verify_inner( + user_key: &SymmetricCryptoKey, + request: &VerifyAsymmetricKeysRequest, + ) -> Result<(), VerifyError> { + let decrypted_private_key: Vec = request + .user_key_encrypted_private_key + .decrypt_with_key(user_key) + .map_err(VerifyError::DecryptFailed)?; + + let private_key = AsymmetricCryptoKey::from_der(&decrypted_private_key) + .map_err(VerifyError::ParseFailed)?; + + let derived_public_key_vec = private_key + .to_public_der() + .map_err(VerifyError::PublicFailed)?; + + let derived_public_key = STANDARD.encode(&derived_public_key_vec); + + if derived_public_key != request.user_public_key { + return Err(VerifyError::KeyMismatch); + } + Ok(()) + } + + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; + + Ok(match verify_inner(&user_key, &request) { + Ok(_) => VerifyAsymmetricKeysResponse { + private_key_decryptable: true, + valid_private_key: true, + }, + Err(e) => { + log::debug!("User asymmetric keys verification: {}", e); + + VerifyAsymmetricKeysResponse { + private_key_decryptable: !matches!(e, VerifyError::DecryptFailed(_)), + valid_private_key: false, + } + } + }) +} + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -354,6 +460,8 @@ pub(super) fn derive_key_connector(request: DeriveKeyConnectorRequest) -> Result mod tests { use std::num::NonZeroU32; + use bitwarden_crypto::RsaKeyPair; + use super::*; use crate::Client; @@ -585,4 +693,105 @@ mod tests { assert_eq!(result, "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="); } + + fn setup_asymmetric_keys_test() -> (Client, UserKey, RsaKeyPair) { + let client = Client::new(None); + + let master_key = MasterKey::derive( + "asdfasdfasdf", + "test@bitwarden.com", + &Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + ) + .unwrap(); + let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap(); + let key_pair = user_key.make_key_pair().unwrap(); + + client + .internal + .initialize_user_crypto_master_key( + master_key, + encrypted_user_key, + key_pair.private.clone(), + ) + .unwrap(); + return (client, user_key, key_pair); + } + + #[test] + fn test_make_key_pair() { + let (client, user_key, _) = setup_asymmetric_keys_test(); + + let response = make_key_pair(&client).unwrap(); + + assert!(!response.user_public_key.is_empty()); + let encrypted_private_key = response.user_key_encrypted_private_key; + let private_key: Vec = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap(); + assert!(!private_key.is_empty()); + } + + #[test] + fn test_verify_asymmetric_keys_success() { + let (client, _, key_pair) = setup_asymmetric_keys_test(); + + let request = VerifyAsymmetricKeysRequest { + user_public_key: key_pair.public, + user_key_encrypted_private_key: key_pair.private, + }; + let response = client.crypto().verify_asymmetric_keys(request).unwrap(); + + assert!(response.private_key_decryptable); + assert!(response.valid_private_key); + } + + #[test] + fn test_verify_asymmetric_keys_decrypt_failed() { + let (client, _, key_pair) = setup_asymmetric_keys_test(); + let undecryptable_private_key = "2.cqD39M4erPZ3tWaz2Fng9w==|+Bsp/xvM30oo+HThKN12qirK0A63EjMadcwethCX7kEgfL5nEXgAFsSgRBMpByc1djgpGDMXzUTLOE+FejXRsrEHH/ICZ7jPMgSR+lV64Mlvw3fgvDPQdJ6w3MCmjPueGQtrlPj1K78BkRomN3vQwwRBFUIJhLAnLshTOIFrSghoyG78na7McqVMMD0gmC0zmRaSs2YWu/46ES+2Rp8V5OC4qdeeoJM9MQfaOtmaqv7NRVDeDM3DwoyTJAOcon8eovMKE4jbFPUboiXjNQBkBgjvLhco3lVJnFcQuYgmjqrwuUQRsfAtZjxFXg/RQSH2D+SI5uRaTNQwkL4iJqIw7BIKtI0gxDz6eCVdq/+DLhpImgCV/aaIhF/jkpGqLCceFsYMbuqdULMM1VYKgV+IAuyC65R+wxOaKS+1IevvPnNp7tgKAvT5+shFg8piusj+rQ49daX2SmV2OImwdWMmmX93bcVV0xJ/WYB1yrqmyRUcTwyvX3RQF25P5okIIzFasRp8jXFZe8C6f93yzkn1TPQbp95zF4OsWjfPFVH4hzca07ACt2HjbAB75JakWbFA5MbCF8aOIwIfeLVhVlquQXCldOHCsl22U/f3HTGLB9OS8F83CDAy7qZqpKha9Im8RUhHoyf+lXrky0gyd6un7Ky8NSkVOGd8CEG7bvZfutxv/qtAjEM9/lV78fh8TQIy9GNgioMzplpuzPIJOgMaY/ZFZj6a8H9OMPneN5Je0H/DwHEglSyWy7CMgwcbQgXYGXc8rXTTxL71GUAFHzDr4bAJvf40YnjndoL9tf+oBw8vVNUccoD4cjyOT5w8h7M3Liaxk9/0O8JR98PKxxpv1Xw6XjFCSEHeG2y9FgDUASFR4ZwG1qQBiiLMnJ7e9kvxsdnmasBux9H0tOdhDhAM16Afk3NPPKA8eztJVHJBAfQiaNiUA4LIJ48d8EpUAe2Tvz0WW/gQThplUINDTpvPf+FojLwc5lFwNIPb4CVN1Ui8jOJI5nsOw4BSWJvLzJLxawHxX/sBuK96iXza+4aMH+FqYKt/twpTJtiVXo26sPtHe6xXtp7uO4b+bL9yYUcaAci69L0W8aNdu8iF0lVX6kFn2lOL8dBLRleGvixX9gYEVEsiI7BQBjxEBHW/YMr5F4M4smqCpleZIAxkse1r2fQ33BSOJVQKInt4zzgdKwrxDzuVR7RyiIUuNXHsprKtRHNJrSc4x5kWFUeivahed2hON+Ir/ZvrxYN6nJJPeYYH4uEm1Nn4osUzzfWILlqpmDPK1yYy365T38W8wT0cbdcJrI87ycS37HeB8bzpFJZSY/Dzv48Yy19mDZJHLJLCRqyxNeIlBPsVC8fvxQhzr+ZyS3Wi8Dsa2Sgjt/wd0xPULLCJlb37s+1aWgYYylr9QR1uhXheYfkXFED+saGWwY1jlYL5e2Oo9n3sviBYwJxIZ+RTKFgwlXV5S+Jx/MbDpgnVHP1KaoU6vvzdWYwMChdHV/6PhZVbeT2txq7Qt+zQN59IGrOWf6vlMkHxfUzMTD58CE+xAaz/D05ljHMesLj9hb3MSrymw0PcwoFGWUMIzIQE73pUVYNE7fVHa8HqUOdoxZ5dRZqXRVox1xd9siIPE3e6CuVQIMabTp1YLno=|Y38qtTuCwNLDqFnzJ3Cgbjm1SE15OnhDm9iAMABaQBA=".parse().unwrap(); + + let request = VerifyAsymmetricKeysRequest { + user_public_key: key_pair.public, + user_key_encrypted_private_key: undecryptable_private_key, + }; + let response = client.crypto().verify_asymmetric_keys(request).unwrap(); + + assert!(!response.private_key_decryptable); + assert!(!response.valid_private_key); + } + + #[test] + fn test_verify_asymmetric_keys_parse_failed() { + let (client, user_key, key_pair) = setup_asymmetric_keys_test(); + + let invalid_private_key = "bad_key" + .to_string() + .into_bytes() + .encrypt_with_key(&user_key.0) + .unwrap(); + + let request = VerifyAsymmetricKeysRequest { + user_public_key: key_pair.public, + user_key_encrypted_private_key: invalid_private_key, + }; + let response = client.crypto().verify_asymmetric_keys(request).unwrap(); + + assert!(response.private_key_decryptable); + assert!(!response.valid_private_key); + } + + #[test] + fn test_verify_asymmetric_keys_key_mismatch() { + let (client, user_key, key_pair) = setup_asymmetric_keys_test(); + let new_key_pair = user_key.make_key_pair().unwrap(); + + let request = VerifyAsymmetricKeysRequest { + user_public_key: key_pair.public, + user_key_encrypted_private_key: new_key_pair.private, + }; + let response = client.crypto().verify_asymmetric_keys(request).unwrap(); + + assert!(response.private_key_decryptable); + assert!(!response.valid_private_key); + } } diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs index 3a5c972b6..58b17a662 100644 --- a/crates/bitwarden-wasm-internal/src/crypto.rs +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -1,7 +1,10 @@ use std::rc::Rc; use bitwarden::{ - mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + mobile::crypto::{ + InitOrgCryptoRequest, InitUserCryptoRequest, MakeKeyPairResponse, + VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse, + }, Client, }; use wasm_bindgen::prelude::*; @@ -34,4 +37,15 @@ impl ClientCrypto { .await .map_err(bitwarden_core::Error::EncryptionSettings)?) } + + pub fn make_key_pair(&self) -> Result { + Ok(self.0.crypto().make_key_pair()?) + } + + pub fn verify_asymmetric_keys( + &self, + request: VerifyAsymmetricKeysRequest, + ) -> Result { + Ok(self.0.crypto().verify_asymmetric_keys(request)?) + } } From 963e9afc3ce1444b5c80550aa044f860735f9fa9 Mon Sep 17 00:00:00 2001 From: Thomas Avery Date: Fri, 11 Oct 2024 16:59:13 -0500 Subject: [PATCH 18/21] clippy fixes --- crates/bitwarden-core/src/mobile/crypto.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-core/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs index 9db698216..4f98dba42 100644 --- a/crates/bitwarden-core/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -344,7 +344,7 @@ pub fn verify_asymmetric_keys( let enc = client.internal.get_encryption_settings()?; let user_key = enc.get_key(&None)?; - Ok(match verify_inner(&user_key, &request) { + Ok(match verify_inner(user_key, &request) { Ok(_) => VerifyAsymmetricKeysResponse { private_key_decryptable: true, valid_private_key: true, @@ -716,7 +716,7 @@ mod tests { key_pair.private.clone(), ) .unwrap(); - return (client, user_key, key_pair); + (client, user_key, key_pair) } #[test] From c3be870353a2b10fbdd55ad5cf0c43c9ec791892 Mon Sep 17 00:00:00 2001 From: Thomas Avery Date: Mon, 14 Oct 2024 11:35:10 -0500 Subject: [PATCH 19/21] delete error.js --- crates/bitwarden-wasm-internal/src/error.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 crates/bitwarden-wasm-internal/src/error.js diff --git a/crates/bitwarden-wasm-internal/src/error.js b/crates/bitwarden-wasm-internal/src/error.js deleted file mode 100644 index cd3d3a346..000000000 --- a/crates/bitwarden-wasm-internal/src/error.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Error thrown by the WASM module. - * @param {string} message - Error message. - * @extends Error - */ -class WasmError extends Error { - constructor(message) { - super(message); - this.name = "WasmError"; - } -} - -exports.WasmError = WasmError; From 2db787de45525986bc64485b7de20b49dab66865 Mon Sep 17 00:00:00 2001 From: Thomas Avery Date: Mon, 14 Oct 2024 11:38:20 -0500 Subject: [PATCH 20/21] Add methods to wasm-internal --- crates/bitwarden-wasm-internal/src/crypto.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs index f1b581964..dc6d8fdc9 100644 --- a/crates/bitwarden-wasm-internal/src/crypto.rs +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -1,7 +1,10 @@ use std::rc::Rc; use bitwarden_core::{ - mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + mobile::crypto::{ + InitOrgCryptoRequest, InitUserCryptoRequest, MakeKeyPairResponse, + VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse, + }, Client, }; use wasm_bindgen::prelude::*; @@ -30,4 +33,15 @@ impl ClientCrypto { pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> { Ok(self.0.crypto().initialize_org_crypto(req).await?) } + + pub fn make_key_pair(&self) -> Result { + Ok(self.0.crypto().make_key_pair()?) + } + + pub fn verify_asymmetric_keys( + &self, + request: VerifyAsymmetricKeysRequest, + ) -> Result { + Ok(self.0.crypto().verify_asymmetric_keys(request)?) + } } From e9d4fc28a16afbb0fe57d90351b2e1ab5eaff7f7 Mon Sep 17 00:00:00 2001 From: Thomas Avery Date: Wed, 16 Oct 2024 13:58:58 -0500 Subject: [PATCH 21/21] Add code comments to public API. --- crates/bitwarden-wasm-internal/src/crypto.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs index dc6d8fdc9..56d47d7a0 100644 --- a/crates/bitwarden-wasm-internal/src/crypto.rs +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -34,10 +34,15 @@ impl ClientCrypto { Ok(self.0.crypto().initialize_org_crypto(req).await?) } + /// Generates a new key pair and encrypts the private key with the initialized user key. + /// Needs to be called after `initialize_user_crypto`. pub fn make_key_pair(&self) -> Result { Ok(self.0.crypto().make_key_pair()?) } + /// Verifies a user's asymmetric keys by decrypting the private key with the initialized user + /// key. Returns if the private key is decryptable and if it is a valid matching key. + /// Needs to be called after `initialize_user_crypto`. pub fn verify_asymmetric_keys( &self, request: VerifyAsymmetricKeysRequest,