aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-05-05 14:42:50 +0200
committerChristian Grothoff <christian@grothoff.org>2016-05-05 14:42:50 +0200
commit70ba42e55ba5c68a54f5690728c9ece9d029fe6e (patch)
tree462793bf892e2a84e5bcc5e1571828c199942407
parentd7eb23ad965c0207e561261588cbf742f93df935 (diff)
check double spending proofs
-rw-r--r--src/backend/taler-merchant-httpd_pay.c19
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c21
-rw-r--r--src/include/taler_merchant_service.h10
-rw-r--r--src/include/taler_merchantdb_plugin.h9
-rw-r--r--src/lib/Makefile.am2
-rw-r--r--src/lib/merchant_api_pay.c121
-rw-r--r--src/lib/test_merchant_api.c25
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),