diff options
author | Christian Grothoff <christian@grothoff.org> | 2016-05-05 14:42:50 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2016-05-05 14:42:50 +0200 |
commit | 70ba42e55ba5c68a54f5690728c9ece9d029fe6e (patch) | |
tree | 462793bf892e2a84e5bcc5e1571828c199942407 | |
parent | d7eb23ad965c0207e561261588cbf742f93df935 (diff) |
check double spending proofs
-rw-r--r-- | src/backend/taler-merchant-httpd_pay.c | 19 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 21 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 10 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 9 | ||||
-rw-r--r-- | src/lib/Makefile.am | 2 | ||||
-rw-r--r-- | src/lib/merchant_api_pay.c | 121 | ||||
-rw-r--r-- | src/lib/test_merchant_api.c | 25 |
7 files changed, 203 insertions, 4 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index f7830896..1c5b66bf 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -300,10 +300,19 @@ deposit_cb (void *cls, } else { - /* Forward error including 'proof' for the body */ + /* Forward error, adding the "coin_pub" for which the + error was being generated */ + json_t *eproof; + + eproof = json_copy ((json_t *) proof); + json_object_set (eproof, + "coin_pub", + GNUNET_JSON_from_data (&dc->coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))); resume_pay_with_response (pc, http_status, - TMH_RESPONSE_make_json (proof)); + TMH_RESPONSE_make_json (eproof)); + json_decref (eproof); } return; } @@ -633,8 +642,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh, GNUNET_break (0); return MHD_NO; /* hard error */ } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response for /pay.\n"); res = MHD_queue_response (connection, pc->response_code, pc->response); @@ -643,6 +650,10 @@ MH_handler_pay (struct TMH_RequestHandler *rh, MHD_destroy_response (pc->response); pc->response = NULL; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response (%u) for /pay (%s).\n", + (unsigned int) pc->response_code, + res ? "OK" : "FAILED"); return res; } diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index e106a82e..4902cba1 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -48,6 +48,26 @@ struct PostgresClosure /** + * Drop merchant tables + * + * @param cls closure our `struct Plugin` + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_tables (void *cls) +{ + struct PostgresClosure *pg = cls; + int ret; + + ret = GNUNET_POSTGRES_exec (pg->conn, + "DROP TABLE payments;"); + if (GNUNET_OK != ret) + return ret; + return GNUNET_OK; +} + + +/** * Initialize merchant tables * * @param cls closure our `struct Plugin` @@ -278,6 +298,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) pg->conn = GNUNET_POSTGRES_connect (cfg, "merchantdb-postgres"); plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin); plugin->cls = pg; + plugin->drop_tables = &postgres_drop_tables; plugin->initialize = &postgres_initialize; plugin->store_payment = &postgres_store_payment; plugin->check_payment = &postgres_check_payment; diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 3d0ee8ee..52070860 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -133,6 +133,11 @@ struct TALER_MERCHANT_PayCoin struct TALER_DenominationSignature denom_sig; /** + * Overall value that coins of this @e denom_pub have. + */ + struct TALER_Amount denom_value; + + /** * Coin's private key. */ struct TALER_CoinSpendPrivateKeyP coin_priv; @@ -207,6 +212,11 @@ struct TALER_MERCHANT_PaidCoin struct TALER_DenominationSignature denom_sig; /** + * Overall value that coins of this @e denom_pub have. + */ + struct TALER_Amount denom_value; + + /** * Coin's public key. */ struct TALER_CoinSpendPublicKeyP coin_pub; diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 266f1cf6..1239b13c 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -48,6 +48,15 @@ struct TALER_MERCHANTDB_Plugin char *library_name; /** + * Drop merchant tables. Used for testcases. + * + * @param cls closure + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*drop_tables) (void *cls); + + /** * Initialize merchant tables * * @param cls closure diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index ab79c308..467c6a70 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -18,6 +18,7 @@ libtalermerchant_la_SOURCES = \ merchant_api_pay.c libtalermerchant_la_LIBADD = \ + -ltalerexchange \ -ltalerjson \ -ltalerutil \ -lgnunetcurl \ @@ -43,6 +44,7 @@ TESTS = \ test_merchant_api_SOURCES = \ test_merchant_api.c test_merchant_api_LDADD = \ + $(top_srcdir)/src/backenddb/libtalermerchantdb.la \ libtalermerchant.la \ $(LIBGCRYPT_LIBS) \ -ltalerexchange \ diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c index 8e016da8..713607ee 100644 --- a/src/lib/merchant_api_pay.c +++ b/src/lib/merchant_api_pay.c @@ -28,6 +28,7 @@ #include "taler_merchant_service.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +#include <taler/taler_exchange_service.h> /** @@ -65,9 +66,113 @@ struct TALER_MERCHANT_Pay * Reference to the execution context. */ struct GNUNET_CURL_Context *ctx; + + /** + * Number of @e coins we are paying with. + */ + unsigned int num_coins; + + /** + * The coins we are paying with. + */ + struct TALER_MERCHANT_PaidCoin *coins; + }; +/** + * We got a 403 response back from the exchange (or the merchant). + * Now we need to check the provided cryptographic proof that the + * coin was actually already spent! + * + * @param pc handle of the original coin we paid with + * @param json cryptographic proof of coin's transaction history as + * was returned by the exchange/merchant + * @return #GNUNET_OK if proof checks out + */ +static int +check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc, + json_t *json) +{ + struct TALER_Amount spent; + struct TALER_Amount spent_plus_contrib; + + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (pc->amount_with_fee.currency, + &pc->coin_pub, + json, + &spent)) + { + /* Exchange's history fails to verify */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&spent_plus_contrib, + &spent, + &pc->amount_with_fee)) + { + /* We got an integer overflow? Bad application! */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (-1 != TALER_amount_cmp (&pc->denom_value, + &spent_plus_contrib)) + { + /* according to our calculations, the transaction should + have still worked, exchange error! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Accepting proof of double-spending\n"); + return GNUNET_OK; +} + + +/** + * We got a 403 response back from the exchange (or the merchant). + * Now we need to check the provided cryptographic proof that the + * coin was actually already spent! + * + * @param ph handle of the original pay operation + * @param json cryptographic proof returned by the exchange/merchant + * @return #GNUNET_OK if proof checks out + */ +static int +check_forbidden (struct TALER_MERCHANT_Pay *ph, + const json_t *json) +{ + json_t *history; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), + GNUNET_JSON_spec_end() + }; + unsigned int i; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (i=0;i<ph->num_coins;i++) + { + if (0 == memcmp (&ph->coins[i].coin_pub, + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))) + return check_coin_history (&ph->coins[i], + history); + } + GNUNET_break_op (0); /* complaint is not about any of the coins + that we actually paid with... */ + return GNUNET_SYSERR; +} + /** * Function called when we're done processing the @@ -96,6 +201,12 @@ handle_pay_finished (void *cls, (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: + if (GNUNET_OK != check_forbidden (ph, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } break; case MHD_HTTP_UNAUTHORIZED: /* Nothing really to verify, merchant says one of the signatures is @@ -119,6 +230,9 @@ handle_pay_finished (void *cls, response_code = 0; break; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "/pay completed with response code %u\n", + (unsigned int) response_code); ph->cb (ph->cb_cls, response_code, json); @@ -209,6 +323,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, &p->coin_sig.eddsa_signature); p->denom_pub = coin->denom_pub; p->denom_sig = coin->denom_sig; + p->denom_value = coin->denom_value; p->coin_pub = dr.coin_pub; p->amount_with_fee = coin->amount_with_fee; p->amount_without_fee = coin->amount_without_fee; @@ -451,6 +566,12 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, ph->cb = pay_cb; ph->cb_cls = pay_cb_cls; ph->url = GNUNET_strdup (merchant_uri); + ph->num_coins = num_coins; + ph->coins = GNUNET_new_array (num_coins, + struct TALER_MERCHANT_PaidCoin); + memcpy (ph->coins, + coins, + num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); eh = curl_easy_init (); GNUNET_assert (NULL != (ph->json_enc = diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c index 70714bae..b7081212 100644 --- a/src/lib/test_merchant_api.c +++ b/src/lib/test_merchant_api.c @@ -24,6 +24,7 @@ #include <taler/taler_exchange_service.h> #include <taler/taler_json_lib.h> #include "taler_merchant_service.h" +#include "taler_merchantdb_lib.h" #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include <microhttpd.h> @@ -1137,6 +1138,7 @@ interpreter_run (void *cls) pc.coin_priv = ref->details.reserve_withdraw.coin_priv; pc.denom_pub = ref->details.reserve_withdraw.pk->key; pc.denom_sig = ref->details.reserve_withdraw.sig; + pc.denom_value = ref->details.reserve_withdraw.pk->value; break; default: GNUNET_assert (0); @@ -1484,12 +1486,35 @@ main (int argc, struct GNUNET_OS_Process *proc; struct GNUNET_OS_Process *exchanged; struct GNUNET_OS_Process *merchantd; + struct TALER_MERCHANTDB_Plugin *db; + struct GNUNET_CONFIGURATION_Handle *cfg; unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_CONFIG_HOME"); GNUNET_log_setup ("test-merchant-api", "WARNING", NULL); + cfg = GNUNET_CONFIGURATION_create (); + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_load (cfg, + "test_merchant_api.conf")); + db = TALER_MERCHANTDB_plugin_load (cfg); + if (NULL == db) + { + GNUNET_CONFIGURATION_destroy (cfg); + return 77; + } + (void) db->drop_tables (db->cls); + if (GNUNET_OK != db->initialize (db->cls)) + { + TALER_MERCHANTDB_plugin_unload (db); + GNUNET_CONFIGURATION_destroy (cfg); + return 77; + } + TALER_MERCHANTDB_plugin_unload (db); + GNUNET_CONFIGURATION_destroy (cfg); + + GNUNET_assert (GNUNET_OK == GNUNET_STRINGS_string_to_data (merchant_pub_str, strlen (merchant_pub_str), |