summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-07-10 23:09:46 +0200
committerChristian Grothoff <christian@grothoff.org>2020-07-10 23:09:46 +0200
commit7085cfef70889a11508cdac4cd887b9959f59218 (patch)
tree610af337a248c92981ab6bf62697ee3e7d5a91e8
parentddf95c491af05732220ac35c6fb1bea48e6f4050 (diff)
downloadexchange-7085cfef70889a11508cdac4cd887b9959f59218.tar.gz
exchange-7085cfef70889a11508cdac4cd887b9959f59218.tar.bz2
exchange-7085cfef70889a11508cdac4cd887b9959f59218.zip
test coin_priv re-use with deposit and refresh, update handling of the error code client-side
-rw-r--r--src/include/taler_testing_lib.h24
-rw-r--r--src/lib/exchange_api_melt.c83
-rw-r--r--src/testing/test_exchange_api.c43
-rw-r--r--src/testing/testing_api_cmd_withdraw.c115
4 files changed, 249 insertions, 16 deletions
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index c3278b55..332b429f 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -1296,6 +1296,30 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
/**
+ * Create a withdraw command, letting the caller specify
+ * the desired amount as string and also re-using an existing
+ * coin private key in the process (violating the specification,
+ * which will result in an error when spending the coin!).
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param amount how much we withdraw.
+ * @param coin_ref reference to (withdraw/reveal) command of a coin
+ * from which we should re-use the private key
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+ const char *label,
+ const char *reserve_reference,
+ const char *amount,
+ const char *coin_ref,
+ unsigned int expected_response_code);
+
+
+/**
* Create withdraw command, letting the caller specify the
* amount by a denomination key.
*
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index 2c1e85d7..989c6501 100644
--- a/src/lib/exchange_api_melt.c
+++ b/src/lib/exchange_api_melt.c
@@ -76,6 +76,11 @@ struct TALER_EXCHANGE_MeltHandle
struct MeltData *md;
/**
+ * Public key of the coin being melted.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
* @brief Public information about the coin's denomination key
*/
struct TALER_EXCHANGE_DenomPublicKey dki;
@@ -155,6 +160,48 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
/**
* Verify that the signatures on the "409 CONFLICT" response from the
+ * exchange demonstrating customer denomination key differences
+ * resulting from coin private key reuse are valid.
+ *
+ * @param mh melt handle
+ * @param json json reply with the signature(s) and transaction history
+ * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_melt_signature_denom_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json)
+
+{
+ json_t *history;
+ struct TALER_Amount total;
+ struct GNUNET_HashCode h_denom_pub;
+
+ memset (&h_denom_pub,
+ 0,
+ sizeof (h_denom_pub));
+ history = json_object_get (json,
+ "history");
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_coin_history (&mh->dki,
+ mh->dki.value.currency,
+ &mh->coin_pub,
+ history,
+ &h_denom_pub,
+ &total))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 != GNUNET_memcmp (&mh->dki.h_key,
+ &h_denom_pub))
+ return GNUNET_OK; /* indeed, proof with different denomination key provided */
+ /* invalid proof provided */
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Verify that the signatures on the "409 CONFLICT" response from the
* exchange demonstrating customer double-spending are valid.
*
* @param mh melt handle
@@ -162,8 +209,8 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
* @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
*/
static int
-verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
- const json_t *json)
+verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json)
{
json_t *history;
struct TALER_Amount original_value;
@@ -329,15 +376,38 @@ handle_melt_finished (void *cls,
hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- /* Double spending; check signatures on transaction history */
- if (GNUNET_OK !=
- verify_melt_signature_conflict (mh,
- j))
+ hr.ec = TALER_JSON_get_error_code (j);
+ switch (hr.ec)
{
+ case TALER_EC_MELT_INSUFFICIENT_FUNDS:
+ /* Double spending; check signatures on transaction history */
+ if (GNUNET_OK !=
+ verify_melt_signature_spend_conflict (mh,
+ j))
+ {
+ GNUNET_break_op (0);
+ hr.http_status = 0;
+ hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
+ hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ break;
+ case TALER_EC_COIN_CONFLICTING_DENOMINATION_KEY:
+ if (GNUNET_OK !=
+ verify_melt_signature_denom_conflict (mh,
+ j))
+ {
+ GNUNET_break_op (0);
+ hr.http_status = 0;
+ hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
+ hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ break;
+ default:
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
hr.hint = TALER_JSON_get_error_hint (j);
+ break;
}
break;
case MHD_HTTP_FORBIDDEN:
@@ -485,6 +555,7 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
/* and now we can at last begin the actual request handling */
mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
mh->exchange = exchange;
+ mh->coin_pub = melt.coin_pub;
mh->dki = *dki;
mh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
not copy the pointer */
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index 410c1a49..75e3d480 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -136,12 +136,12 @@ run (void *cls,
* Do another transfer to the same reserve
*/
TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-1.2",
- "EUR:1",
+ "EUR:2.01",
&bc.exchange_auth,
bc.user42_payto,
"create-reserve-1"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1.2",
- "EUR:1",
+ "EUR:2.01",
bc.user42_payto,
bc.exchange_payto,
"create-reserve-1.2"),
@@ -154,12 +154,28 @@ run (void *cls,
"EUR:5",
MHD_HTTP_OK),
/**
+ * Withdraw EUR:1 using the SAME private coin key as for the previous coin
+ * (in violation of the specification, to be detected on spending!).
+ */
+ TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
+ "create-reserve-1",
+ "EUR:1",
+ "withdraw-coin-1",
+ MHD_HTTP_OK),
+ /**
* Check the reserve is depleted.
*/
TALER_TESTING_cmd_status ("status-1",
"create-reserve-1",
"EUR:0",
MHD_HTTP_OK),
+ /*
+ * Try to overdraw.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@@ -178,13 +194,14 @@ run (void *cls,
TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay",
"deposit-simple",
MHD_HTTP_OK),
- /*
- * Try to overdraw.
- */
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
- "create-reserve-1",
- "EUR:5",
- MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_deposit ("deposit-reused-coin-key-failure",
+ "withdraw-coin-1x",
+ 0,
+ bc.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_CONFLICT),
/**
* Try to double spend using different wire details.
*/
@@ -225,6 +242,14 @@ run (void *cls,
};
struct TALER_TESTING_Command refresh[] = {
+ /**
+ * Try to melt the coin that shared the private key with another
+ * coin (should fail). */
+ TALER_TESTING_cmd_melt ("refresh-melt-reused-coin-key-failure",
+ "withdraw-coin-1x",
+ MHD_HTTP_CONFLICT,
+ NULL),
+
/* Fill reserve with EUR:5, 1ct is for fees. */
CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
"EUR:5.01"),
diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c
index 5db97cbf..5b2ad26e 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -60,6 +60,12 @@ struct WithdrawState
const char *reserve_reference;
/**
+ * Reference to a withdraw or reveal operation from which we should
+ * re-use the private coin key, or NULL for regular withdrawal.
+ */
+ const char *reuse_coin_key_ref;
+
+ /**
* String describing the denomination value we should withdraw.
* A corresponding denomination key must exist in the exchange's
* offerings. Can be NULL if @e pk is set instead.
@@ -275,6 +281,50 @@ reserve_withdraw_cb (void *cls,
/**
+ * Parser reference to a coin.
+ *
+ * @param coin_reference of format $LABEL['#' $INDEX]?
+ * @param[out] cref where we return a copy of $LABEL
+ * @param[out] idx where we set $INDEX
+ * @return #GNUNET_SYSERR if $INDEX is present but not numeric
+ */
+static int
+parse_coin_reference (const char *coin_reference,
+ char **cref,
+ unsigned int *idx)
+{
+ const char *index;
+
+ /* We allow command references of the form "$LABEL#$INDEX" or
+ just "$LABEL", which implies the index is 0. Figure out
+ which one it is. */
+ index = strchr (coin_reference, '#');
+ if (NULL == index)
+ {
+ *idx = 0;
+ *cref = GNUNET_strdup (coin_reference);
+ return GNUNET_OK;
+ }
+ *cref = GNUNET_strndup (coin_reference,
+ index - coin_reference);
+ if (1 != sscanf (index + 1,
+ "%u",
+ idx))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
+ index,
+ __FILE__,
+ __LINE__);
+ GNUNET_free (*cref);
+ *cref = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Run the command.
*/
static void
@@ -307,7 +357,32 @@ withdraw_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
- TALER_planchet_setup_random (&ws->ps);
+ if (NULL == ws->reuse_coin_key_ref)
+ {
+ TALER_planchet_setup_random (&ws->ps);
+ }
+ else
+ {
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_TESTING_Command *cref;
+ char *cstr;
+ unsigned int index;
+
+ GNUNET_assert (GNUNET_OK ==
+ parse_coin_reference (ws->reuse_coin_key_ref,
+ &cstr,
+ &index));
+ cref = TALER_TESTING_interpreter_lookup_command (is,
+ cstr);
+ GNUNET_assert (NULL != cref);
+ GNUNET_free (cstr);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_coin_priv (cref,
+ index,
+ &coin_priv));
+ TALER_planchet_setup_random (&ws->ps);
+ ws->ps.coin_priv = *coin_priv;
+ }
ws->is = is;
if (NULL == ws->pk)
{
@@ -527,6 +602,44 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
/**
+ * Create a withdraw command, letting the caller specify
+ * the desired amount as string and also re-using an existing
+ * coin private key in the process (violating the specification,
+ * which will result in an error when spending the coin!).
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param amount how much we withdraw.
+ * @param coin_ref reference to (withdraw/reveal) command of a coin
+ * from which we should re-use the private key
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+ const char *label,
+ const char *reserve_reference,
+ const char *amount,
+ const char *coin_ref,
+ unsigned int expected_response_code)
+{
+ struct TALER_TESTING_Command cmd;
+
+ cmd = TALER_TESTING_cmd_withdraw_amount (label,
+ reserve_reference,
+ amount,
+ expected_response_code);
+ {
+ struct WithdrawState *ws = cmd.cls;
+
+ ws->reuse_coin_key_ref = coin_ref;
+ }
+ return cmd;
+}
+
+
+/**
* Create withdraw command, letting the caller specify the
* amount by a denomination key.
*