From 2ca543cd073a55f241c39b0905588a20882f7c93 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 15 Jun 2015 14:18:37 +0200 Subject: fix #3818 and handle coins being melted into multiple sessions --- src/include/taler_mintdb_plugin.h | 31 ++++++-- src/mint/taler-mint-httpd_db.c | 145 ++++++++++++++++++++++++++-------- src/mint/taler-mint-httpd_responses.c | 89 +++++++++++---------- src/mint/taler-mint-httpd_responses.h | 34 ++++++-- src/mintdb/plugin_mintdb_postgres.c | 110 ++++++++++++++------------ 5 files changed, 273 insertions(+), 136 deletions(-) (limited to 'src') diff --git a/src/include/taler_mintdb_plugin.h b/src/include/taler_mintdb_plugin.h index 72156ecec..88fe2801e 100644 --- a/src/include/taler_mintdb_plugin.h +++ b/src/include/taler_mintdb_plugin.h @@ -576,6 +576,22 @@ struct TALER_MINTDB_MeltCommitment struct TALER_MINTDB_Session; +/** + * Function called with the session hashes and transfer secret + * information for a given coin. + * + * @param cls closure + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + * @param shared_secret_enc set to shared secret for the session + */ +typedef void +(*TALER_MINTDB_TransferDataCallback)(void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EncryptedLinkSecretP *shared_secret_enc); + + /** * @brief The plugin API, returned from the plugin's "init" function. * The argument given to "init" is simply a configuration handle. @@ -1092,13 +1108,13 @@ struct TALER_MINTDB_Plugin * * @param cls the @e cls of this struct with the plugin-specific state * @param sesssion database connection - * @param coin_pub public key to use to retrieve linkage data - * @return all known link data for the coin + * @param session_hash session to get linkage data for + * @return all known link data for the session */ struct TALER_MINTDB_LinkDataList * (*get_link_data_list) (void *cls, struct TALER_MINTDB_Session *sesssion, - const struct TALER_CoinSpendPublicKeyP *coin_pub); + const struct GNUNET_HashCode *session_hash); /** @@ -1122,8 +1138,8 @@ struct TALER_MINTDB_Plugin * @param cls the @e cls of this struct with the plugin-specific state * @param sesssion database connection * @param coin_pub public key of the coin - * @param[out] transfer_pub public transfer key - * @param[out] shared_secret_enc set to shared secret + * @param tdc function to call for each session the coin was melted into + * @param tdc_cls closure for @a tdc * @return #GNUNET_OK on success, * #GNUNET_NO on failure (not found) * #GNUNET_SYSERR on internal failure (database issue) @@ -1132,8 +1148,9 @@ struct TALER_MINTDB_Plugin (*get_transfer) (void *cls, struct TALER_MINTDB_Session *sesssion, const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_TransferPublicKeyP *transfer_pub, - struct TALER_EncryptedLinkSecretP *shared_secret_enc); + TALER_MINTDB_TransferDataCallback tdc, + void *tdc_cls); + /** diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 26122d1e1..8a46a1839 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -1231,6 +1231,92 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, } +/** + * Closure for #handle_transfer_data(). + */ +struct HTD_Context +{ + + /** + * Session link data we collect. + */ + struct TMH_RESPONSE_LinkSessionInfo *sessions; + + /** + * Database session. Nothing to do with @a sessions. + */ + struct TALER_MINTDB_Session *session; + + /** + * MHD connection, for queueing replies. + */ + struct MHD_Connection *connection; + + /** + * Number of sessions the coin was melted into. + */ + unsigned int num_sessions; + + /** + * How are we expected to proceed. #GNUNET_SYSERR if we + * failed to return an error (should return #MHD_NO). + * #GNUNET_NO if we succeeded in queueing an MHD error + * (should return #MHD_YES from #TMH_execute_refresh_link), + * #GNUNET_OK if we should call #TMH_RESPONSE_reply_refresh_link_success(). + */ + int status; +}; + + +/** + * Function called with the session hashes and transfer secret + * information for a given coin. Gets the linkage data and + * builds the reply for the client. + * + * + * @param cls closure, a `struct HTD_Context` + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + * @param shared_secret_enc set to shared secret for the session + */ +static void +handle_transfer_data (void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ + struct HTD_Context *ctx = cls; + struct TALER_MINTDB_LinkDataList *ldl; + struct TMH_RESPONSE_LinkSessionInfo *lsi; + + if (GNUNET_OK != ctx->status) + return; + ldl = TMH_plugin->get_link_data_list (TMH_plugin->cls, + ctx->session, + session_hash); + if (NULL == ldl) + { + GNUNET_break (0); + ctx->status = GNUNET_NO; + if (MHD_NO == + TMH_RESPONSE_reply_json_pack (ctx->connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "link data not found (link)")) + ctx->status = GNUNET_SYSERR; + return; + } + GNUNET_array_grow (ctx->sessions, + ctx->num_sessions, + ctx->num_sessions + 1); + lsi = &ctx->sessions[ctx->num_sessions - 1]; + lsi->transfer_pub = *transfer_pub; + lsi->shared_secret_enc = *shared_secret_enc; + lsi->ldl = ldl; +} + + /** * Execute a "/refresh/link". Returns the linkage information that * will allow the owner of a coin to follow the refresh trail to @@ -1244,52 +1330,47 @@ int TMH_DB_execute_refresh_link (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub) { + struct HTD_Context ctx; int res; - struct TALER_MINTDB_Session *session; - struct TALER_TransferPublicKeyP transfer_pub; - struct TALER_EncryptedLinkSecretP shared_secret_enc; - struct TALER_MINTDB_LinkDataList *ldl; + unsigned int i; - if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, - GNUNET_NO))) + if (NULL == (ctx.session = TMH_plugin->get_session (TMH_plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } + ctx.connection = connection; + ctx.num_sessions = 0; + ctx.sessions = NULL; + ctx.status = GNUNET_OK; res = TMH_plugin->get_transfer (TMH_plugin->cls, - session, + ctx.session, coin_pub, - &transfer_pub, - &shared_secret_enc); - if (GNUNET_SYSERR == res) + &handle_transfer_data, + &ctx); + if (GNUNET_SYSERR == ctx.status) { - GNUNET_break (0); - return TMH_RESPONSE_reply_internal_db_error (connection); + res = MHD_NO; + goto cleanup; } - if (GNUNET_NO == res) + if (GNUNET_NO == ctx.status) { + res = MHD_YES; + goto cleanup; + } + GNUNET_assert (GNUNET_OK == ctx.status); + if (0 == ctx.num_sessions) return TMH_RESPONSE_reply_arg_unknown (connection, "coin_pub"); - } - GNUNET_assert (GNUNET_OK == res); - - ldl = TMH_plugin->get_link_data_list (TMH_plugin->cls, - session, - coin_pub); - if (NULL == ldl) - { - return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_NOT_FOUND, - "{s:s}", - "error", - "link data not found (link)"); - } res = TMH_RESPONSE_reply_refresh_link_success (connection, - &transfer_pub, - &shared_secret_enc, - ldl); - TMH_plugin->free_link_data_list (TMH_plugin->cls, - ldl); + ctx.num_sessions, + ctx.sessions); + cleanup: + for (i=0;ifree_link_data_list (TMH_plugin->cls, + ctx.sessions[i].ldl); + GNUNET_free (ctx.sessions); return res; } diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index f8240df52..ccc144e29 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -908,58 +908,65 @@ TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, * Send a response for "/refresh/link". * * @param connection the connection to send the response to - * @param transfer_pub transfer public key - * @param shared_secret_enc encrypted shared secret - * @param ldl linked list with link data + * @param num_sessions number of sessions the coin was used in + * @param sessions array of @a num_session entries with + * information for each session * @return a MHD result code */ int TMH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, - const struct TALER_TransferPublicKeyP *transfer_pub, - const struct TALER_EncryptedLinkSecretP *shared_secret_enc, - const struct TALER_MINTDB_LinkDataList *ldl) + unsigned int num_sessions, + const struct TMH_RESPONSE_LinkSessionInfo *sessions) { - const struct TALER_MINTDB_LinkDataList *pos; json_t *root; - json_t *list; + json_t *mlist; int res; + unsigned int i; - list = json_array (); - for (pos = ldl; NULL != pos; pos = pos->next) + mlist = json_array (); + for (i=0;ilink_data_enc->coin_priv_enc, - sizeof (struct TALER_CoinSpendPrivateKeyP) + - ldl->link_data_enc->blinding_key_enc_size)); - json_object_set_new (obj, - "denom_pub", - TALER_json_from_rsa_public_key (ldl->denom_pub.rsa_public_key)); - json_object_set_new (obj, - "ev_sig", - TALER_json_from_rsa_signature (ldl->ev_sig.rsa_signature)); - json_array_append_new (list, obj); - } + const struct TALER_MINTDB_LinkDataList *pos; + json_t *list = json_array (); - root = json_object (); - json_object_set_new (root, - "new_coins", - list); - json_object_set_new (root, - "transfer_pub", - TALER_json_from_data (transfer_pub, - sizeof (struct TALER_TransferPublicKeyP))); - json_object_set_new (root, - "secret_enc", - TALER_json_from_data (shared_secret_enc, - sizeof (struct TALER_EncryptedLinkSecretP))); + for (pos = sessions[i].ldl; NULL != pos; pos = pos->next) + { + json_t *obj; + + obj = json_object (); + json_object_set_new (obj, + "link_enc", + TALER_json_from_data (pos->link_data_enc->coin_priv_enc, + sizeof (struct TALER_CoinSpendPrivateKeyP) + + pos->link_data_enc->blinding_key_enc_size)); + json_object_set_new (obj, + "denom_pub", + TALER_json_from_rsa_public_key (pos->denom_pub.rsa_public_key)); + json_object_set_new (obj, + "ev_sig", + TALER_json_from_rsa_signature (pos->ev_sig.rsa_signature)); + json_array_append_new (list, + obj); + } + root = json_object (); + json_object_set_new (root, + "new_coins", + list); + json_object_set_new (root, + "transfer_pub", + TALER_json_from_data (&sessions[i].transfer_pub, + sizeof (struct TALER_TransferPublicKeyP))); + json_object_set_new (root, + "secret_enc", + TALER_json_from_data (&sessions[i].shared_secret_enc, + sizeof (struct TALER_EncryptedLinkSecretP))); + json_array_append_new (mlist, + root); + } res = TMH_RESPONSE_reply_json (connection, - root, - MHD_HTTP_OK); - json_decref (root); + mlist, + MHD_HTTP_OK); + json_decref (mlist); return res; } diff --git a/src/mint/taler-mint-httpd_responses.h b/src/mint/taler-mint-httpd_responses.h index 8392e73d7..2ae06209d 100644 --- a/src/mint/taler-mint-httpd_responses.h +++ b/src/mint/taler-mint-httpd_responses.h @@ -334,20 +334,42 @@ TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, const char *missmatch_object); +/** + * Information for each session a coin was melted into. + */ +struct TMH_RESPONSE_LinkSessionInfo +{ + /** + * Transfer public key of the coin. + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Encrypted shared secret for decrypting the transfer secrets. + */ + struct TALER_EncryptedLinkSecretP shared_secret_enc; + + /** + * Linked data of coins being created in the session. + */ + struct TALER_MINTDB_LinkDataList *ldl; + +}; + + /** * Send a response for "/refresh/link". * * @param connection the connection to send the response to - * @param transfer_pub transfer public key - * @param shared_secret_enc encrypted shared secret - * @param ldl linked list with link data + * @param num_sessions number of sessions the coin was used in + * @param sessions array of @a num_session entries with + * information for each session * @return a MHD result code */ int TMH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, - const struct TALER_TransferPublicKeyP *transfer_pub, - const struct TALER_EncryptedLinkSecretP *shared_secret_enc, - const struct TALER_MINTDB_LinkDataList *ldl); + unsigned int num_sessions, + const struct TMH_RESPONSE_LinkSessionInfo *sessions); #endif diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c index c222d1c37..6f048c35d 100644 --- a/src/mintdb/plugin_mintdb_postgres.c +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -312,7 +312,7 @@ postgres_create_tables (void *cls, */ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melts " "(coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)" - ",session BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" + ",session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" ",oldcoin_index INT2 NOT NULL" ",coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)" ",amount_with_fee_val INT8 NOT NULL" @@ -321,7 +321,7 @@ postgres_create_tables (void *cls, ",melt_fee_val INT8 NOT NULL" ",melt_fee_frac INT8 NOT NULL" ",melt_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" - ",PRIMARY KEY (session, oldcoin_index)" /* a coin can be used only + ",PRIMARY KEY (session_hash, oldcoin_index)" /* a coin can be used only once in a refresh session */ ") "); /* Table with information about the desired denominations to be created @@ -675,7 +675,7 @@ postgres_prepare (PGconn *db_conn) PREPARE ("insert_refresh_melt", "INSERT INTO refresh_melts " "(coin_pub " - ",session" + ",session_hash" ",oldcoin_index " ",coin_sig " ",amount_with_fee_val " @@ -700,12 +700,12 @@ postgres_prepare (PGconn *db_conn) ",melt_fee_frac " ",melt_fee_curr " " FROM refresh_melts" - " WHERE session=$1 AND oldcoin_index=$2", + " WHERE session_hash=$1 AND oldcoin_index=$2", 2, NULL); /* Query the 'refresh_melts' by coin public key */ PREPARE ("get_refresh_melt_by_coin", "SELECT" - " session" + " session_hash" /* ",oldcoin_index" // not needed */ ",coin_sig" ",amount_with_fee_val" @@ -845,43 +845,49 @@ postgres_prepare (PGconn *db_conn) ") VALUES " "($1, $2, $3)", 3, NULL); -#if 0 - /* FIXME: not complete yet -- #3818... */ - /* Used in #postgres_get_link_data_list(). - FIXME: document how this is supposed to work... */ + /* Used in #postgres_get_link_data_list(). We use the session_hash + to obtain the "noreveal_index" for that session, and then select + the encrypted link vectors (link_vector_enc) and the + corresponding signatures (ev_sig) and the denomination keys from + the respective tables (namely refresh_melts and refresh_order) + using the session_hash as the primary filter (on join) and the + 'noreveal_index' to constrain the selection on the commitment. + We also want to get the triplet for each of the newcoins, so we + have another constraint to ensure we get each triplet with + matching "newcoin_index" values. NOTE: This may return many + results, both for different sessions and for the different coins + being minted in the refresh ops. NOTE: There may be more + efficient ways to express the same query. */ PREPARE ("get_link", - "SELECT link_vector_enc,ro.denom_pub,ev_sig" - " FROM refresh_melt rm " + "SELECT link_vector_enc,ev_sig,ro.denom_pub" + " FROM refresh_melts rm " " JOIN refresh_order ro USING (session_hash)" " JOIN refresh_commit_coin rcc USING (session_hash)" " JOIN refresh_sessions rs USING (session_hash)" " JOIN refresh_out rc USING (session_hash)" - " WHERE rm.coin_pub=$1" + " WHERE ro.session_hash=$1" " AND ro.newcoin_index=rcc.newcoin_index" " AND ro.newcoin_index=rc.newcoin_index" - " AND rcc.cnc_index=rs.noreveal_index % (" - " SELECT count(*) FROM refresh_commit_coin rcc2" - " WHERE rcc2.newcoin_index=0" - " AND rcc2.session_hash=rs.session_hash" - " ) ", + " AND rcc.cnc_index=rs.noreveal_index", 1, NULL); - /* Used in #postgres_get_transfer(). - FIXME: document how this is supposed to work... -- #3818 */ + /* Used in #postgres_get_transfer(). Given the public key of a + melted coin, we obtain the corresponding encrypted link secret + and the transfer public key. This is done by first finding + the session_hash(es) of all sessions the coin was melted into, + and then constraining the result to the selected "noreveal_index" + and the transfer public key to the corresponding index of the + old coin. + NOTE: This may (in theory) return multiple results, one per session + that the old coin was melted into. */ PREPARE ("get_transfer", - "SELECT transfer_pub,link_secret_enc" - " FROM refresh_melt rm" + "SELECT transfer_pub,link_secret_enc,session_hash" + " FROM refresh_melts rm" " JOIN refresh_commit_link rcl USING (session_hash)" " JOIN refresh_sessions rs USING (session_hash)" " WHERE rm.coin_pub=$1" " AND rm.oldcoin_index = rcl.oldcoin_index" - " AND rcl.cnc_index=rs.noreveal_index % (" - " SELECT count(*) FROM refresh_commit_coin rcc2" - " WHERE newcoin_index=0" - " AND rcc2.session_hash=rm.session_hash" - " )", + " AND rcl.cnc_index=rs.noreveal_index", 1, NULL); -#endif - return GNUNET_OK; #undef PREPARE } @@ -2848,20 +2854,20 @@ postgres_insert_refresh_out (void *cls, * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param session database connection - * @param coin_pub public key to use to retrieve linkage data - * @return all known link data for the coin + * @param session_hash refresh session to get linkage data for + * @return all known link data for the session */ static struct TALER_MINTDB_LinkDataList * postgres_get_link_data_list (void *cls, struct TALER_MINTDB_Session *session, - const struct TALER_CoinSpendPublicKeyP *coin_pub) + const struct GNUNET_HashCode *session_hash) { struct TALER_MINTDB_LinkDataList *ldl; struct TALER_MINTDB_LinkDataList *pos; int i; int nrows; struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_auto_from_type (coin_pub), + TALER_PQ_query_param_auto_from_type (session_hash), TALER_PQ_query_param_end }; PGresult *result; @@ -2895,10 +2901,10 @@ postgres_get_link_data_list (void *cls, TALER_PQ_result_spec_variable_size ("link_vector_enc", &ld_buf, &ld_buf_size), - TALER_PQ_result_spec_rsa_public_key ("denom_pub", - &denom_pub), TALER_PQ_result_spec_rsa_signature ("ev_sig", &sig), + TALER_PQ_result_spec_rsa_public_key ("denom_pub", + &denom_pub), TALER_PQ_result_spec_end }; @@ -2942,8 +2948,8 @@ postgres_get_link_data_list (void *cls, * @param cls the `struct PostgresClosure` with the plugin-specific state * @param session database connection * @param coin_pub public key of the coin - * @param[out] transfer_pub public transfer key - * @param[out] shared_secret_enc set to shared secret + * @param tdc function to call for each session the coin was melted into + * @param tdc_cls closure for @a tdc * @return #GNUNET_OK on success, * #GNUNET_NO on failure (not found) * #GNUNET_SYSERR on internal failure (database issue) @@ -2952,14 +2958,16 @@ static int postgres_get_transfer (void *cls, struct TALER_MINTDB_Session *session, const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_TransferPublicKeyP *transfer_pub, - struct TALER_EncryptedLinkSecretP *shared_secret_enc) + TALER_MINTDB_TransferDataCallback tdc, + void *tdc_cls) { struct TALER_PQ_QueryParam params[] = { TALER_PQ_query_param_auto_from_type (coin_pub), TALER_PQ_query_param_end }; PGresult *result; + int nrows; + int i; result = TALER_PQ_exec_prepared (session->conn, "get_transfer", @@ -2971,23 +2979,21 @@ postgres_get_transfer (void *cls, PQclear (result); return GNUNET_SYSERR; } - if (0 == PQntuples (result)) + nrows = PQntuples (result); + if (0 == nrows) { PQclear (result); return GNUNET_NO; } - if (1 != PQntuples (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "got %d tuples from get_transfer\n", - PQntuples (result)); - GNUNET_break (0); - return GNUNET_SYSERR; - } + for (i=0;isession_hash), /* oldcoin_index not needed */ TALER_PQ_result_spec_auto_from_type ("coin_sig", -- cgit v1.2.3