diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -16,7 +16,7 @@ }; type ClientBackup = { - +uploadBackupProtocol: (userData: UserData) => Promise, + +uploadBackupProtocol: () => Promise, +restoreBackupProtocol: ( expectedUserData: UserData, ) => Promise<{ +dataIntegritySuccess: boolean }>, @@ -37,30 +37,24 @@ ); const loggedIn = useSelector(isLoggedIn); - const uploadBackupProtocol = React.useCallback( - async (userData: UserData) => { - if (!loggedIn || !currentUserID) { - throw new Error('Attempt to upload backup for not logged in user.'); - } - console.info('Start uploading backup...'); + const uploadBackupProtocol = React.useCallback(async () => { + if (!loggedIn || !currentUserID) { + throw new Error('Attempt to upload backup for not logged in user.'); + } + console.info('Start uploading backup...'); - const ed25519 = await getContentSigningKey(); - await commCoreModule.setCommServicesAuthMetadata( - currentUserID, - ed25519, - accessToken ? accessToken : '', - ); + const ed25519 = await getContentSigningKey(); + await commCoreModule.setCommServicesAuthMetadata( + currentUserID, + ed25519, + accessToken ? accessToken : '', + ); - const backupSecret = await getBackupSecret(); - await commCoreModule.createNewBackup( - backupSecret, - JSON.stringify(userData), - ); + const backupSecret = await getBackupSecret(); + await commCoreModule.createNewBackup(backupSecret); - console.info('Backup uploaded.'); - }, - [accessToken, currentUserID, loggedIn], - ); + console.info('Backup uploaded.'); + }, [accessToken, currentUserID, loggedIn]); const restoreBackupProtocol = React.useCallback( async (expectedUserData: UserData) => { diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -23,6 +23,7 @@ std::string SQLiteQueryExecutor::encryptionKey; std::once_flag SQLiteQueryExecutor::initialized; int SQLiteQueryExecutor::sqlcipherEncryptionKeySize = 64; +// Should match constant defined in `native_rust_library/src/constants.rs` std::string SQLiteQueryExecutor::secureStoreEncryptionKeyID = "comm.encryptionKey"; int SQLiteQueryExecutor::backupLogsEncryptionKeySize = 32; diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -135,10 +135,8 @@ virtual jsi::Value clearCommServicesAccessToken(jsi::Runtime &rt) override; virtual void startBackupHandler(jsi::Runtime &rt) override; virtual void stopBackupHandler(jsi::Runtime &rt) override; - virtual jsi::Value createNewBackup( - jsi::Runtime &rt, - jsi::String backupSecret, - jsi::String userData) override; + virtual jsi::Value + createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) override; virtual jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) override; 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 @@ -1426,12 +1426,9 @@ } } -jsi::Value CommCoreModule::createNewBackup( - jsi::Runtime &rt, - jsi::String backupSecret, - jsi::String userData) { +jsi::Value +CommCoreModule::createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); - std::string userDataStr = userData.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { this->cryptoThread->scheduleTask([=, &innerRt]() { @@ -1466,7 +1463,6 @@ rust::string(backupSecretStr), rust::string(pickleKey), rust::string(pickledAccount), - rust::string(userDataStr), currentID); } else { this->jsInvoker_->invokeAsync( diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -155,7 +155,7 @@ return jsi::Value::undefined(); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_createNewBackup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { - return static_cast(&turboModule)->createNewBackup(rt, args[0].asString(rt), args[1].asString(rt)); + return static_cast(&turboModule)->createNewBackup(rt, args[0].asString(rt)); } static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_restoreBackup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->restoreBackup(rt, args[0].asString(rt)); @@ -208,7 +208,7 @@ methodMap_["clearCommServicesAccessToken"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearCommServicesAccessToken}; methodMap_["startBackupHandler"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_startBackupHandler}; methodMap_["stopBackupHandler"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_stopBackupHandler}; - methodMap_["createNewBackup"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_createNewBackup}; + methodMap_["createNewBackup"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_createNewBackup}; methodMap_["restoreBackup"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_restoreBackup}; } diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -65,7 +65,7 @@ virtual jsi::Value clearCommServicesAccessToken(jsi::Runtime &rt) = 0; virtual void startBackupHandler(jsi::Runtime &rt) = 0; virtual void stopBackupHandler(jsi::Runtime &rt) = 0; - virtual jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret, jsi::String userData) = 0; + virtual jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) = 0; virtual jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) = 0; }; @@ -448,13 +448,13 @@ return bridging::callFromJs( rt, &T::stopBackupHandler, jsInvoker_, instance_); } - jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret, jsi::String userData) override { + jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) override { static_assert( - bridging::getParameterCount(&T::createNewBackup) == 3, - "Expected createNewBackup(...) to have 3 parameters"); + bridging::getParameterCount(&T::createNewBackup) == 2, + "Expected createNewBackup(...) to have 2 parameters"); return bridging::callFromJs( - rt, &T::createNewBackup, jsInvoker_, instance_, std::move(backupSecret), std::move(userData)); + rt, &T::createNewBackup, jsInvoker_, instance_, std::move(backupSecret)); } jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) override { static_assert( diff --git a/native/native_rust_library/src/backup.rs b/native/native_rust_library/src/backup.rs --- a/native/native_rust_library/src/backup.rs +++ b/native/native_rust_library/src/backup.rs @@ -4,14 +4,16 @@ use crate::argon2_tools::{compute_backup_key, compute_backup_key_str}; use crate::constants::{aes, secure_store}; -use crate::ffi::secure_store_get; +use crate::ffi::{ + create_main_compaction, get_backup_user_keys_file_path, secure_store_get, +}; +use crate::future_manager; +use crate::handle_string_result_as_callback; use crate::BACKUP_SOCKET_ADDR; use crate::RUNTIME; -use crate::{handle_string_result_as_callback, handle_void_result_as_callback}; use backup_client::{ - BackupClient, BackupData, BackupDescriptor, DownloadLogsRequest, - LatestBackupIDResponse, LogUploadConfirmation, LogWSResponse, RequestedData, - SinkExt, StreamExt, UploadLogRequest, UserIdentity, + BackupClient, BackupDescriptor, DownloadLogsRequest, LatestBackupIDResponse, + LogWSResponse, RequestedData, SinkExt, StreamExt, UserIdentity, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -22,24 +24,41 @@ pub use upload_handler::ffi::*; - pub fn create_backup_sync( + pub fn create_backup( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, - user_data: String, promise_id: u32, ) { + compaction_upload_promises::insert(backup_id.clone(), promise_id); + RUNTIME.spawn(async move { - let result = create_backup( - backup_id, + let result = create_userkeys_compaction( + backup_id.clone(), backup_secret, pickle_key, pickled_account, - user_data, ) - .await; - handle_void_result_as_callback(result, promise_id); + .await + .map_err(|err| err.to_string()); + + if let Err(err) = result { + compaction_upload_promises::resolve(&backup_id, Err(err)); + return; + } + + let (future_id, future) = future_manager::new_future::<()>().await; + create_main_compaction(&backup_id, future_id); + if let Err(err) = future.await { + compaction_upload_promises::resolve(&backup_id, Err(err)); + tokio::spawn(upload_handler::compaction::cleanup_files(backup_id)); + return; + } + + trigger_backup_file_upload(); + + // The promise will be resolved when the backup is uploaded }); } @@ -51,21 +70,17 @@ } } -pub async fn create_backup( +pub async fn create_userkeys_compaction( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, - user_data: String, ) -> Result<(), Box> { 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 backup_data_key = + secure_store_get(secure_store::SECURE_STORE_ENCRYPTION_KEY_ID)?; let user_keys = UserKeys { backup_data_key, @@ -74,46 +89,8 @@ }; let encrypted_user_keys = user_keys.encrypt(&mut backup_key)?; - let backup_client = BackupClient::new(BACKUP_SOCKET_ADDR)?; - - let user_identity = get_user_identity_from_secure_store()?; - - let backup_data = BackupData { - backup_id: backup_id.clone(), - user_data: encrypted_user_data, - user_keys: encrypted_user_keys, - attachments: Vec::new(), - }; - - backup_client - .upload_backup(&user_identity, backup_data) - .await?; - - let (tx, rx) = backup_client.upload_logs(&user_identity).await?; - - tokio::pin!(tx); - tokio::pin!(rx); - - let log_data = UploadLogRequest { - backup_id: backup_id.clone(), - log_id: 1, - content: (1..100).collect(), - attachments: None, - }; - tx.send(log_data.clone()).await?; - match rx.next().await { - Some(Ok(LogUploadConfirmation { - backup_id: response_backup_id, - log_id: 1, - })) - if backup_id == response_backup_id => - { - // Correctly uploaded - } - response => { - return Err(Box::new(InvalidWSLogResponse(format!("{response:?}")))) - } - }; + let user_keys_file = get_backup_user_keys_file_path(&backup_id)?; + tokio::fs::write(user_keys_file, encrypted_user_keys).await?; Ok(()) } @@ -142,7 +119,7 @@ .download_backup_data(&latest_backup_descriptor, RequestedData::UserKeys) .await?; - let mut user_keys = + let user_keys = UserKeys::from_encrypted(&mut encrypted_user_keys, &mut backup_key)?; let backup_data_descriptor = BackupDescriptor::BackupID { @@ -154,8 +131,10 @@ .download_backup_data(&backup_data_descriptor, RequestedData::UserData) .await?; - let user_data = - decrypt(&mut user_keys.backup_data_key, &mut encrypted_user_data)?; + let user_data = decrypt( + &mut user_keys.backup_data_key.as_bytes().to_vec(), + &mut encrypted_user_data, + )?; let user_data: serde_json::Value = serde_json::from_slice(&user_data)?; @@ -210,7 +189,7 @@ #[derive(Debug, Serialize, Deserialize)] struct UserKeys { - backup_data_key: [u8; 32], + backup_data_key: String, pickle_key: String, pickled_account: String, } diff --git a/native/native_rust_library/src/constants.rs b/native/native_rust_library/src/constants.rs --- a/native/native_rust_library/src/constants.rs +++ b/native/native_rust_library/src/constants.rs @@ -1,16 +1,19 @@ use std::time::Duration; +#[allow(unused)] pub mod aes { pub const KEY_SIZE: usize = 32; // bytes pub const IV_LENGTH: usize = 12; // bytes - unique Initialization Vector (nonce) pub const TAG_LENGTH: usize = 16; // bytes - GCM auth tag } -/// Should match constants defined in `CommSecureStore.h` pub mod secure_store { + /// Should match constants defined in `CommSecureStore.h` pub const COMM_SERVICES_ACCESS_TOKEN: &str = "accessToken"; pub const USER_ID: &str = "userID"; pub const DEVICE_ID: &str = "deviceID"; + /// Should match constant defined in `SQLiteQueryExecutor.h` + pub const SECURE_STORE_ENCRYPTION_KEY_ID: &str = "comm.encryptionKey"; } pub const BACKUP_SERVICE_CONNECTION_RETRY_DELAY: Duration = 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 @@ -310,12 +310,11 @@ fn trigger_backup_file_upload(); #[cxx_name = "createBackup"] - fn create_backup_sync( + fn create_backup( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, - user_data: String, promise_id: u32, ); @@ -341,18 +340,15 @@ unsafe extern "C++" { include!("RustBackupExecutor.h"); - #[allow(unused)] #[cxx_name = "getBackupDirectoryPath"] fn get_backup_directory_path() -> Result; - #[allow(unused)] #[cxx_name = "getBackupFilePath"] fn get_backup_file_path( backup_id: &str, is_attachments: bool, ) -> Result; - #[allow(unused)] #[cxx_name = "getBackupLogFilePath"] fn get_backup_log_file_path( backup_id: &str, @@ -360,7 +356,6 @@ is_attachments: bool, ) -> Result; - #[allow(unused)] #[cxx_name = "getBackupUserKeysFilePath"] fn get_backup_user_keys_file_path(backup_id: &str) -> Result; diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js --- a/native/profile/backup-menu.react.js +++ b/native/profile/backup-menu.react.js @@ -31,7 +31,18 @@ state => state.localSettings.isBackupEnabled, ); - const { restoreBackupProtocol } = useClientBackup(); + const { uploadBackupProtocol, restoreBackupProtocol } = useClientBackup(); + + const uploadBackup = React.useCallback(async () => { + let message = 'Success'; + try { + await uploadBackupProtocol(); + } catch (e) { + message = `Backup upload error: ${String(getMessageForException(e))}`; + console.error(message); + } + Alert.alert('Upload protocol result', message); + }, [uploadBackupProtocol]); const testRestore = React.useCallback(async () => { let message; @@ -71,6 +82,17 @@ ACTIONS + + +