diff --git a/lib/types/backup-types.js b/lib/types/backup-types.js --- a/lib/types/backup-types.js +++ b/lib/types/backup-types.js @@ -25,6 +25,6 @@ export type BackupEncrypted = { +backupID: string, - +userKeys: Uint8Array, - +userData: Uint8Array, + +userKeys: string, + +userData: string, }; diff --git a/native/backup/api.js b/native/backup/api.js --- a/native/backup/api.js +++ b/native/backup/api.js @@ -8,22 +8,24 @@ import { toBase64URL } from 'lib/utils/base64.js'; import { handleHTTPResponseError } from 'lib/utils/services-utils.js'; -import { getBackupBytesFromBlob } from './conversion-utils.js'; +import { getBackupStringFromBlob } from './conversion-utils.js'; import { commUtilsModule } from '../native-modules.js'; function getBackupFormData(backup: BackupEncrypted): FormData { const { backupID, userKeys, userData } = backup; - const userKeysHash = commUtilsModule.sha256(userKeys.buffer); - const userDataHash = commUtilsModule.sha256(userData.buffer); - const userKeysStr = commUtilsModule.base64EncodeBuffer(userKeys.buffer); - const userDataStr = commUtilsModule.base64EncodeBuffer(userData.buffer); + const userKeysHash = commUtilsModule.sha256( + commUtilsModule.encodeStringToUTF8ArrayBuffer(userKeys), + ); + const userDataHash = commUtilsModule.sha256( + commUtilsModule.encodeStringToUTF8ArrayBuffer(userData), + ); const formData = new FormData(); formData.append('backup_id', backupID); formData.append('user_keys_hash', toBase64URL(userKeysHash)); - formData.append('user_keys', userKeysStr); + formData.append('user_keys', backup.userKeys); formData.append('user_data_hash', toBase64URL(userDataHash)); - formData.append('user_data', userDataStr); + formData.append('user_data', backup.userData); formData.append('attachments', ''); return formData; } @@ -71,7 +73,7 @@ async function getUserKeys( backupID: string, auth: BackupAuth, -): Promise { +): Promise { const authHeader = getBackupAuthorizationHeader(auth); const getUserKeysEndpoint = backupService.httpEndpoints.GET_USER_KEYS; @@ -88,13 +90,13 @@ handleHTTPResponseError(getUserKeysResponse); const blob = await getUserKeysResponse.blob(); - return getBackupBytesFromBlob(blob); + return getBackupStringFromBlob(blob); } async function getUserData( backupID: string, auth: BackupAuth, -): Promise { +): Promise { const authHeader = getBackupAuthorizationHeader(auth); const getUserDataEndpoint = backupService.httpEndpoints.GET_USER_DATA; @@ -111,7 +113,7 @@ handleHTTPResponseError(getUserDataResponse); const blob = await getUserDataResponse.blob(); - return getBackupBytesFromBlob(blob); + return getBackupStringFromBlob(blob); } export { uploadBackup, getBackupID, getUserKeys, getUserData }; diff --git a/native/backup/constants.js b/native/backup/constants.js --- a/native/backup/constants.js +++ b/native/backup/constants.js @@ -1,5 +1,3 @@ // @flow -export const BACKUP_ID_LENGTH = 32; //256 bits - export const BACKUP_HASH_STORAGE_KEY = 'RECENT_USER_DATA_HASH'; diff --git a/native/backup/conversion-utils.js b/native/backup/conversion-utils.js --- a/native/backup/conversion-utils.js +++ b/native/backup/conversion-utils.js @@ -3,11 +3,9 @@ import { commUtilsModule } from '../native-modules.js'; import { arrayBufferFromBlob } from '../utils/blob-utils-module.js'; -function getBackupBytesFromBlob(blob: Blob): Uint8Array { +function getBackupStringFromBlob(blob: Blob): string { const buffer = arrayBufferFromBlob(blob); - const str = commUtilsModule.decodeUTF8ArrayBufferToString(buffer); - const decodedBuffer = commUtilsModule.base64DecodeBuffer(str); - return new Uint8Array(decodedBuffer); + return commUtilsModule.decodeUTF8ArrayBufferToString(buffer); } function convertObjToBytes(obj: T): Uint8Array { @@ -21,4 +19,4 @@ return JSON.parse(str); } -export { getBackupBytesFromBlob, convertObjToBytes, convertBytesToObj }; +export { getBackupStringFromBlob, convertObjToBytes, convertBytesToObj }; diff --git a/native/backup/encryption.js b/native/backup/encryption.js deleted file mode 100644 --- a/native/backup/encryption.js +++ /dev/null @@ -1,62 +0,0 @@ -// @flow - -import { hexToUintArray } from 'lib/media/data-utils.js'; -import type { - Backup, - BackupEncrypted, - UserData, - UserKeys, -} from 'lib/types/backup-types.js'; - -import { convertBytesToObj, convertObjToBytes } from './conversion-utils.js'; -import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; -import { commCoreModule } from '../native-modules.js'; -import * as AES from '../utils/aes-crypto-module.js'; - -async function getBackupKey(backupID: string): Promise { - const nativeCredentials = await fetchNativeKeychainCredentials(); - if (!nativeCredentials) { - throw new Error('Native credentials are missing'); - } - const { password } = nativeCredentials; - const backupKey = await commCoreModule.computeBackupKey(password, backupID); - return new Uint8Array(backupKey); -} - -async function encryptBackup(backup: Backup): Promise { - const { backupID, userKeys, userData } = backup; - const userKeysBytes = convertObjToBytes(userKeys); - const backupKey = await getBackupKey(backupID); - const ct1 = AES.encrypt(backupKey, userKeysBytes); - - const userDataBytes = convertObjToBytes(userData); - const backupDataKey = hexToUintArray(userKeys.backupDataKey); - const ct2 = AES.encrypt(backupDataKey, userDataBytes); - - return { backupID, userKeys: ct1, userData: ct2 }; -} - -async function decryptUserKeys( - backupID: string, - userKeysBytes: ArrayBuffer, -): Promise { - const backupKey = await getBackupKey(backupID); - const decryptedUserKeys = AES.decrypt( - backupKey, - new Uint8Array(userKeysBytes), - ); - return convertBytesToObj(decryptedUserKeys); -} - -async function decryptUserData( - backupDataKey: string, - userDataBytes: ArrayBuffer, -): Promise { - const decryptedUserData = AES.decrypt( - hexToUintArray(backupDataKey), - new Uint8Array(userDataBytes), - ); - return convertBytesToObj(decryptedUserData); -} - -export { getBackupKey, encryptBackup, decryptUserKeys, decryptUserData }; 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 @@ -3,20 +3,17 @@ import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; -import { uintArrayToHexString } from 'lib/media/data-utils.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; -import type { BackupAuth, UserData, UserKeys } from 'lib/types/backup-types.js'; +import type { + BackupAuth, + UserData, + BackupEncrypted, +} from 'lib/types/backup-types.js'; import { getBackupID, getUserData, getUserKeys, uploadBackup } from './api.js'; -import { BACKUP_ID_LENGTH } from './constants.js'; -import { - decryptUserData, - decryptUserKeys, - encryptBackup, -} from './encryption.js'; +import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; -import { generateKey } from '../utils/aes-crypto-module.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; // purpose of this result is to improve logging and @@ -25,7 +22,6 @@ getBackupID?: boolean, getUserKeys?: boolean, getUserData?: boolean, - decryptUserKeys?: boolean, decryptUserData?: boolean, userDataIntegrity?: boolean, error?: Error, @@ -38,6 +34,14 @@ ) => Promise, }; +async function getBackupSecret(): Promise { + const nativeCredentials = await fetchNativeKeychainCredentials(); + if (!nativeCredentials) { + throw new Error('Native credentials are missing'); + } + return nativeCredentials.password; +} + function useClientBackup(): ClientBackup { const accessToken = useSelector(state => state.commServicesAccessToken); const currentUserID = useSelector( @@ -52,24 +56,16 @@ } console.info('Start uploading backup...'); - const backupDataKey = generateKey(); - - const [ed25519, backupID] = await Promise.all([ - getContentSigningKey(), - commCoreModule.generateRandomString(BACKUP_ID_LENGTH), - ]); + const backupSecret = await getBackupSecret(); - const userKeys: UserKeys = { - backupDataKey: uintArrayToHexString(backupDataKey), - ed25519, - }; + const encryptedBackupStr = await commCoreModule.createNewBackup( + backupSecret, + JSON.stringify(userData), + ); - const encryptedBackup = await encryptBackup({ - backupID, - userKeys, - userData, - }); + const encryptedBackup: BackupEncrypted = JSON.parse(encryptedBackupStr); + const ed25519 = await getContentSigningKey(); const backupAuth: BackupAuth = { userID: currentUserID, accessToken: accessToken ? accessToken : '', @@ -92,7 +88,6 @@ getBackupID: undefined, getUserKeys: undefined, getUserData: undefined, - decryptUserKeys: undefined, decryptUserData: undefined, userDataIntegrity: undefined, error: undefined, @@ -101,7 +96,7 @@ const backupIDPromise: Promise = (async () => { try { // We are using UserID instead of the username. - // The reason is tha the initial version of the backup service + // 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; @@ -128,7 +123,7 @@ deviceID: ed25519, }; - const userKeysPromise: Promise = (async () => { + const userKeysPromise: Promise = (async () => { try { const userKeysResponse = await getUserKeys(backupID, backupAuth); result.getUserKeys = true; @@ -139,7 +134,7 @@ return undefined; } })(); - const userDataPromise: Promise = (async () => { + const userDataPromise: Promise = (async () => { try { const userDataResponse = await getUserData(backupID, backupAuth); result.getUserData = true; @@ -162,33 +157,24 @@ return result; } - let userKeys; - try { - userKeys = await decryptUserKeys(backupID, userKeysResponse.buffer); - result.decryptUserKeys = true; - } catch (e) { - result.decryptUserKeys = false; - result.error = e; - } - - if (!userKeys) { - result.decryptUserKeys = false; - result.error = new Error('UserKeys is empty'); - return result; - } - if (!userDataResponse) { result.getUserData = false; result.error = new Error('UserData response is empty'); return result; } - let userData; + const backupSecret = await getBackupSecret(); + + let userData: UserData; try { - userData = await decryptUserData( - userKeys.backupDataKey, - userDataResponse.buffer, + 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;