gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: signature verification for recoup


From: gnunet
Subject: [taler-wallet-core] 02/02: signature verification for recoup
Date: Fri, 13 Mar 2020 14:34:25 +0100

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

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

commit 1744b1a80063397105081a4d5aeec76936781345
Author: Florian Dold <address@hidden>
AuthorDate: Fri Mar 13 19:04:16 2020 +0530

    signature verification for recoup
---
 src/crypto/workers/cryptoApi.ts            | 43 +++++++++++++++--
 src/crypto/workers/cryptoImplementation.ts | 74 ++++++++++++++++++++++++++++--
 src/operations/exchanges.ts                |  2 +
 src/operations/recoup.ts                   | 42 ++++++++++++++++-
 src/types/dbTypes.ts                       |  8 ++++
 src/types/talerTypes.ts                    | 29 ++++++++++--
 src/util/time.ts                           | 10 ++++
 7 files changed, 193 insertions(+), 15 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 29753ce2..163f7759 100644
--- a/src/operations/recoup.ts
+++ b/src/operations/recoup.ts
@@ -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]