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 @@ -4,34 +4,18 @@ import * as React from 'react'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; -import type { - BackupAuth, - UserData, - BackupEncrypted, -} from 'lib/types/backup-types.js'; +import type { UserData } from 'lib/types/backup-types.js'; -import { getBackupID, getUserData, getUserKeys, uploadBackup } from './api.js'; import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; -// purpose of this result is to improve logging and -// testing the initial backup version -type RestoreBackupResult = { - getBackupID?: boolean, - getUserKeys?: boolean, - getUserData?: boolean, - decryptUserData?: boolean, - userDataIntegrity?: boolean, - error?: Error, -}; - type ClientBackup = { +uploadBackupProtocol: (userData: UserData) => Promise, +restoreBackupProtocol: ( expectedUserData: UserData, - ) => Promise, + ) => Promise<{ +dataIntegritySuccess: boolean }>, }; async function getBackupSecret(): Promise { @@ -56,23 +40,19 @@ } console.info('Start uploading backup...'); - const backupSecret = await getBackupSecret(); + const ed25519 = await getContentSigningKey(); + await commCoreModule.setCommServicesAuthMetadata( + currentUserID, + ed25519, + accessToken ? accessToken : '', + ); - const encryptedBackupStr = await commCoreModule.createNewBackup( + const backupSecret = await getBackupSecret(); + await commCoreModule.createNewBackup( backupSecret, JSON.stringify(userData), ); - const encryptedBackup: BackupEncrypted = JSON.parse(encryptedBackupStr); - - const ed25519 = await getContentSigningKey(); - const backupAuth: BackupAuth = { - userID: currentUserID, - accessToken: accessToken ? accessToken : '', - deviceID: ed25519, - }; - - await uploadBackup(encryptedBackup, backupAuth); console.info('Backup uploaded.'); }, [accessToken, currentUserID, loggedIn], @@ -84,108 +64,13 @@ throw new Error('Attempt to restore backup for not logged in user.'); } - const result: RestoreBackupResult = { - getBackupID: undefined, - getUserKeys: undefined, - getUserData: undefined, - decryptUserData: undefined, - userDataIntegrity: undefined, - error: undefined, - }; - - const backupIDPromise: Promise = (async () => { - try { - // We are using UserID instead of the username. - // The reason is that the initial version of the backup service - // cannot get UserID based on username. - const backupID = await getBackupID(currentUserID); - result.getBackupID = true; - return backupID; - } catch (e) { - result.getBackupID = false; - result.error = e; - return undefined; - } - })(); - - const [ed25519, backupID] = await Promise.all([ - getContentSigningKey(), - backupIDPromise, - ]); - - if (!backupID) { - return result; - } - - const backupAuth: BackupAuth = { - userID: currentUserID, - accessToken: accessToken ? accessToken : '', - deviceID: ed25519, - }; - - const userKeysPromise: Promise = (async () => { - try { - const userKeysResponse = await getUserKeys(backupID, backupAuth); - result.getUserKeys = true; - return userKeysResponse; - } catch (e) { - result.getUserKeys = false; - result.error = e; - return undefined; - } - })(); - const userDataPromise: Promise = (async () => { - try { - const userDataResponse = await getUserData(backupID, backupAuth); - result.getUserData = true; - return userDataResponse; - } catch (e) { - result.getUserData = false; - result.error = e; - return undefined; - } - })(); - - const [userKeysResponse, userDataResponse] = await Promise.all([ - userKeysPromise, - userDataPromise, - ]); - - if (!userKeysResponse) { - result.getUserKeys = false; - result.error = new Error('UserKeys response is empty'); - return result; - } - - if (!userDataResponse) { - result.getUserData = false; - result.error = new Error('UserData response is empty'); - return result; - } - const backupSecret = await getBackupSecret(); + const restoreResultStr = await commCoreModule.restoreBackup(backupSecret); + const { userData }: { userData: UserData } = JSON.parse(restoreResultStr); - let userData: UserData; - try { - const restoreResultStr = await commCoreModule.restoreBackup( - backupID, - backupSecret, - userKeysResponse, - userDataResponse, - ); - const { userData: userDataStr } = JSON.parse(restoreResultStr); - userData = JSON.parse(userDataStr); - result.decryptUserData = true; - } catch (e) { - result.decryptUserData = false; - result.error = e; - } - - result.userDataIntegrity = !!_isEqual(userData, expectedUserData); - - return result; + return { dataIntegritySuccess: !!_isEqual(userData, expectedUserData) }; }, - [accessToken, currentUserID, loggedIn], + [currentUserID, loggedIn], ); return { uploadBackupProtocol, restoreBackupProtocol }; 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 @@ -109,12 +109,8 @@ jsi::Runtime &rt, jsi::String backupSecret, jsi::String userData) override; - virtual jsi::Value restoreBackup( - jsi::Runtime &rt, - jsi::String backupID, - jsi::String backupSecret, - jsi::String encryptedUserKeys, - jsi::String encryptedUserData) override; + virtual jsi::Value + restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) override; public: CommCoreModule(std::shared_ptr jsInvoker); 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 @@ -1105,26 +1105,14 @@ }); } -jsi::Value CommCoreModule::restoreBackup( - jsi::Runtime &rt, - jsi::String backupID, - jsi::String backupSecret, - jsi::String encryptedUserKeys, - jsi::String encryptedUserData) { - std::string backupIDStr = backupID.utf8(rt); +jsi::Value +CommCoreModule::restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); - std::string encryptedUserKeysStr = encryptedUserKeys.utf8(rt); - std::string encryptedUserDataStr = encryptedUserData.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( promise, this->jsInvoker_, innerRt); - ::restoreBackup( - rust::string(backupIDStr), - rust::string(backupSecretStr), - rust::string(encryptedUserKeysStr), - rust::string(encryptedUserDataStr), - currentID); + ::restoreBackup(rust::string(backupSecretStr), currentID); }); } 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 @@ -132,7 +132,7 @@ return static_cast(&turboModule)->createNewBackup(rt, args[0].asString(rt), args[1].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), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt)); + return static_cast(&turboModule)->restoreBackup(rt, args[0].asString(rt)); } CommCoreModuleSchemaCxxSpecJSI::CommCoreModuleSchemaCxxSpecJSI(std::shared_ptr jsInvoker) @@ -175,7 +175,7 @@ methodMap_["setCommServicesAccessToken"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_setCommServicesAccessToken}; methodMap_["clearCommServicesAccessToken"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_clearCommServicesAccessToken}; methodMap_["createNewBackup"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_createNewBackup}; - methodMap_["restoreBackup"] = MethodMetadata {4, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_restoreBackup}; + 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 @@ -58,7 +58,7 @@ virtual jsi::Value setCommServicesAccessToken(jsi::Runtime &rt, jsi::String accessToken) = 0; virtual jsi::Value clearCommServicesAccessToken(jsi::Runtime &rt) = 0; virtual jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret, jsi::String userData) = 0; - virtual jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupID, jsi::String backupSecret, jsi::String encryptedUserKeys, jsi::String encryptedUserData) = 0; + virtual jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) = 0; }; @@ -384,13 +384,13 @@ return bridging::callFromJs( rt, &T::createNewBackup, jsInvoker_, instance_, std::move(backupSecret), std::move(userData)); } - jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupID, jsi::String backupSecret, jsi::String encryptedUserKeys, jsi::String encryptedUserData) override { + jsi::Value restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) override { static_assert( - bridging::getParameterCount(&T::restoreBackup) == 5, - "Expected restoreBackup(...) to have 5 parameters"); + bridging::getParameterCount(&T::restoreBackup) == 2, + "Expected restoreBackup(...) to have 2 parameters"); return bridging::callFromJs( - rt, &T::restoreBackup, jsInvoker_, instance_, std::move(backupID), std::move(backupSecret), std::move(encryptedUserKeys), std::move(encryptedUserData)); + rt, &T::restoreBackup, jsInvoker_, instance_, std::move(backupSecret)); } private: 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 @@ -1,12 +1,21 @@ use crate::argon2_tools::compute_backup_key; +use crate::argon2_tools::compute_backup_key_str; use crate::constants::aes; +use crate::constants::secure_store; +use crate::ffi::secure_store_get; use crate::handle_string_result_as_callback; +use crate::BACKUP_SOCKET_ADDR; use crate::RUNTIME; -use base64::{prelude::BASE64_STANDARD, Engine}; +use backup_client::BackupDescriptor; +use backup_client::LatestBackupIDResponse; +use backup_client::RequestedData; +use backup_client::{BackupClient, BackupData, UserIdentity}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::error::Error; pub mod ffi { + use crate::handle_void_result_as_callback; + use super::*; pub fn create_backup_sync( @@ -24,37 +33,27 @@ pickle_key, pickled_account, user_data, - ); - handle_string_result_as_callback(result, promise_id); + ) + .await; + handle_void_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, - ) { + pub fn restore_backup_sync(backup_secret: String, promise_id: u32) { RUNTIME.spawn(async move { - let result = restore_backup( - backup_id, - backup_secret, - encrypted_user_keys, - encrypted_user_data, - ); + let result = restore_backup(backup_secret).await; handle_string_result_as_callback(result, promise_id); }); } } -pub fn create_backup( +pub async fn create_backup( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, user_data: String, -) -> Result> { +) -> Result<(), Box> { let mut backup_key = compute_backup_key(backup_secret.as_bytes(), backup_id.as_bytes())?; @@ -71,41 +70,68 @@ }; 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(), - ) + let backup_client = BackupClient::new(BACKUP_SOCKET_ADDR)?; + + let user_identity = get_user_identity_from_secure_store()?; + + let backup_data = BackupData { + backup_id, + user_data: encrypted_user_data, + user_keys: encrypted_user_keys, + attachments: Vec::new(), + }; + + backup_client + .upload_backup(&user_identity, backup_data) + .await?; + + Ok(()) } -pub fn restore_backup( - backup_id: String, +pub async fn restore_backup( backup_secret: String, - encrypted_user_keys: String, - encrypted_user_data: String, ) -> Result> { - let mut encrypted_user_keys: Vec = - BASE64_STANDARD.decode(&encrypted_user_keys)?; - let mut encrypted_user_data: Vec = - BASE64_STANDARD.decode(&encrypted_user_data)?; + let backup_client = BackupClient::new(BACKUP_SOCKET_ADDR)?; - let mut backup_id = backup_id.into_bytes(); + let user_identity = get_user_identity_from_secure_store()?; - let mut backup_key = - compute_backup_key(backup_secret.as_bytes(), &mut backup_id)?; + let latest_backup_descriptor = BackupDescriptor::Latest { + username: user_identity.user_id.clone(), + }; + + let backup_id_response = backup_client + .download_backup_data(&latest_backup_descriptor, RequestedData::BackupID) + .await?; + + let LatestBackupIDResponse { backup_id } = + serde_json::from_slice(&backup_id_response)?; + + let mut backup_key = compute_backup_key_str(&backup_secret, &backup_id)?; + + let mut encrypted_user_keys = backup_client + .download_backup_data(&latest_backup_descriptor, RequestedData::UserKeys) + .await?; let mut user_keys = UserKeys::from_encrypted(&mut encrypted_user_keys, &mut backup_key)?; + let backup_data_descriptor = BackupDescriptor::BackupID { + backup_id, + user_identity, + }; + + let mut encrypted_user_data = backup_client + .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: serde_json::Value = serde_json::from_slice(&user_data)?; + Ok( json!({ - "userData": String::from_utf8(user_data)?, + "userData": user_data, "pickleKey": user_keys.pickle_key, "pickledAccount": user_keys.pickled_account, }) @@ -113,6 +139,15 @@ ) } +fn get_user_identity_from_secure_store() -> Result +{ + Ok(UserIdentity { + user_id: secure_store_get(secure_store::USER_ID)?, + access_token: secure_store_get(secure_store::COMM_SERVICES_ACCESS_TOKEN)?, + device_id: secure_store_get(secure_store::DEVICE_ID)?, + }) +} + #[derive(Debug, Serialize, Deserialize)] struct UserKeys { backup_data_key: [u8; 32], 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,6 +1,12 @@ -#[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 { + pub const COMM_SERVICES_ACCESS_TOKEN: &str = "accessToken"; + pub const USER_ID: &str = "userID"; + pub const DEVICE_ID: &str = "deviceID"; +} 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 @@ -212,13 +212,7 @@ ); #[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 restore_backup_sync(backup_secret: String, promise_id: u32); } // Secure store @@ -230,7 +224,6 @@ #[cxx_name = "secureStoreSet"] fn secure_store_set(key: &str, value: String) -> Result<()>; - #[allow(unused)] #[cxx_name = "secureStoreGet"] fn secure_store_get(key: &str) -> Result; } diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -108,13 +108,8 @@ +getCommServicesAuthMetadata: () => Promise; +setCommServicesAccessToken: (accessToken: string) => Promise; +clearCommServicesAccessToken: () => Promise; - +createNewBackup: (backupSecret: string, userData: string) => Promise; - +restoreBackup: ( - backupID: string, - backupSecret: string, - encryptedUserKeys: string, - encryptedUserData: string, - ) => Promise; + +createNewBackup: (backupSecret: string, userData: string) => Promise; + +restoreBackup: (backupSecret: string) => Promise; } export interface CoreModuleSpec extends Spec {