From 7fd6be5cef06d0bd495f4e03d33c4d6f04c36131 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 17 Nov 2016 16:37:40 +0100 Subject: [PATCH] add idempotency checks for /refresh/reveal, fixing #4793 --- ChangeLog | 3 + src/exchange-lib/test_exchange_api.c | 17 +++-- src/exchange/taler-exchange-httpd_db.c | 21 ++++-- src/exchange/taler-exchange-httpd_responses.c | 2 +- src/exchangedb/plugin_exchangedb_postgres.c | 72 +++++++++++++++++++ src/include/taler_exchangedb_plugin.h | 22 ++++++ 6 files changed, 125 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index ec9eddc36..d34dc843f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +Thu Nov 17 16:37:22 CET 2016 + Added missing idempotency checks for /refresh/reveal. -CG + Thu Nov 17 11:37:56 CET 2016 Fixed a few cases of missing database rollbacks, causing the exchange to be stuck. -CG diff --git a/src/exchange-lib/test_exchange_api.c b/src/exchange-lib/test_exchange_api.c index e25fa66cd..8c5e17d15 100644 --- a/src/exchange-lib/test_exchange_api.c +++ b/src/exchange-lib/test_exchange_api.c @@ -1710,6 +1710,9 @@ interpreter_run (void *cls) fail (is); return; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Running command `%s'\n", + cmd->label); switch (cmd->oc) { case OC_END: @@ -2836,6 +2839,12 @@ run (void *cls) .expected_response_code = MHD_HTTP_OK, .details.refresh_reveal.melt_ref = "refresh-melt-1" }, + /* do it again to check idempotency */ + { .oc = OC_REFRESH_REVEAL, + .label = "refresh-reveal-1-idempotency", + .expected_response_code = MHD_HTTP_OK, + .details.refresh_reveal.melt_ref = "refresh-melt-1" }, + /* Test that /refresh/link works */ { .oc = OC_REFRESH_LINK, .label = "refresh-link-1", @@ -2849,7 +2858,7 @@ run (void *cls) .label = "refresh-deposit-refreshed-1a", .expected_response_code = MHD_HTTP_OK, .details.deposit.amount = "EUR:1", - .details.deposit.coin_ref = "refresh-reveal-1", + .details.deposit.coin_ref = "refresh-reveal-1-idempotency", .details.deposit.coin_idx = 0, .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }", .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", @@ -3072,12 +3081,6 @@ main (int argc, enum GNUNET_OS_ProcessStatusType type; unsigned long code; - GNUNET_log_setup ("test-exchange-api", - "DEBUG", - "/tmp/logs"); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "test log\n"); - return 0; - /* These might get in the way... */ unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_CONFIG_HOME"); diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 2c6e90656..238112771 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -1280,16 +1280,28 @@ refresh_exchange_coin (struct MHD_Connection *connection, ev_sig.rsa_signature = NULL; return ev_sig; } + if (GNUNET_OK == + TEH_plugin->get_refresh_out (TEH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning cashed reply for /refresh/reveal signature\n"); + return ev_sig; + } + ev_sig.rsa_signature - = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key, - commit_coin->coin_ev, - commit_coin->coin_ev_size); + = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key, + commit_coin->coin_ev, + commit_coin->coin_ev_size); if (NULL == ev_sig.rsa_signature) { GNUNET_break (0); return ev_sig; } - if (GNUNET_OK != + if (GNUNET_SYSERR == TEH_plugin->insert_refresh_out (TEH_plugin->cls, session, session_hash, @@ -1300,6 +1312,7 @@ refresh_exchange_coin (struct MHD_Connection *connection, GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); ev_sig.rsa_signature = NULL; } + return ev_sig; } diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index b31f22e10..2ecd3b4e5 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -318,7 +318,7 @@ TEH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection, { return TEH_RESPONSE_reply_internal_error (connection, ec, - "Failed to connect to database"); + "Failure in database interaction"); } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 7ae8b5753..11d08f82d 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -1154,6 +1154,15 @@ postgres_prepare (PGconn *db_conn) "($1, $2, $3)", 3, NULL); + /* Used in #postgres_get_refresh_out() to test if the + generated signature(s) already exists */ + PREPARE ("get_refresh_out", + "SELECT ev_sig" + " FROM refresh_out" + " WHERE session_hash=$1" + " AND newcoin_index=$2", + 2, NULL); + /* Used in #postgres_get_link_data_list(). We use the session_hash to obtain the "noreveal_index" for that session, and then select the corresponding signatures (ev_sig) and the denomination keys from @@ -3431,6 +3440,67 @@ postgres_get_refresh_transfer_public_key (void *cls, } +/** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success, #GNUNET_NO if we have no such result + * #GNUNET_SYSERR on error + */ +static int +postgres_get_refresh_out (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t newcoin_index, + struct TALER_DenominationSignature *ev_sig) +{ + PGresult *result; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (session_hash), + GNUNET_PQ_query_param_uint16 (&newcoin_index), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_rsa_signature ("ev_sig", + &ev_sig->rsa_signature), + GNUNET_PQ_result_spec_end + }; + + result = GNUNET_PQ_exec_prepared (session->conn, + "get_refresh_out", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (1 != PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + /** * Insert signature of a new coin generated during refresh into * the database indexed by the refresh session and the index @@ -3443,6 +3513,7 @@ postgres_get_refresh_transfer_public_key (void *cls, * @param newcoin_index coin index * @param ev_sig coin signature * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error */ static int postgres_insert_refresh_out (void *cls, @@ -5050,6 +5121,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->free_refresh_commit_coins = &postgres_free_refresh_commit_coins; plugin->insert_refresh_transfer_public_key = &postgres_insert_refresh_transfer_public_key; plugin->get_refresh_transfer_public_key = &postgres_get_refresh_transfer_public_key; + plugin->get_refresh_out = &postgres_get_refresh_out; plugin->insert_refresh_out = &postgres_insert_refresh_out; plugin->get_link_data_list = &postgres_get_link_data_list; plugin->free_link_data_list = &common_free_link_data_list; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 02d41f2bf..19fb1d042 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1359,6 +1359,27 @@ struct TALER_EXCHANGEDB_Plugin struct TALER_TransferPublicKeyP *tp); + /** + * Get signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param[out] ev_sig coin signature + * @return #GNUNET_OK on success, #GNUNET_NO if we have no such entry, + * #GNUNET_SYSERR on error + */ + int + (*get_refresh_out) (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t newcoin_index, + struct TALER_DenominationSignature *ev_sig); + + /** * Insert signature of a new coin generated during refresh into * the database indexed by the refresh session and the index @@ -1371,6 +1392,7 @@ struct TALER_EXCHANGEDB_Plugin * @param newcoin_index coin index * @param ev_sig coin signature * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error */ int (*insert_refresh_out) (void *cls,