diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/backup/index.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/backup/index.ts | 188 |
1 files changed, 89 insertions, 99 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index e35765165..a5e8dbd42 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -43,7 +43,6 @@ import { TalerErrorDetail, TalerPreciseTimestamp, URL, - WalletBackupContentV1, buildCodecForObject, buildCodecForUnion, bytesToString, @@ -99,9 +98,8 @@ import { TaskIdentifiers, } from "../common.js"; import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js"; -import { exportBackup } from "./export.js"; -import { BackupCryptoPrecomputedData, importBackup } from "./import.js"; -import { getWalletBackupState, provideBackupState } from "./state.js"; +import { WalletStoresV1 } from "../../db.js"; +import { GetReadOnlyAccess } from "../../util/query.js"; const logger = new Logger("operations/backup.ts"); @@ -131,7 +129,7 @@ const magic = "TLRWBK01"; */ export async function encryptBackup( config: WalletBackupConfState, - blob: WalletBackupContentV1, + blob: any, ): Promise<Uint8Array> { const chunks: Uint8Array[] = []; chunks.push(stringToBytes(magic)); @@ -150,64 +148,6 @@ export async function encryptBackup( return concatArrays(chunks); } -/** - * Compute cryptographic values for a backup blob. - * - * FIXME: Take data that we already know from the DB. - * FIXME: Move computations into crypto worker. - */ -async function computeBackupCryptoData( - cryptoApi: TalerCryptoInterface, - backupContent: WalletBackupContentV1, -): Promise<BackupCryptoPrecomputedData> { - const cryptoData: BackupCryptoPrecomputedData = { - coinPrivToCompletedCoin: {}, - rsaDenomPubToHash: {}, - proposalIdToContractTermsHash: {}, - proposalNoncePrivToPub: {}, - reservePrivToPub: {}, - }; - for (const backupExchangeDetails of backupContent.exchange_details) { - for (const backupDenom of backupExchangeDetails.denominations) { - if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) { - throw Error("unsupported cipher"); - } - for (const backupCoin of backupDenom.coins) { - const coinPub = encodeCrock( - eddsaGetPublic(decodeCrock(backupCoin.coin_priv)), - ); - const blindedCoin = rsaBlind( - hash(decodeCrock(backupCoin.coin_priv)), - decodeCrock(backupCoin.blinding_key), - decodeCrock(backupDenom.denom_pub.rsa_public_key), - ); - cryptoData.coinPrivToCompletedCoin[backupCoin.coin_priv] = { - coinEvHash: encodeCrock(hash(blindedCoin)), - coinPub, - }; - } - cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] = - encodeCrock(hashDenomPub(backupDenom.denom_pub)); - } - } - for (const backupWg of backupContent.withdrawal_groups) { - cryptoData.reservePrivToPub[backupWg.reserve_priv] = encodeCrock( - eddsaGetPublic(decodeCrock(backupWg.reserve_priv)), - ); - } - for (const purch of backupContent.purchases) { - if (!purch.contract_terms_raw) continue; - const { h: contractTermsHash } = await cryptoApi.hashString({ - str: canonicalJson(purch.contract_terms_raw), - }); - const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv))); - cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub; - cryptoData.proposalIdToContractTermsHash[purch.proposal_id] = - contractTermsHash; - } - return cryptoData; -} - function deriveAccountKeyPair( bc: WalletBackupConfState, providerUrl: string, @@ -262,7 +202,9 @@ async function runBackupCycleForProvider( return TaskRunResult.finished(); } - const backupJson = await exportBackup(ws); + //const backupJson = await exportBackup(ws); + // FIXME: re-implement backup + const backupJson = {}; const backupConfig = await provideBackupState(ws); const encBackup = await encryptBackup(backupConfig, backupJson); const currentBackupHash = hash(encBackup); @@ -441,9 +383,9 @@ async function runBackupCycleForProvider( logger.info("conflicting backup found"); const backupEnc = new Uint8Array(await resp.bytes()); const backupConfig = await provideBackupState(ws); - const blob = await decryptBackup(backupConfig, backupEnc); - const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob); - await importBackup(ws, blob, cryptoData); + // const blob = await decryptBackup(backupConfig, backupEnc); + // FIXME: Re-implement backup import with merging + // await importBackup(ws, blob, cryptoData); await ws.db .mktx((x) => [x.backupProviders, x.operationRetries]) .runReadWrite(async (tx) => { @@ -789,18 +731,6 @@ export interface BackupInfo { providers: ProviderInfo[]; } -export async function importBackupPlain( - ws: InternalWalletState, - blob: any, -): Promise<void> { - // FIXME: parse - const backup: WalletBackupContentV1 = blob; - - const cryptoData = await computeBackupCryptoData(ws.cryptoApi, backup); - - await importBackup(ws, blob, cryptoData); -} - export enum ProviderPaymentType { Unpaid = "unpaid", Pending = "pending", @@ -1036,23 +966,10 @@ export async function loadBackupRecovery( } } -export async function exportBackupEncrypted( - ws: InternalWalletState, -): Promise<Uint8Array> { - await provideBackupState(ws); - const blob = await exportBackup(ws); - const bs = await ws.db - .mktx((x) => [x.config]) - .runReadOnly(async (tx) => { - return await getWalletBackupState(ws, tx); - }); - return encryptBackup(bs, blob); -} - export async function decryptBackup( backupConfig: WalletBackupConfState, data: Uint8Array, -): Promise<WalletBackupContentV1> { +): Promise<any> { const rMagic = bytesToString(data.slice(0, 8)); if (rMagic != magic) { throw Error("invalid backup file (magic tag mismatch)"); @@ -1068,12 +985,85 @@ export async function decryptBackup( return JSON.parse(bytesToString(gunzipSync(dataCompressed))); } -export async function importBackupEncrypted( +export async function provideBackupState( ws: InternalWalletState, - data: Uint8Array, +): Promise<WalletBackupConfState> { + const bs: ConfigRecord | undefined = await ws.db + .mktx((stores) => [stores.config]) + .runReadOnly(async (tx) => { + return await tx.config.get(ConfigRecordKey.WalletBackupState); + }); + if (bs) { + checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); + return bs.value; + } + // We need to generate the key outside of the transaction + // due to how IndexedDB works. + const k = await ws.cryptoApi.createEddsaKeypair({}); + const d = getRandomBytes(5); + // FIXME: device ID should be configured when wallet is initialized + // and be based on hostname + const deviceId = `wallet-core-${encodeCrock(d)}`; + return await ws.db + .mktx((x) => [x.config]) + .runReadWrite(async (tx) => { + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + ConfigRecordKey.WalletBackupState, + ); + if (!backupStateEntry) { + backupStateEntry = { + key: ConfigRecordKey.WalletBackupState, + value: { + deviceId, + walletRootPub: k.pub, + walletRootPriv: k.priv, + lastBackupPlainHash: undefined, + }, + }; + await tx.config.put(backupStateEntry); + } + checkDbInvariant( + backupStateEntry.key === ConfigRecordKey.WalletBackupState, + ); + return backupStateEntry.value; + }); +} + +export async function getWalletBackupState( + ws: InternalWalletState, + tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>, +): Promise<WalletBackupConfState> { + const bs = await tx.config.get(ConfigRecordKey.WalletBackupState); + checkDbInvariant(!!bs, "wallet backup state should be in DB"); + checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); + return bs.value; +} + +export async function setWalletDeviceId( + ws: InternalWalletState, + deviceId: string, ): Promise<void> { - const backupConfig = await provideBackupState(ws); - const blob = await decryptBackup(backupConfig, data); - const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob); - await importBackup(ws, blob, cryptoData); + await provideBackupState(ws); + await ws.db + .mktx((x) => [x.config]) + .runReadWrite(async (tx) => { + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + ConfigRecordKey.WalletBackupState, + ); + if ( + !backupStateEntry || + backupStateEntry.key !== ConfigRecordKey.WalletBackupState + ) { + return; + } + backupStateEntry.value.deviceId = deviceId; + await tx.config.put(backupStateEntry); + }); +} + +export async function getWalletDeviceId( + ws: InternalWalletState, +): Promise<string> { + const bs = await provideBackupState(ws); + return bs.deviceId; } |