diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_recoup.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_recoup.c | 479 |
1 files changed, 177 insertions, 302 deletions
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c index b5074ce35..afbbd7474 100644 --- a/src/exchange/taler-exchange-httpd_recoup.c +++ b/src/exchange/taler-exchange-httpd_recoup.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2017-2020 Taler Systems SA + Copyright (C) 2017-2022 Taler Systems SA 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 @@ -28,6 +28,7 @@ #include <pthread.h> #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_db.h" #include "taler-exchange-httpd_recoup.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keys.h" @@ -39,14 +40,15 @@ struct RecoupContext { /** - * Hash of the blinded coin. + * Hash identifying the withdraw request. */ - struct TALER_BlindedCoinHash h_blind; + struct TALER_BlindedCoinHashP h_coin_ev; /** - * Full value of the coin. + * Set by #recoup_transaction() to the reserve that will + * receive the recoup, if #refreshed is #GNUNET_NO. */ - struct TALER_Amount value; + struct TALER_ReservePublicKeyP reserve_pub; /** * Details about the coin. @@ -56,7 +58,7 @@ struct RecoupContext /** * Key used to blind the coin. */ - const union TALER_DenominationBlindingKeyP *coin_bks; + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks; /** * Signature of the coin requesting recoup. @@ -64,39 +66,20 @@ struct RecoupContext const struct TALER_CoinSpendSignatureP *coin_sig; /** - * Where does the value of the recouped coin go? Which member - * of the union is valid depends on @e refreshed. + * Unique ID of the withdraw operation in the reserves_out table. */ - union - { - /** - * Set by #recoup_transaction() to the reserve that will - * receive the recoup, if #refreshed is #GNUNET_NO. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Set by #recoup_transaction() to the old coin that will - * receive the recoup, if #refreshed is #GNUNET_YES. - */ - struct TALER_CoinSpendPublicKeyP old_coin_pub; - } target; + uint64_t reserve_out_serial_id; /** - * Set by #recoup_transaction() to the amount that will be paid back + * Unique ID of the coin in the known_coins table. */ - struct TALER_Amount amount; + uint64_t known_coin_id; /** * Set by #recoup_transaction to the timestamp when the recoup * was accepted. */ - struct GNUNET_TIME_Absolute now; - - /** - * #GNUNET_YES if the client claims the coin originated from a refresh. - */ - int refreshed; + struct GNUNET_TIME_Timestamp now; }; @@ -124,206 +107,53 @@ recoup_transaction (void *cls, MHD_RESULT *mhd_ret) { struct RecoupContext *pc = cls; - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount spent; - struct TALER_Amount recouped; enum GNUNET_DB_QueryStatus qs; - int existing_recoup_found; - - /* make sure coin is 'known' in database */ - qs = TEH_make_coin_known (pc->coin, - connection, - mhd_ret); - if (qs < 0) - return qs; - - /* Check whether a recoup is allowed, and if so, to which - reserve / account the money should go */ - if (pc->refreshed) - { - qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls, - &pc->h_blind, - &pc->target.old_coin_pub); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "old coin by h_blind"); - } - return qs; - } - } - else - { - qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, - &pc->h_blind, - &pc->target.reserve_pub); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserve by h_blind"); - } - return qs; - } - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Recoup requested for unknown envelope %s\n", - GNUNET_h2s (&pc->h_blind.hash)); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Calculate remaining balance, including recoups already applied. */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - &pc->coin->coin_pub, - GNUNET_YES, - &tl); + bool recoup_ok; + bool internal_failure; + + /* Finally, store new refund data */ + pc->now = GNUNET_TIME_timestamp_get (); + qs = TEH_plugin->do_recoup (TEH_plugin->cls, + &pc->reserve_pub, + pc->reserve_out_serial_id, + pc->coin_bks, + &pc->coin->coin_pub, + pc->known_coin_id, + pc->coin_sig, + &pc->now, + &recoup_ok, + &internal_failure); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "coin transaction list"); - } + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do_recoup"); return qs; } - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pc->value.currency, - &spent)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pc->value.currency, - &recouped)); - /* Check if this coin has been recouped already at least once */ - existing_recoup_found = GNUNET_NO; - for (struct TALER_EXCHANGEDB_TransactionList *pos = tl; - NULL != pos; - pos = pos->next) - { - if ( (TALER_EXCHANGEDB_TT_RECOUP == pos->type) || - (TALER_EXCHANGEDB_TT_RECOUP_REFRESH == pos->type) ) - { - existing_recoup_found = GNUNET_YES; - break; - } - } - if (GNUNET_OK != - TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, - &spent, - &spent)) + if (internal_failure) { GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - "coin transaction history"); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + "do_recoup"); return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Recoup: calculated spent %s\n", - TALER_amount2s (&spent)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Recoup: coin value %s\n", - TALER_amount2s (&pc->value)); - if (0 > - TALER_amount_subtract (&pc->amount, - &pc->value, - &spent)) + if (! recoup_ok) { - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_NEGATIVE, - NULL); + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, + &pc->coin->denom_pub_hash, + &pc->coin->coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (0 == pc->amount.fraction) && - (0 == pc->amount.value) ) - { - /* Recoup has no effect: coin fully spent! */ - enum GNUNET_DB_QueryStatus ret; - - TEH_plugin->rollback (TEH_plugin->cls); - if (GNUNET_NO == existing_recoup_found) - { - /* Refuse: insufficient funds for recoup */ - *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection, - TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO, - &pc->coin->coin_pub, - tl); - ret = GNUNET_DB_STATUS_HARD_ERROR; - } - else - { - /* We didn't add any new recoup transaction, but there was at least - one recoup before, so we give a success response (idempotency!) */ - ret = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return ret; - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - pc->now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&pc->now); - - /* add coin to list of wire transfers for recoup */ - if (pc->refreshed) - { - qs = TEH_plugin->insert_recoup_refresh_request (TEH_plugin->cls, - pc->coin, - pc->coin_sig, - pc->coin_bks, - &pc->amount, - &pc->h_blind, - pc->now); - } - else - { - qs = TEH_plugin->insert_recoup_request (TEH_plugin->cls, - &pc->target.reserve_pub, - pc->coin, - pc->coin_sig, - pc->coin_bks, - &pc->amount, - &pc->h_blind, - pc->now); - } - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - TALER_LOG_WARNING ("Failed to store recoup information in database\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "recoup request"); - } - return qs; - } - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + return qs; } @@ -336,22 +166,24 @@ recoup_transaction (void *cls, * * @param connection the MHD connection to handle * @param coin information about the coin + * @param exchange_vals values contributed by the exchange + * during withdrawal * @param coin_bks blinding data of the coin (to be checked) + * @param nonce coin's nonce if CS is used * @param coin_sig signature of the coin - * @param refreshed #GNUNET_YES if the coin was refreshed * @return MHD result code */ static MHD_RESULT verify_and_execute_recoup ( struct MHD_Connection *connection, const struct TALER_CoinPublicInfo *coin, - const union TALER_DenominationBlindingKeyP *coin_bks, - const struct TALER_CoinSpendSignatureP *coin_sig, - int refreshed) + const struct TALER_ExchangeWithdrawValues *exchange_vals, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const union GNUNET_CRYPTO_BlindSessionNonce *nonce, + const struct TALER_CoinSpendSignatureP *coin_sig) { struct RecoupContext pc; const struct TEH_DenominationKey *dk; - struct TALER_CoinPubHash c_hash; MHD_RESULT mret; /* check denomination exists and is in recoup mode */ @@ -360,144 +192,171 @@ verify_and_execute_recoup ( &mret); if (NULL == dk) return mret; - if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit)) + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); /* This denomination is past the expiration time for recoup */ return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "RECOUP"); } - if (GNUNET_TIME_absolute_is_future (dk->meta.start)) + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); /* This denomination is not yet valid */ return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, "RECOUP"); } if (! dk->recoup_possible) { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); /* This denomination is not eligible for recoup */ return TEH_RESPONSE_reply_expired_denom_pub_hash ( connection, &coin->denom_pub_hash, - now, TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE, "RECOUP"); } - pc.value = dk->meta.value; - /* check denomination signature */ + switch (dk->denom_pub.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; + break; + case GNUNET_CRYPTO_BSA_CS: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; + break; + default: + break; + } if (GNUNET_YES != TALER_test_coin_valid (coin, &dk->denom_pub)) { - TALER_LOG_WARNING ("Invalid coin passed for recoup\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, - NULL); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, + NULL); } /* check recoup request signature */ + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_wallet_recoup_verify (&coin->denom_pub_hash, + coin_bks, + &coin->coin_pub, + coin_sig)) { - struct TALER_RecoupRequestPS pr = { - .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), - .purpose.size = htonl (sizeof (pr)), - .coin_pub = coin->coin_pub, - .h_denom_pub = coin->denom_pub_hash, - .coin_blind = *coin_bks - }; - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, - &pr, - &coin_sig->eddsa_signature, - &coin->coin_pub.eddsa_pub)) - { - TALER_LOG_WARNING ("Invalid signature on recoup request\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID, - NULL); - } + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID, + NULL); } + /* re-compute client-side blinding so we can + (a bit later) check that this coin was indeed + signed by us. */ { - void *coin_ev; - size_t coin_ev_size; + struct TALER_CoinPubHashP c_hash; + struct TALER_BlindedPlanchet blinded_planchet; if (GNUNET_OK != TALER_denom_blind (&dk->denom_pub, coin_bks, - NULL, /* FIXME-Oec: TALER_AgeHash * */ + nonce, + &coin->h_age_commitment, &coin->coin_pub, + exchange_vals, &c_hash, - &coin_ev, - &coin_ev_size)) + &blinded_planchet)) { GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED, + NULL); } - TALER_coin_ev_hash (coin_ev, - coin_ev_size, - &pc.h_blind); - GNUNET_free (coin_ev); + TALER_coin_ev_hash (&blinded_planchet, + &coin->denom_pub_hash, + &pc.h_coin_ev); + TALER_blinded_planchet_free (&blinded_planchet); } - /* Perform actual recoup transaction */ pc.coin_sig = coin_sig; pc.coin_bks = coin_bks; pc.coin = coin; - pc.refreshed = refreshed; + + { + MHD_RESULT mhd_ret = MHD_NO; + enum GNUNET_DB_QueryStatus qs; + + /* make sure coin is 'known' in database */ + qs = TEH_make_coin_known (coin, + connection, + &pc.known_coin_id, + &mhd_ret); + /* no transaction => no serialization failures should be possible */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + if (qs < 0) + return mhd_ret; + } + + { + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, + &pc.h_coin_ev, + &pc.reserve_pub, + &pc.reserve_out_serial_id); + if (0 > qs) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_by_h_blind"); + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Recoup requested for unknown envelope %s\n", + GNUNET_h2s (&pc.h_coin_ev.hash)); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND, + NULL); + } + } + + /* Perform actual recoup transaction */ { MHD_RESULT mhd_ret; if (GNUNET_OK != TEH_DB_run_transaction (connection, "run recoup", + TEH_MT_REQUEST_OTHER, &mhd_ret, &recoup_transaction, &pc)) return mhd_ret; } /* Recoup succeeded, return result */ - return (refreshed) - ? TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ( - "old_coin_pub", - &pc.target.old_coin_pub), - GNUNET_JSON_pack_bool ("refreshed", - true)) - : TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ( - "reserve_pub", - &pc.target.reserve_pub), - GNUNET_JSON_pack_bool ("refreshed", - false)); + return TALER_MHD_REPLY_JSON_PACK (connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ( + "reserve_pub", + &pc.reserve_pub)); } @@ -519,24 +378,37 @@ TEH_handler_recoup (struct MHD_Connection *connection, { enum GNUNET_GenericReturnValue ret; struct TALER_CoinPublicInfo coin; - union TALER_DenominationBlindingKeyP coin_bks; + union GNUNET_CRYPTO_BlindingSecretP coin_bks; struct TALER_CoinSpendSignatureP coin_sig; - int refreshed = GNUNET_NO; + struct TALER_ExchangeWithdrawValues exchange_vals; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + bool no_nonce; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", &coin.denom_pub_hash), TALER_JSON_spec_denom_sig ("denom_sig", &coin.denom_sig), + TALER_JSON_spec_exchange_withdraw_values ("ewv", + &exchange_vals), GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret", &coin_bks), GNUNET_JSON_spec_fixed_auto ("coin_sig", &coin_sig), - GNUNET_JSON_spec_mark_optional - (GNUNET_JSON_spec_boolean ("refreshed", - &refreshed)), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &coin.h_age_commitment), + &coin.no_age_commitment), + // FIXME: should be renamed to just 'nonce'! + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("cs_nonce", + &nonce), + &no_nonce), GNUNET_JSON_spec_end () }; + memset (&coin, + 0, + sizeof (coin)); coin.coin_pub = *coin_pub; ret = TALER_MHD_parse_json_data (connection, root, @@ -550,9 +422,12 @@ TEH_handler_recoup (struct MHD_Connection *connection, res = verify_and_execute_recoup (connection, &coin, + &exchange_vals, &coin_bks, - &coin_sig, - refreshed); + no_nonce + ? NULL + : &nonce, + &coin_sig); GNUNET_JSON_parse_free (spec); return res; } |