From f8e62141f27c296b326fc810f18206d16d41b468 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 19 Jun 2017 21:04:49 +0200 Subject: split httpd_refresh.c into refresh_melt, refresh_link and refresh_reveal --- src/exchange/taler-exchange-httpd_refresh_reveal.c | 833 +++++++++++++++++++++ 1 file changed, 833 insertions(+) create mode 100644 src/exchange/taler-exchange-httpd_refresh_reveal.c (limited to 'src/exchange/taler-exchange-httpd_refresh_reveal.c') diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c new file mode 100644 index 000000000..05422a85a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c @@ -0,0 +1,833 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Inria & GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_refresh_reveal.c + * @brief Handle /refresh/reveal requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler-exchange-httpd_parsing.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refresh_reveal.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * How often should we retry a transaction before giving up + * (for transactions resulting in serialization/dead locks only). + */ +#define MAX_TRANSACTION_COMMIT_RETRIES 3 + +/** + * Code to begin a transaction, must be inline as we define a block + * that ends with #COMMIT_TRANSACTION() within which we perform a number + * of retries. Note that this code may call "return" internally, so + * it must be called within a function where any cleanup will be done + * by the caller. Furthermore, the function's return value must + * match that of a #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define START_TRANSACTION(session,connection) \ +{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\ + unsigned int transaction_retries = 0; \ + enum GNUNET_DB_QueryStatus transaction_commit_result; \ +transaction_start_label: /* we will use goto for retries */ \ + if (GNUNET_OK != \ + TEH_plugin->start (TEH_plugin->cls, \ + session)) \ + { \ + GNUNET_break (0); \ + return TEH_RESPONSE_reply_internal_db_error (connection, \ + TALER_EC_DB_START_FAILED); \ + } + +/** + * Code to conclude a transaction, dual to #START_TRANSACTION(). Note + * that this code may call "return" internally, so it must be called + * within a function where any cleanup will be done by the caller. + * Furthermore, the function's return value must match that of a + * #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define COMMIT_TRANSACTION(session,connection) \ + transaction_commit_result = \ + TEH_plugin->commit (TEH_plugin->cls, \ + session); \ + if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_HARD); \ + } \ + if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } \ +} /* end of scope opened by BEGIN_TRANSACTION */ + + +/** + * Code to include to retry a transaction, must only be used in between + * #START_TRANSACTION and #COMMIT_TRANSACTION. + * + * @param session session handle + * @param connection connection handle + */ +#define RETRY_TRANSACTION(session,connection) \ + do { \ + TEH_plugin->rollback (TEH_plugin->cls, \ + session); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } while (0) + + + + + +/** + * Send a response for "/refresh/reveal". + * + * @param connection the connection to send the response to + * @param num_newcoins number of new coins for which we reveal data + * @param sigs array of @a num_newcoins signatures revealed + * @return a MHD result code + */ +static int +reply_refresh_reveal_success (struct MHD_Connection *connection, + unsigned int num_newcoins, + const struct TALER_DenominationSignature *sigs) +{ + int newcoin_index; + json_t *root; + json_t *obj; + json_t *list; + int ret; + + list = json_array (); + for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++) + { + obj = json_object (); + json_object_set_new (obj, + "ev_sig", + GNUNET_JSON_from_rsa_signature (sigs[newcoin_index].rsa_signature)); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + root = json_object (); + json_object_set_new (root, + "ev_sigs", + list); + ret = TEH_RESPONSE_reply_json (connection, + root, + MHD_HTTP_OK); + json_decref (root); + return ret; +} + + +/** + * Send a response for a failed "/refresh/reveal", where the + * revealed value(s) do not match the original commitment. + * + * @param connection the connection to send the response to + * @param session info about session + * @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma + * @param denom_pubs array of @a num_newcoins denomination keys for the new coins + * @param gamma_tp transfer public key at offset @a gamma + * @return a MHD result code + */ +static int +reply_refresh_reveal_missmatch (struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_RefreshSession *session, + const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins, + const struct TALER_DenominationPublicKey *denom_pubs, + const struct TALER_TransferPublicKeyP *gamma_tp) +{ + json_t *info_new; + json_t *info_commit_k; + unsigned int i; + + info_new = json_array (); + info_commit_k = json_array (); + for (i=0;inum_newcoins;i++) + { + const struct TALER_EXCHANGEDB_RefreshCommitCoin *cc; + json_t *cc_json; + + GNUNET_assert (0 == + json_array_append_new (info_new, + GNUNET_JSON_from_rsa_public_key (denom_pubs[i].rsa_public_key))); + + cc = &commit_coins[i]; + cc_json = json_pack ("{s:o}", + "coin_ev", + GNUNET_JSON_from_data (cc->coin_ev, + cc->coin_ev_size)); + GNUNET_assert (0 == + json_array_append_new (info_commit_k, + cc_json)); + } + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:i}", + "error", "commitment violation", + "code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, + "coin_sig", GNUNET_JSON_from_data_auto (&session->melt.coin_sig), + "coin_pub", GNUNET_JSON_from_data_auto (&session->melt.coin.coin_pub), + "melt_amount_with_fee", TALER_JSON_from_amount (&session->melt.amount_with_fee), + "melt_fee", TALER_JSON_from_amount (&session->melt.melt_fee), + "newcoin_infos", info_new, + "commit_infos", info_commit_k, + "gamma_tp", GNUNET_JSON_from_data_auto (gamma_tp), + "gamma", (int) session->noreveal_index); +} + + + +/** + * Check if the given @a transfer_privs correspond to an honest + * commitment for the given session. + * Checks that the transfer private keys match their commitments. + * Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param transfer_priv private transfer key + * @param melt information about the melted coin + * @param num_newcoins number of newcoins being generated + * @param denom_pubs array of @a num_newcoins keys for the new coins + * @param hash_context hash context to update by hashing in the data + * from this offset + * @return #GNUNET_OK if the committment was honest, + * #GNUNET_NO if there was a problem and we generated an error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +check_commitment (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + const struct TALER_TransferPrivateKeyP *transfer_priv, + const struct TALER_EXCHANGEDB_RefreshMelt *melt, + unsigned int num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs, + struct GNUNET_HashContext *hash_context) +{ + struct TALER_TransferSecretP transfer_secret; + unsigned int j; + + TALER_link_reveal_transfer_secret (transfer_priv, + &melt->coin.coin_pub, + &transfer_secret); + + /* Check that the commitments for all new coins were correct */ + for (j = 0; j < num_newcoins; j++) + { + struct TALER_FreshCoinP fc; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode h_msg; + char *buf; + size_t buf_len; + + TALER_setup_fresh_coin (&transfer_secret, + j, + &fc); + GNUNET_CRYPTO_eddsa_key_get_public (&fc.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + &h_msg); + if (GNUNET_YES != + GNUNET_CRYPTO_rsa_blind (&h_msg, + &fc.blinding_key.bks, + denom_pubs[j].rsa_public_key, + &buf, + &buf_len)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Blind failed (bad denomination key!?)\n"); + return (MHD_YES == + TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_REVEAL_BLINDING_ERROR, + "Blinding error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + buf, + buf_len); + GNUNET_free (buf); + } + return GNUNET_OK; +} + + +/** + * Exchange a coin as part of a refresh operation. Obtains the + * envelope from the database and performs the signing operation. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param key_state key state to lookup denomination pubs + * @param denom_pub denomination key for the coin to create + * @param commit_coin the coin that was committed + * @param coin_off number of the coin + * @return NULL on error, otherwise signature over the coin + */ +static struct TALER_DenominationSignature +refresh_exchange_coin (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct TEH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin, + unsigned int coin_off) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_DenominationSignature ev_sig; + + dki = TEH_KS_denomination_key_lookup (key_state, + denom_pub, + TEH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + GNUNET_break (0); + 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 cached 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); + if (NULL == ev_sig.rsa_signature) + { + GNUNET_break (0); + return ev_sig; + } + if (GNUNET_SYSERR == + TEH_plugin->insert_refresh_out (TEH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); + ev_sig.rsa_signature = NULL; + } + + return ev_sig; +} + + +/** + * The client request was well-formed, now execute the DB transaction + * of a "/refresh/reveal" operation. We use the @a ev_sigs and + * @a commit_coins to clean up resources after this function returns + * as we might experience retries of the database transaction. + * + * @param connection the MHD connection to handle + * @param session database session + * @param session_hash hash identifying the refresh session + * @param refresh_session information about the refresh operation we are doing + * @param denom_pubs array of "num_newcoins" denomination keys for the new coins + * @param[out] ev_sigs where to store generated signatures for the new coins, + * array of length "num_newcoins", memory released by the + * caller + * @param[out] commit_coins array of length "num_newcoins" to be used for + * information about the new coins from the commitment. + * @return MHD result code + */ +static int +execute_refresh_reveal_transaction (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + const struct TALER_EXCHANGEDB_RefreshSession *refresh_session, + const struct TALER_DenominationPublicKey *denom_pubs, + struct TALER_DenominationSignature *ev_sigs, + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins) +{ + unsigned int j; + struct TEH_KS_StateHandle *key_state; + int ret; + + START_TRANSACTION (session, connection); + key_state = TEH_KS_acquire (); + for (j=0;jnum_newcoins;j++) + { + if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */ + ev_sigs[j] = refresh_exchange_coin (connection, + session, + session_hash, + key_state, + &denom_pubs[j], + &commit_coins[j], + j); + if (NULL == ev_sigs[j].rsa_signature) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR); + goto cleanup; + } + } + COMMIT_TRANSACTION (session, connection); + ret = reply_refresh_reveal_success (connection, + refresh_session->num_newcoins, + ev_sigs); + cleanup: + TEH_KS_release (key_state); + return ret; +} + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins, + * and if so, return the signed coins for corresponding to the set of + * coins that was not chosen. + * + * @param connection the MHD connection to handle + * @param session_hash hash identifying the refresh session + * @param transfer_privs array with the revealed transfer keys, + * length must be #TALER_CNC_KAPPA - 1 + * @return MHD result code + */ +static int +execute_refresh_reveal (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + struct TALER_TransferPrivateKeyP *transfer_privs) +{ + int res; + struct TALER_EXCHANGEDB_Session *session; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_DenominationPublicKey *denom_pubs; + struct TALER_DenominationSignature *ev_sigs; + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; + unsigned int i; + unsigned int j; + unsigned int off; + struct GNUNET_HashContext *hash_context; + struct GNUNET_HashCode sh_check; + int ret; + struct TALER_TransferPublicKeyP gamma_tp; + + if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) + { + GNUNET_break (0); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_DB_SETUP_FAILED); + } + + res = TEH_plugin->get_refresh_session (TEH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_NO == res) + return TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "session_hash"); + if ( (GNUNET_SYSERR == res) || + (refresh_session.noreveal_index >= TALER_CNC_KAPPA) ) + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR); + denom_pubs = GNUNET_new_array (refresh_session.num_newcoins, + struct TALER_DenominationPublicKey); + if (GNUNET_OK != + TEH_plugin->get_refresh_order (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + hash_context = GNUNET_CRYPTO_hash_context_start (); + /* first, iterate over transfer public keys for hash_context */ + off = 0; + for (i=0;iget_refresh_transfer_public_key (TEH_plugin->cls, + session, + session_hash, + &gamma_tp)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + &gamma_tp, + sizeof (struct TALER_TransferPublicKeyP)); + } + else + { + /* compute tp from private key */ + struct TALER_TransferPublicKeyP tp; + + GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[i - off].ecdhe_priv, + &tp.ecdhe_pub); + GNUNET_CRYPTO_hash_context_read (hash_context, + &tp, + sizeof (struct TALER_TransferPublicKeyP)); + } + } + + /* next, add all of the hashes from the denomination keys to the + hash_context */ + { + struct TALER_DenominationPublicKey denom_pubs[refresh_session.num_newcoins]; + + if (GNUNET_OK != + TEH_plugin->get_refresh_order (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + for (i=0;iget_refresh_commit_coins (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + commit_coins)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR); + } + /* add envelopes to hash_context */ + for (j=0;j