gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (b5b8f96c -> 1744b1a8)


From: gnunet
Subject: [taler-wallet-core] branch master updated (b5b8f96c -> 1744b1a8)
Date: Fri, 13 Mar 2020 14:34:23 +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 b5b8f96c improved error reporting / towards a working recoup
     new 51eef541 license header typo
     new 1744b1a8 signature verification for recoup

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:
 src/crypto/workers/cryptoApi.ts            | 43 +++++++++++++++--
 src/crypto/workers/cryptoImplementation.ts | 74 ++++++++++++++++++++++++++++--
 src/operations/exchanges.ts                |  2 +
 src/operations/recoup.ts                   | 44 +++++++++++++++++-
 src/types/dbTypes.ts                       |  8 ++++
 src/types/talerTypes.ts                    | 29 ++++++++++--
 src/util/time.ts                           | 10 ++++
 7 files changed, 194 insertions(+), 16 deletions(-)

diff --git a/src/crypto/workers/cryptoApi.ts b/src/crypto/workers/cryptoApi.ts
index 4adf2882..31ab4dd7 100644
--- a/src/crypto/workers/cryptoApi.ts
+++ b/src/crypto/workers/cryptoApi.ts
@@ -34,7 +34,13 @@ import {
 
 import { CryptoWorker } from "./cryptoWorker";
 
-import { RecoupRequest, CoinDepositPermission } from "../../types/talerTypes";
+import {
+  RecoupRequest,
+  CoinDepositPermission,
+  RecoupConfirmation,
+  ExchangeSignKeyJson,
+  EddsaPublicKeyString,
+} from "../../types/talerTypes";
 
 import {
   BenchmarkResult,
@@ -382,13 +388,30 @@ export class CryptoApi {
     );
   }
 
+  /**
+   * Validate the signature in a recoup confirmation.
+   */
+  isValidRecoupConfirmation(
+    recoupCoinPub: EddsaPublicKeyString,
+    recoupConfirmation: RecoupConfirmation,
+    exchangeSigningKeys: ExchangeSignKeyJson[],
+  ): Promise<boolean> {
+    return this.doRpc<boolean>(
+      "isValidRecoupConfirmation",
+      1,
+      recoupCoinPub,
+      recoupConfirmation,
+      exchangeSigningKeys,
+    );
+  }
+
   signDepositPermission(
-    depositInfo: DepositInfo
+    depositInfo: DepositInfo,
   ): Promise<CoinDepositPermission> {
     return this.doRpc<CoinDepositPermission>(
       "signDepositPermission",
       3,
-      depositInfo
+      depositInfo,
     );
   }
 
@@ -404,8 +427,18 @@ export class CryptoApi {
     return this.doRpc<boolean>("rsaVerify", 4, hm, sig, pk);
   }
 
-  isValidWireAccount(paytoUri: string, sig: string, masterPub: string): 
Promise<boolean> {
-    return this.doRpc<boolean>("isValidWireAccount", 4, paytoUri, sig, 
masterPub);
+  isValidWireAccount(
+    paytoUri: string,
+    sig: string,
+    masterPub: string,
+  ): Promise<boolean> {
+    return this.doRpc<boolean>(
+      "isValidWireAccount",
+      4,
+      paytoUri,
+      sig,
+      masterPub,
+    );
   }
 
   createRecoupRequest(coin: CoinRecord): Promise<RecoupRequest> {
diff --git a/src/crypto/workers/cryptoImplementation.ts 
b/src/crypto/workers/cryptoImplementation.ts
index 5659fec2..4d03e70f 100644
--- a/src/crypto/workers/cryptoImplementation.ts
+++ b/src/crypto/workers/cryptoImplementation.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2020 Taler Systems SA
 
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,6 +18,8 @@
  * Synchronous implementation of crypto-related functions for the wallet.
  *
  * The functionality is parameterized over an Emscripten environment.
+ *
+ * @author Florian Dold <address@hidden>
  */
 
 /**
@@ -34,7 +36,13 @@ import {
   CoinSourceType,
 } from "../../types/dbTypes";
 
-import { CoinDepositPermission, RecoupRequest } from "../../types/talerTypes";
+import {
+  CoinDepositPermission,
+  RecoupRequest,
+  RecoupConfirmation,
+  ExchangeSignKeyJson,
+  EddsaPublicKeyString,
+} from "../../types/talerTypes";
 import {
   BenchmarkResult,
   PlanchetCreationResult,
@@ -63,7 +71,11 @@ import {
 } from "../talerCrypto";
 import { randomBytes } from "../primitives/nacl-fast";
 import { kdf } from "../primitives/kdf";
-import { Timestamp, getTimestampNow } from "../../util/time";
+import {
+  Timestamp,
+  getTimestampNow,
+  timestampIsBetween,
+} from "../../util/time";
 
 enum SignaturePurpose {
   RESERVE_WITHDRAW = 1200,
@@ -76,6 +88,8 @@ enum SignaturePurpose {
   MERCHANT_PAYMENT_OK = 1104,
   WALLET_COIN_RECOUP = 1203,
   WALLET_COIN_LINK = 1204,
+  EXCHANGE_CONFIRM_RECOUP = 1039,
+  EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
 }
 
 function amountToBuffer(amount: AmountJson): Uint8Array {
@@ -131,6 +145,19 @@ function buildSigPS(purposeNum: number): 
SignaturePurposeBuilder {
   return new SignaturePurposeBuilder(purposeNum);
 }
 
+function checkSignKeyOkay(
+  key: string,
+  exchangeKeys: ExchangeSignKeyJson[],
+): boolean {
+  const now = getTimestampNow();
+  for (const k of exchangeKeys) {
+    if (k.key == key) {
+      return timestampIsBetween(now, k.stamp_start, k.stamp_end);
+    }
+  }
+  return false;
+}
+
 export class CryptoImplementation {
   static enableTracing: boolean = false;
 
@@ -216,7 +243,7 @@ export class CryptoImplementation {
       coin_sig: encodeCrock(coinSig),
       denom_pub_hash: coin.denomPubHash,
       denom_sig: coin.denomSig,
-      refreshed: (coin.coinSource.type === CoinSourceType.Refresh),
+      refreshed: coin.coinSource.type === CoinSourceType.Refresh,
     };
     return paybackRequest;
   }
@@ -327,7 +354,6 @@ export class CryptoImplementation {
    * and deposit permissions for each given coin.
    */
   signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission {
-
     const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
       .put(decodeCrock(depositInfo.contractTermsHash))
       .put(decodeCrock(depositInfo.wireInfoHash))
@@ -492,6 +518,44 @@ export class CryptoImplementation {
     return encodeCrock(sig);
   }
 
+  /**
+   * Validate the signature in a recoup confirmation.
+   */
+  isValidRecoupConfirmation(
+    recoupCoinPub: EddsaPublicKeyString,
+    recoupConfirmation: RecoupConfirmation,
+    exchangeSigningKeys: ExchangeSignKeyJson[],
+  ): boolean {
+    const pubEnc = recoupConfirmation.exchange_pub;
+    if (!checkSignKeyOkay(pubEnc, exchangeSigningKeys)) {
+      return false;
+    }
+
+    const sig = decodeCrock(recoupConfirmation.exchange_sig);
+    const pub = decodeCrock(pubEnc);
+
+    if (recoupConfirmation.old_coin_pub) {
+      // We're dealing with a refresh recoup
+      const p = buildSigPS(
+        SignaturePurpose.EXCHANGE_CONFIRM_RECOUP_REFRESH,
+      ).put(timestampToBuffer(recoupConfirmation.timestamp))
+       .put(amountToBuffer(Amounts.parseOrThrow(recoupConfirmation.amount)))
+       .put(decodeCrock(recoupCoinPub))
+       .put(decodeCrock(recoupConfirmation.old_coin_pub)).build();
+       return eddsaVerify(p, sig, pub)
+    } else if (recoupConfirmation.reserve_pub) {
+      const p = buildSigPS(
+        SignaturePurpose.EXCHANGE_CONFIRM_RECOUP_REFRESH,
+      ).put(timestampToBuffer(recoupConfirmation.timestamp))
+       .put(amountToBuffer(Amounts.parseOrThrow(recoupConfirmation.amount)))
+       .put(decodeCrock(recoupCoinPub))
+       .put(decodeCrock(recoupConfirmation.reserve_pub)).build();
+       return eddsaVerify(p, sig, pub)
+    } else {
+      throw Error("invalid recoup confirmation");
+    }
+  }
+
   benchmark(repetitions: number): BenchmarkResult {
     let time_hash = 0;
     for (let i = 0; i < repetitions; i++) {
diff --git a/src/operations/exchanges.ts b/src/operations/exchanges.ts
index 04238e61..f920a5a5 100644
--- a/src/operations/exchanges.ts
+++ b/src/operations/exchanges.ts
@@ -211,12 +211,14 @@ async function updateExchangeWithKeys(
       if (r.details) {
         // FIXME: We need to do some consistency checks!
       }
+      // FIXME: validate signing keys and merge with old set
       r.details = {
         auditors: exchangeKeysJson.auditors,
         currency: currency,
         lastUpdateTime: lastUpdateTimestamp,
         masterPublicKey: exchangeKeysJson.master_public_key,
         protocolVersion: protocolVersion,
+        signingKeys: exchangeKeysJson.signkeys,
       };
       r.updateStatus = ExchangeUpdateStatus.FetchWire;
       r.lastError = undefined;
diff --git a/src/operations/recoup.ts b/src/operations/recoup.ts
index 3097dd05..163f7759 100644
--- a/src/operations/recoup.ts
+++ b/src/operations/recoup.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019-2010 Taler Systems SA
+ (C) 2019-2020 Taler Systems SA
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -142,7 +142,26 @@ async function recoupWithdrawCoin(
     throw Error(`Coin's reserve doesn't match reserve on recoup`);
   }
 
-  // FIXME: verify signature
+  const exchange = await ws.db.get(Stores.exchanges, coin.exchangeBaseUrl);
+  if (!exchange) {
+    // FIXME: report inconsistency?
+    return;
+  }
+  const exchangeDetails = exchange.details;
+  if (!exchangeDetails) {
+    // FIXME: report inconsistency?
+    return;
+  }
+
+  const isValid = ws.cryptoApi.isValidRecoupConfirmation(
+    coin.coinPub,
+    recoupConfirmation,
+    exchangeDetails.signingKeys,
+  );
+
+  if (!isValid) {
+    throw Error("invalid recoup confirmation signature");
+  }
 
   // FIXME: verify that our expectations about the amount match
 
@@ -207,6 +226,27 @@ async function recoupRefreshCoin(
     throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
   }
 
+  const exchange = await ws.db.get(Stores.exchanges, coin.exchangeBaseUrl);
+  if (!exchange) {
+    // FIXME: report inconsistency?
+    return;
+  }
+  const exchangeDetails = exchange.details;
+  if (!exchangeDetails) {
+    // FIXME: report inconsistency?
+    return;
+  }
+
+  const isValid = ws.cryptoApi.isValidRecoupConfirmation(
+    coin.coinPub,
+    recoupConfirmation,
+    exchangeDetails.signingKeys,
+  );
+
+  if (!isValid) {
+    throw Error("invalid recoup confirmation signature");
+  }
+
   const refreshGroupId = await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.reserves],
     async tx => {
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 36b45f5a..f28426ac 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -30,6 +30,7 @@ import {
   MerchantRefundPermission,
   PayReq,
   TipResponse,
+  ExchangeSignKeyJson,
 } from "./talerTypes";
 
 import { Index, Store } from "../util/query";
@@ -410,6 +411,7 @@ export interface ExchangeDetails {
    * Master public key of the exchange.
    */
   masterPublicKey: string;
+
   /**
    * Auditors (partially) auditing the exchange.
    */
@@ -425,6 +427,12 @@ export interface ExchangeDetails {
    */
   protocolVersion: string;
 
+  /**
+   * Signing keys we got from the exchange, can also contain
+   * older signing keys that are not returned by /keys anymore.
+   */
+  signingKeys: ExchangeSignKeyJson[];
+
   /**
    * Timestamp for last update.
    */
diff --git a/src/types/talerTypes.ts b/src/types/talerTypes.ts
index 2ecb8234..569b9312 100644
--- a/src/types/talerTypes.ts
+++ b/src/types/talerTypes.ts
@@ -598,6 +598,17 @@ export class Recoup {
   h_denom_pub: string;
 }
 
+/**
+ * Structure of one exchange signing key in the /keys response.
+ */
+export class ExchangeSignKeyJson {
+  stamp_start: Timestamp;
+  stamp_expire: Timestamp;
+  stamp_end: Timestamp;
+  key: EddsaPublicKeyString;
+  master_sig: EddsaSignatureString;
+}
+
 /**
  * Structure that the exchange gives us in /keys.
  */
@@ -631,7 +642,7 @@ export class ExchangeKeysJson {
    * Short-lived signing keys used to sign online
    * responses.
    */
-  signkeys: any;
+  signkeys: ExchangeSignKeyJson[];
 
   /**
    * Protocol version.
@@ -881,6 +892,17 @@ export const codecForRecoup = () =>
       .build("Payback"),
   );
 
+export const codecForExchangeSigningKey = () =>
+  typecheckedCodec<ExchangeSignKeyJson>(
+    makeCodecForObject<ExchangeSignKeyJson>()
+      .property("key", codecForString)
+      .property("master_sig", codecForString)
+      .property("stamp_end", codecForTimestamp)
+      .property("stamp_start", codecForTimestamp)
+      .property("stamp_expire", codecForTimestamp)
+      .build("ExchangeSignKeyJson"),
+  );
+
 export const codecForExchangeKeysJson = () =>
   typecheckedCodec<ExchangeKeysJson>(
     makeCodecForObject<ExchangeKeysJson>()
@@ -889,7 +911,7 @@ export const codecForExchangeKeysJson = () =>
       .property("auditors", makeCodecForList(codecForAuditor()))
       .property("list_issue_date", codecForTimestamp)
       .property("recoup", 
makeCodecOptional(makeCodecForList(codecForRecoup())))
-      .property("signkeys", codecForAny)
+      .property("signkeys", makeCodecForList(codecForExchangeSigningKey()))
       .property("version", codecForString)
       .build("KeysJson"),
   );
@@ -981,10 +1003,9 @@ export const codecForRecoupConfirmation = () =>
       .build("RecoupConfirmation"),
   );
 
-
 export const codecForWithdrawResponse = () =>
   typecheckedCodec<WithdrawResponse>(
     makeCodecForObject<WithdrawResponse>()
       .property("ev_sig", codecForString)
       .build("WithdrawResponse"),
-  );
\ No newline at end of file
+  );
diff --git a/src/util/time.ts b/src/util/time.ts
index 54d22bf8..88297f9a 100644
--- a/src/util/time.ts
+++ b/src/util/time.ts
@@ -132,6 +132,16 @@ export function timestampDifference(t1: Timestamp, t2: 
Timestamp): Duration {
   return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
 }
 
+export function timestampIsBetween(t: Timestamp, start: Timestamp, end: 
Timestamp) {
+  if (timestampCmp(t, start) < 0) {
+    return false;
+  }
+  if (timestampCmp(t, end) > 0) {
+    return false;
+  }
+  return true;
+}
+
 export const codecForTimestamp: Codec<Timestamp> = {
   decode(x: any, c?: Context): Timestamp {
     const t_ms = x.t_ms;

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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