gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (e0f574e2 -> 26503410)


From: gnunet
Subject: [taler-wallet-core] branch master updated (e0f574e2 -> 26503410)
Date: Thu, 07 Jan 2021 18:56:15 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository wallet-core.

    from e0f574e2 re-add secretbox functionality of nacl
     new b2e213ba imports
     new 26503410 implement backup encryption, some more CLI commands

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-wallet-cli/src/index.ts             |  53 ++++++++
 .../src/crypto/primitives/nacl-fast.ts             |  12 +-
 .../taler-wallet-core/src/operations/backup.ts     | 140 ++++++++++++++++++++-
 .../taler-wallet-core/src/types/backupTypes.ts     |   1 +
 packages/taler-wallet-core/src/types/dbTypes.ts    |   4 -
 packages/taler-wallet-core/src/wallet.ts           |  25 ++++
 6 files changed, 221 insertions(+), 14 deletions(-)

diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index f4970e73..87e0e00d 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -409,6 +409,29 @@ backupCli.subcommand("exportPlain", 
"export-plain").action(async (args) => {
   });
 });
 
+backupCli
+  .subcommand("export", "export")
+  .requiredArgument("filename", clk.STRING, {
+    help: "backup filename",
+  })
+  .action(async (args) => {
+    await withWallet(args, async (wallet) => {
+      const backup = await wallet.exportBackupEncrypted();
+      fs.writeFileSync(args.export.filename, backup);
+    });
+  });
+
+backupCli
+  .subcommand("import", "import")
+  .requiredArgument("filename", clk.STRING, {
+    help: "backup filename",
+  })
+  .action(async (args) => {
+    await withWallet(args, async (wallet) => {
+      const backupEncBlob = fs.readFileSync(args.import.filename);
+      await wallet.importBackupEncrypted(backupEncBlob);
+    });
+  });
 
 backupCli.subcommand("importPlain", "import-plain").action(async (args) => {
   await withWallet(args, async (wallet) => {
@@ -417,6 +440,36 @@ backupCli.subcommand("importPlain", 
"import-plain").action(async (args) => {
   });
 });
 
+backupCli.subcommand("recoverySave", "save-recovery").action(async (args) => {
+  await withWallet(args, async (wallet) => {
+    const recoveryJson = await wallet.getBackupRecovery();
+    console.log(JSON.stringify(recoveryJson, undefined, 2));
+  });
+});
+
+backupCli.subcommand("run", "run").action(async (args) => {
+  await withWallet(args, async (wallet) => {
+    await wallet.runBackupCycle();
+  });
+});
+
+backupCli
+  .subcommand("recoveryLoad", "load-recovery")
+  .action(async (args) => {});
+
+backupCli.subcommand("status", "status").action(async (args) => {});
+
+backupCli
+  .subcommand("addProvider", "add-provider")
+  .requiredArgument("url", clk.STRING)
+  .action(async (args) => {
+    await withWallet(args, async (wallet) => {
+      wallet.addBackupProvider({
+        backupProviderBaseUrl: args.addProvider.url,
+      });
+    });
+  });
+
 const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
   help:
     "Subcommands for advanced operations (only use if you know what you're 
doing!).",
diff --git a/packages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts 
b/packages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts
index ceb60146..acaebf54 100644
--- a/packages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts
+++ b/packages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts
@@ -2990,7 +2990,11 @@ export function sign_ed25519_pk_to_curve25519(
   return x25519_pk;
 }
 
-export function secretbox(msg: Uint8Array, nonce: Uint8Array, key: Uint8Array) 
{
+export function secretbox(
+  msg: Uint8Array,
+  nonce: Uint8Array,
+  key: Uint8Array,
+): Uint8Array {
   checkArrayTypes(msg, nonce, key);
   checkLengths(key, nonce);
   var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length);
@@ -3005,15 +3009,15 @@ export function secretbox_open(
   box: Uint8Array,
   nonce: Uint8Array,
   key: Uint8Array,
-) {
+): Uint8Array | undefined {
   checkArrayTypes(box, nonce, key);
   checkLengths(key, nonce);
   var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length);
   var m = new Uint8Array(c.length);
   for (var i = 0; i < box.length; i++)
     c[i + crypto_secretbox_BOXZEROBYTES] = box[i];
-  if (c.length < 32) return null;
-  if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return null;
+  if (c.length < 32) return undefined;
+  if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return 
undefined;
   return m.subarray(crypto_secretbox_ZEROBYTES);
 }
 
diff --git a/packages/taler-wallet-core/src/operations/backup.ts 
b/packages/taler-wallet-core/src/operations/backup.ts
index 4f736c3d..5108dccf 100644
--- a/packages/taler-wallet-core/src/operations/backup.ts
+++ b/packages/taler-wallet-core/src/operations/backup.ts
@@ -74,6 +74,7 @@ import {
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants";
 import { AmountJson, Amounts, codecForAmountString } from "../util/amounts";
 import {
+  bytesToString,
   decodeCrock,
   eddsaGetPublic,
   EddsaKeyPair,
@@ -102,11 +103,13 @@ import {
   readSuccessResponseJsonOrThrow,
 } from "../util/http";
 import { Logger } from "../util/logging";
-import { gzipSync } from "fflate";
+import { gunzipSync, gzipSync } from "fflate";
 import { kdf } from "../crypto/primitives/kdf";
 import { initRetryInfo } from "../util/retries";
 import { RefreshReason } from "../types/walletTypes";
 import { CryptoApi } from "../crypto/workers/cryptoApi";
+import { secretbox, secretbox_open } from "../crypto/primitives/nacl-fast";
+import { str } from "../i18n";
 
 interface WalletBackupConfState {
   deviceId: string;
@@ -588,10 +591,54 @@ export async function exportBackup(
   );
 }
 
+function concatArrays(xs: Uint8Array[]): Uint8Array {
+  let len = 0;
+  for (const x of xs) {
+    len += x.byteLength;
+  }
+  const out = new Uint8Array(len);
+  let offset = 0;
+  for (const x of xs) {
+    out.set(x, offset);
+    offset += x.length;
+  }
+  return out;
+}
+
+const magic = "TLRWBK01";
+
+/**
+ * Encrypt the backup.
+ *
+ * Blob format:
+ * Magic "TLRWBK01" (8 bytes)
+ * Nonce (24 bytes)
+ * Compressed JSON blob (rest)
+ */
 export async function encryptBackup(
   config: WalletBackupConfState,
   blob: WalletBackupContentV1,
 ): Promise<Uint8Array> {
+  const chunks: Uint8Array[] = [];
+  chunks.push(stringToBytes(magic));
+  const nonceStr = config.lastBackupNonce;
+  checkLogicInvariant(!!nonceStr);
+  const nonce = decodeCrock(nonceStr).slice(0, 24);
+  chunks.push(nonce);
+  const backupJsonContent = canonicalJson(blob);
+  logger.trace("backup JSON size", backupJsonContent.length);
+  const compressedContent = gzipSync(stringToBytes(backupJsonContent));
+  const secret = deriveBlobSecret(config);
+  const encrypted = secretbox(compressedContent, nonce.slice(0, 24), secret);
+  chunks.push(encrypted);
+  logger.trace(`enc: ${encodeCrock(encrypted)}`);
+  return concatArrays(chunks);
+}
+
+export async function decryptBackup(
+  config: WalletBackupConfState,
+  box: Uint8Array,
+): Promise<WalletBackupContentV1> {
   throw Error("not implemented");
 }
 
@@ -778,7 +825,10 @@ async function getDenomSelStateFromBackup(
   exchangeBaseUrl: string,
   sel: BackupDenomSel,
 ): Promise<DenomSelectionState> {
-  const d0 = await tx.get(Stores.denominations, [exchangeBaseUrl, 
sel[0].denom_pub_hash]);
+  const d0 = await tx.get(Stores.denominations, [
+    exchangeBaseUrl,
+    sel[0].denom_pub_hash,
+  ]);
   checkBackupInvariant(!!d0);
   const selectedDenoms: {
     denomPubHash: string;
@@ -787,16 +837,20 @@ async function getDenomSelStateFromBackup(
   let totalCoinValue = Amounts.getZero(d0.value.currency);
   let totalWithdrawCost = Amounts.getZero(d0.value.currency);
   for (const s of sel) {
-    const d = await tx.get(Stores.denominations, [exchangeBaseUrl, 
s.denom_pub_hash]);
+    const d = await tx.get(Stores.denominations, [
+      exchangeBaseUrl,
+      s.denom_pub_hash,
+    ]);
     checkBackupInvariant(!!d);
     totalCoinValue = Amounts.add(totalCoinValue, d.value).amount;
-    totalWithdrawCost = Amounts.add(totalWithdrawCost, d.value, 
d.feeWithdraw).amount;
+    totalWithdrawCost = Amounts.add(totalWithdrawCost, d.value, d.feeWithdraw)
+      .amount;
   }
   return {
     selectedDenoms,
     totalCoinValue,
     totalWithdrawCost,
-  }
+  };
 }
 
 export async function importBackup(
@@ -1407,6 +1461,15 @@ function deriveAccountKeyPair(
   };
 }
 
+function deriveBlobSecret(bc: WalletBackupConfState): Uint8Array {
+  return kdf(
+    32,
+    decodeCrock(bc.walletRootPriv),
+    stringToBytes("taler-sync-blob-secret-salt"),
+    stringToBytes("taler-sync-blob-secret-info"),
+  );
+}
+
 /**
  * Do one backup cycle that consists of:
  * 1. Exporting a backup and try to upload it.
@@ -1566,6 +1629,71 @@ export async function importBackupPlain(
 /**
  * Get information about the current state of wallet backups.
  */
-export function getBackupInfo(ws: InternalWalletState): Promise<BackupInfo> {
+export async function getBackupInfo(
+  ws: InternalWalletState,
+): Promise<BackupInfo> {
   throw Error("not implemented");
 }
+
+export interface BackupRecovery {
+  walletRootPriv: string;
+  providers: {
+    url: string;
+  }[];
+}
+
+/**
+ * Get information about the current state of wallet backups.
+ */
+export async function getBackupRecovery(
+  ws: InternalWalletState,
+): Promise<BackupRecovery> {
+  const bs = await provideBackupState(ws);
+  const providers = await ws.db.iter(Stores.backupProviders).toArray();
+  return {
+    providers: providers
+      .filter((x) => x.active)
+      .map((x) => {
+        return {
+          url: x.baseUrl,
+        };
+      }),
+    walletRootPriv: bs.walletRootPriv,
+  };
+}
+
+export async function exportBackupEncrypted(
+  ws: InternalWalletState,
+): Promise<Uint8Array> {
+  await provideBackupState(ws);
+  const blob = await exportBackup(ws);
+  const bs = await ws.db.runWithWriteTransaction(
+    [Stores.config],
+    async (tx) => {
+      return await getWalletBackupState(ws, tx);
+    },
+  );
+  return encryptBackup(bs, blob);
+}
+
+export async function importBackupEncrypted(
+  ws: InternalWalletState,
+  data: Uint8Array,
+): Promise<void> {
+  const backupConfig = await provideBackupState(ws);
+  const rMagic = bytesToString(data.slice(0, 8));
+  if (rMagic != magic) {
+    throw Error("invalid backup file (magic tag mismatch)");
+  }
+
+  const nonce = data.slice(8, 8 + 24);
+  const box = data.slice(8 + 24);
+  const secret = deriveBlobSecret(backupConfig);
+  const dataCompressed = secretbox_open(box, nonce, secret);
+  if (!dataCompressed) {
+    throw Error("decryption failed");
+  }
+  const blob = JSON.parse(bytesToString(gunzipSync(dataCompressed)));
+  const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
+  await importBackup(ws, blob, cryptoData);
+}
diff --git a/packages/taler-wallet-core/src/types/backupTypes.ts 
b/packages/taler-wallet-core/src/types/backupTypes.ts
index f7bd8784..32ff8c52 100644
--- a/packages/taler-wallet-core/src/types/backupTypes.ts
+++ b/packages/taler-wallet-core/src/types/backupTypes.ts
@@ -40,6 +40,7 @@
  *     payment cost.
  * 11. Failed refunds do not have any information about why they failed.
  *     => This should go into the general "error reports"
+ * 12. Tombstones for removed backup providers
  *
  * Questions:
  * 1. What happens when two backups are merged that have
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts 
b/packages/taler-wallet-core/src/types/dbTypes.ts
index dcfdf331..1c9f546d 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -38,10 +38,6 @@ import { Index, Store } from "../util/query";
 import { TalerErrorDetails, RefreshReason } from "./walletTypes";
 import {
   ReserveTransaction,
-  ReserveCreditTransaction,
-  ReserveWithdrawTransaction,
-  ReserveClosingTransaction,
-  ReserveRecoupTransaction,
 } from "./ReserveTransaction";
 import { Timestamp, Duration } from "../util/time";
 import { IDBKeyPath } from "idb-bridge";
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index b917246f..0b2b4d63 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -162,6 +162,11 @@ import {
   runBackupCycle,
   exportBackup,
   importBackupPlain,
+  exportBackupEncrypted,
+  importBackupEncrypted,
+  BackupRecovery,
+  getBackupRecovery,
+  AddBackupProviderRequest,
 } from "./operations/backup";
 
 const builtinCurrencies: CurrencyRecord[] = [
@@ -942,6 +947,26 @@ export class Wallet {
     return importBackupPlain(this.ws, backup);
   }
 
+  async exportBackupEncrypted() {
+    return exportBackupEncrypted(this.ws);
+  }
+
+  async importBackupEncrypted(backup: Uint8Array) {
+    return importBackupEncrypted(this.ws, backup);
+  }
+
+  async getBackupRecovery(): Promise<BackupRecovery> {
+    return getBackupRecovery(this.ws);
+  }
+
+  async addBackupProvider(req: AddBackupProviderRequest): Promise<void> {
+    return addBackupProvider(this.ws, req);
+  }
+
+  async runBackupCycle(): Promise<void> {
+    return runBackupCycle(this.ws);
+  }
+
   /**
    * Implementation of the "wallet-core" API.
    */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]