add idempotency checks for /refresh/reveal, fixing #4793

This commit is contained in:
Christian Grothoff 2016-11-17 16:37:40 +01:00
parent e140ca9dce
commit 7fd6be5cef
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
6 changed files with 125 additions and 12 deletions

View File

@ -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 Thu Nov 17 11:37:56 CET 2016
Fixed a few cases of missing database rollbacks, causing the Fixed a few cases of missing database rollbacks, causing the
exchange to be stuck. -CG exchange to be stuck. -CG

View File

@ -1710,6 +1710,9 @@ interpreter_run (void *cls)
fail (is); fail (is);
return; return;
} }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running command `%s'\n",
cmd->label);
switch (cmd->oc) switch (cmd->oc)
{ {
case OC_END: case OC_END:
@ -2836,6 +2839,12 @@ run (void *cls)
.expected_response_code = MHD_HTTP_OK, .expected_response_code = MHD_HTTP_OK,
.details.refresh_reveal.melt_ref = "refresh-melt-1" }, .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 */ /* Test that /refresh/link works */
{ .oc = OC_REFRESH_LINK, { .oc = OC_REFRESH_LINK,
.label = "refresh-link-1", .label = "refresh-link-1",
@ -2849,7 +2858,7 @@ run (void *cls)
.label = "refresh-deposit-refreshed-1a", .label = "refresh-deposit-refreshed-1a",
.expected_response_code = MHD_HTTP_OK, .expected_response_code = MHD_HTTP_OK,
.details.deposit.amount = "EUR:1", .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.coin_idx = 0,
.details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }", .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }",
.details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }",
@ -3072,12 +3081,6 @@ main (int argc,
enum GNUNET_OS_ProcessStatusType type; enum GNUNET_OS_ProcessStatusType type;
unsigned long code; 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... */ /* These might get in the way... */
unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_DATA_HOME");
unsetenv ("XDG_CONFIG_HOME"); unsetenv ("XDG_CONFIG_HOME");

View File

@ -1280,16 +1280,28 @@ refresh_exchange_coin (struct MHD_Connection *connection,
ev_sig.rsa_signature = NULL; ev_sig.rsa_signature = NULL;
return ev_sig; 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 ev_sig.rsa_signature
= GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key, = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key,
commit_coin->coin_ev, commit_coin->coin_ev,
commit_coin->coin_ev_size); commit_coin->coin_ev_size);
if (NULL == ev_sig.rsa_signature) if (NULL == ev_sig.rsa_signature)
{ {
GNUNET_break (0); GNUNET_break (0);
return ev_sig; return ev_sig;
} }
if (GNUNET_OK != if (GNUNET_SYSERR ==
TEH_plugin->insert_refresh_out (TEH_plugin->cls, TEH_plugin->insert_refresh_out (TEH_plugin->cls,
session, session,
session_hash, session_hash,
@ -1300,6 +1312,7 @@ refresh_exchange_coin (struct MHD_Connection *connection,
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
ev_sig.rsa_signature = NULL; ev_sig.rsa_signature = NULL;
} }
return ev_sig; return ev_sig;
} }

View File

@ -318,7 +318,7 @@ TEH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection,
{ {
return TEH_RESPONSE_reply_internal_error (connection, return TEH_RESPONSE_reply_internal_error (connection,
ec, ec,
"Failed to connect to database"); "Failure in database interaction");
} }

View File

@ -1154,6 +1154,15 @@ postgres_prepare (PGconn *db_conn)
"($1, $2, $3)", "($1, $2, $3)",
3, NULL); 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 /* Used in #postgres_get_link_data_list(). We use the session_hash
to obtain the "noreveal_index" for that session, and then select the to obtain the "noreveal_index" for that session, and then select the
corresponding signatures (ev_sig) and the denomination keys from 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 * Insert signature of a new coin generated during refresh into
* the database indexed by the refresh session and the index * 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 newcoin_index coin index
* @param ev_sig coin signature * @param ev_sig coin signature
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
* #GNUNET_SYSERR on error
*/ */
static int static int
postgres_insert_refresh_out (void *cls, 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->free_refresh_commit_coins = &postgres_free_refresh_commit_coins;
plugin->insert_refresh_transfer_public_key = &postgres_insert_refresh_transfer_public_key; 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_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->insert_refresh_out = &postgres_insert_refresh_out;
plugin->get_link_data_list = &postgres_get_link_data_list; plugin->get_link_data_list = &postgres_get_link_data_list;
plugin->free_link_data_list = &common_free_link_data_list; plugin->free_link_data_list = &common_free_link_data_list;

View File

@ -1359,6 +1359,27 @@ struct TALER_EXCHANGEDB_Plugin
struct TALER_TransferPublicKeyP *tp); 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 * Insert signature of a new coin generated during refresh into
* the database indexed by the refresh session and the index * the database indexed by the refresh session and the index
@ -1371,6 +1392,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param newcoin_index coin index * @param newcoin_index coin index
* @param ev_sig coin signature * @param ev_sig coin signature
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
* #GNUNET_SYSERR on error
*/ */
int int
(*insert_refresh_out) (void *cls, (*insert_refresh_out) (void *cls,