diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -3,6 +3,7 @@ #include "BaseDataStore.h" #include "DatabaseManager.h" #include "InternalModules/GlobalDBSingleton.h" +#include "InternalModules/RustPromiseManager.h" #include "NativeModuleUtils.h" #include "TerminateApp.h" @@ -1063,9 +1064,44 @@ std::string userDataStr = userData.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) { - this->jsInvoker_->invokeAsync([=, &innerRt]() { - promise->resolve( - jsi::String::createFromUtf8(innerRt, std::string(""))); + this->cryptoThread->scheduleTask([=, &innerRt]() { + std::string error; + + std::string backupID; + try { + backupID = crypto::Tools::generateRandomString(32); + } catch (const std::exception &e) { + error = "Failed to generate backupID"; + } + + std::string pickleKey; + std::string pickledAccount; + if (!error.size()) { + try { + pickleKey = crypto::Tools::generateRandomString(64); + crypto::Persist persist = + this->cryptoModule->storeAsB64(pickleKey); + pickledAccount = + std::string(persist.account.begin(), persist.account.end()); + } catch (const std::exception &e) { + error = "Failed to pickle crypto account"; + } + } + + if (!error.size()) { + auto currentID = RustPromiseManager::instance.addPromise( + promise, this->jsInvoker_, innerRt); + ::createBackup( + rust::string(backupID), + rust::string(backupSecretStr), + rust::string(pickleKey), + rust::string(pickledAccount), + rust::string(userDataStr), + currentID); + } else { + this->jsInvoker_->invokeAsync( + [=, &innerRt]() { promise->reject(error); }); + } }); }); } @@ -1082,10 +1118,14 @@ std::string encryptedUserDataStr = encryptedUserData.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) { - this->jsInvoker_->invokeAsync([=, &innerRt]() { - promise->resolve( - jsi::String::createFromUtf8(innerRt, std::string(""))); - }); + auto currentID = RustPromiseManager::instance.addPromise( + promise, this->jsInvoker_, innerRt); + ::restoreBackup( + rust::string(backupIDStr), + rust::string(backupSecretStr), + rust::string(encryptedUserKeysStr), + rust::string(encryptedUserDataStr), + currentID); }); } diff --git a/native/native_rust_library/Cargo.lock b/native/native_rust_library/Cargo.lock --- a/native/native_rust_library/Cargo.lock +++ b/native/native_rust_library/Cargo.lock @@ -773,6 +773,7 @@ version = "0.1.0" dependencies = [ "argon2 0.5.1", + "base64", "comm-opaque2", "cxx", "cxx-build", diff --git a/native/native_rust_library/Cargo.toml b/native/native_rust_library/Cargo.toml --- a/native/native_rust_library/Cargo.toml +++ b/native/native_rust_library/Cargo.toml @@ -18,6 +18,7 @@ serde_json = "1.0" argon2 = { version = "0.5.1", features = ["std"] } grpc_clients = { path = "../../shared/grpc_clients" } +base64 = "0.21" [build-dependencies] cxx-build = "1.0" diff --git a/native/native_rust_library/src/backup.rs b/native/native_rust_library/src/backup.rs new file mode 100644 --- /dev/null +++ b/native/native_rust_library/src/backup.rs @@ -0,0 +1,154 @@ +use crate::argon2_tools::compute_backup_key; +use crate::constants::aes; +use crate::handle_string_result_as_callback; +use crate::RUNTIME; +use base64::{prelude::BASE64_STANDARD, Engine}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::error::Error; +pub mod ffi { + use super::*; + + pub fn create_backup_sync( + backup_id: String, + backup_secret: String, + pickle_key: String, + pickled_account: String, + user_data: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let result = create_backup( + backup_id, + backup_secret, + pickle_key, + pickled_account, + user_data, + ); + handle_string_result_as_callback(result, promise_id); + }); + } + + pub fn restore_backup_sync( + backup_id: String, + backup_secret: String, + encrypted_user_keys: String, + encrypted_user_data: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let result = restore_backup( + backup_id, + backup_secret, + encrypted_user_keys, + encrypted_user_data, + ); + handle_string_result_as_callback(result, promise_id); + }); + } +} + +pub fn create_backup( + backup_id: String, + backup_secret: String, + pickle_key: String, + pickled_account: String, + user_data: String, +) -> Result<String, Box<dyn Error>> { + let mut backup_key = + compute_backup_key(backup_secret.as_bytes(), backup_id.as_bytes())?; + + let mut user_data = user_data.into_bytes(); + + let mut backup_data_key = [0; aes::KEY_SIZE]; + crate::ffi::generate_key(&mut backup_data_key)?; + let encrypted_user_data = encrypt(&mut backup_data_key, &mut user_data)?; + + let user_keys = UserKeys { + backup_data_key, + pickle_key, + pickled_account, + }; + let encrypted_user_keys = user_keys.encrypt(&mut backup_key)?; + + Ok( + json!({ + "backupID": backup_id, + "userKeys": BASE64_STANDARD.encode(encrypted_user_keys), + "userData": BASE64_STANDARD.encode(encrypted_user_data), + }) + .to_string(), + ) +} + +pub fn restore_backup( + backup_id: String, + backup_secret: String, + encrypted_user_keys: String, + encrypted_user_data: String, +) -> Result<String, Box<dyn Error>> { + let mut encrypted_user_keys: Vec<u8> = + BASE64_STANDARD.decode(&encrypted_user_keys)?; + let mut encrypted_user_data: Vec<u8> = + BASE64_STANDARD.decode(&encrypted_user_data)?; + + let mut backup_id = backup_id.into_bytes(); + + let mut backup_key = + compute_backup_key(backup_secret.as_bytes(), &mut backup_id)?; + + let mut user_keys = + UserKeys::from_encrypted(&mut encrypted_user_keys, &mut backup_key)?; + + let user_data = + decrypt(&mut user_keys.backup_data_key, &mut encrypted_user_data)?; + + Ok( + json!({ + "userData": String::from_utf8(user_data)?, + "pickleKey": user_keys.pickle_key, + "pickledAccount": user_keys.pickled_account, + }) + .to_string(), + ) +} + +#[derive(Debug, Serialize, Deserialize)] +struct UserKeys { + backup_data_key: [u8; 32], + pickle_key: String, + pickled_account: String, +} + +impl UserKeys { + fn encrypt(&self, backup_key: &mut [u8]) -> Result<Vec<u8>, Box<dyn Error>> { + let mut json = serde_json::to_vec(self)?; + encrypt(backup_key, &mut json) + } + + fn from_encrypted( + data: &mut [u8], + backup_key: &mut [u8], + ) -> Result<Self, Box<dyn Error>> { + let decrypted = decrypt(backup_key, data)?; + Ok(serde_json::from_slice(&decrypted)?) + } +} + +fn encrypt(key: &mut [u8], data: &mut [u8]) -> Result<Vec<u8>, Box<dyn Error>> { + let encrypted_len = data.len() + aes::IV_LENGTH + aes::TAG_LENGTH; + let mut encrypted = vec![0; encrypted_len]; + + crate::ffi::encrypt(key, data, &mut encrypted)?; + + Ok(encrypted) +} + +fn decrypt(key: &mut [u8], data: &mut [u8]) -> Result<Vec<u8>, Box<dyn Error>> { + let decrypted_len = data.len() - aes::IV_LENGTH - aes::TAG_LENGTH; + let mut decrypted = vec![0; decrypted_len]; + + crate::ffi::decrypt(key, data, &mut decrypted)?; + + Ok(decrypted) +} diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -18,6 +18,7 @@ use tracing::instrument; mod argon2_tools; +mod backup; mod constants; use argon2_tools::compute_backup_key_str; @@ -45,6 +46,7 @@ ); } +use backup::ffi::*; #[cxx::bridge] mod ffi { @@ -178,6 +180,28 @@ plaintext: &mut [u8], ) -> Result<()>; } + + // Backup + extern "Rust" { + #[cxx_name = "createBackup"] + fn create_backup_sync( + backup_id: String, + backup_secret: String, + pickle_key: String, + pickled_account: String, + user_data: String, + promise_id: u32, + ); + + #[cxx_name = "restoreBackup"] + fn restore_backup_sync( + backup_id: String, + backup_secret: String, + encrypted_user_keys: String, + encrypted_user_data: String, + promise_id: u32, + ); + } } fn handle_string_result_as_callback<E>(