diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_withdraw.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_withdraw.c | 510 |
1 files changed, 0 insertions, 510 deletions
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c deleted file mode 100644 index d9cba045c..000000000 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ /dev/null @@ -1,510 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2019 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 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 <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_withdraw.c - * @brief Handle /reserves/$RESERVE_PUB/withdraw requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <jansson.h> -#include "taler_json_lib.h" -#include "taler_mhd_lib.h" -#include "taler-exchange-httpd_withdraw.h" -#include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keys.h" - - -/** - * Perform RSA signature before checking with the database? - * Reduces time spent in transaction, but may cause us to - * waste CPU time if DB check fails. - */ -#define OPTIMISTIC_SIGN 1 - - -/** - * Send reserve history information to client with the - * message that we have insufficient funds for the - * requested withdraw operation. - * - * @param connection connection to the client - * @param ebalance expected balance based on our database - * @param rh reserve history to return - * @return MHD result code - */ -static MHD_RESULT -reply_withdraw_insufficient_funds ( - struct MHD_Connection *connection, - const struct TALER_Amount *ebalance, - const struct TALER_EXCHANGEDB_ReserveHistory *rh) -{ - json_t *json_history; - struct TALER_Amount balance; - - json_history = TEH_RESPONSE_compile_reserve_history (rh, - &balance); - if (NULL == json_history) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS, - NULL); - if (0 != - TALER_amount_cmp (&balance, - ebalance)) - { - GNUNET_break (0); - json_decref (json_history); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - "reserve balance corrupt"); - } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_CONFLICT, - TALER_JSON_pack_ec (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS), - TALER_JSON_pack_amount ("balance", - &balance), - GNUNET_JSON_pack_array_steal ("history", - json_history)); -} - - -/** - * Context for #withdraw_transaction. - */ -struct WithdrawContext -{ - /** - * Details about the withdrawal request. - */ - struct TALER_WithdrawRequestPS wsrd; - - /** - * Value of the coin plus withdraw fee. - */ - struct TALER_Amount amount_required; - - /** - * Hash of the denomination public key. - */ - struct GNUNET_HashCode denom_pub_hash; - - /** - * Signature over the request. - */ - struct TALER_ReserveSignatureP signature; - - /** - * Blinded planchet. - */ - char *blinded_msg; - - /** - * Number of bytes in @e blinded_msg. - */ - size_t blinded_msg_len; - - /** - * Set to the resulting signed coin data to be returned to the client. - */ - struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; - -}; - - -/** - * Function implementing withdraw transaction. Runs the - * transaction logic; IF it returns a non-error code, the transaction - * logic MUST NOT queue a MHD response. IF it returns an hard error, - * the transaction logic MUST queue a MHD response and set @a mhd_ret. - * IF it returns the soft error code, the function MAY be called again - * to retry and MUST not queue a MHD response. - * - * Note that "wc->collectable.sig" may already be set before entering - * this function, either because OPTIMISTIC_SIGN was used and we signed - * before entering the transaction, or because this function is run - * twice (!) by #TEH_DB_run_transaction() and the first time created - * the signature and then failed to commit. Furthermore, we may get - * a 2nd correct signature briefly if "get_withdraw_info" succeeds and - * finds one in the DB. To avoid signing twice, the function may - * return a valid signature in "wc->collectable.sig" **even if it failed**. - * The caller must thus free the signature in either case. - * - * @param cls a `struct WithdrawContext *` - * @param connection MHD request which triggered the transaction - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -withdraw_transaction (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) -{ - struct WithdrawContext *wc = cls; - struct TALER_EXCHANGEDB_Reserve r; - enum GNUNET_DB_QueryStatus qs; - struct TALER_DenominationSignature denom_sig; - -#if OPTIMISTIC_SIGN - /* store away optimistic signature to protect - it from being overwritten by get_withdraw_info */ - denom_sig = wc->collectable.sig; - wc->collectable.sig.rsa_signature = NULL; -#endif - qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &wc->wsrd.h_coin_envelope, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "withdraw details"); - wc->collectable.sig = denom_sig; - return qs; - } - - /* Don't sign again if we have already signed the coin */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - /* Toss out the optimistic signature, we got another one from the DB; - optimization trade-off loses in this case: we unnecessarily computed - a signature :-( */ -#if OPTIMISTIC_SIGN - GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature); -#endif - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - /* We should never get more than one result, and we handled - the errors (negative case) above, so that leaves no results. */ - GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); - wc->collectable.sig = denom_sig; /* Note: might still be NULL if we didn't do OPTIMISTIC_SIGN */ - - /* Check if balance is sufficient */ - r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to withdraw from reserve: %s\n", - TALER_B2S (&r.pub)); - qs = TEH_plugin->reserves_get (TEH_plugin->cls, - &r); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserves"); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (0 < TALER_amount_cmp (&wc->amount_required, - &r.balance)) - { - struct TALER_EXCHANGEDB_ReserveHistory *rh; - - /* The reserve does not have the required amount (actual - * amount + withdraw fee) */ -#if GNUNET_EXTRA_LOGGING - { - char *amount_required; - char *r_balance; - - amount_required = TALER_amount_to_string (&wc->amount_required); - r_balance = TALER_amount_to_string (&r.balance); - TALER_LOG_DEBUG ("Asked %s over a reserve worth %s\n", - amount_required, - r_balance); - GNUNET_free (amount_required); - GNUNET_free (r_balance); - } -#endif - qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, - &wc->wsrd.reserve_pub, - &rh); - if (NULL == rh) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserve history"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - *mhd_ret = reply_withdraw_insufficient_funds (connection, - &r.balance, - rh); - TEH_plugin->free_reserve_history (TEH_plugin->cls, - rh); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Balance is good, sign the coin! */ -#if ! OPTIMISTIC_SIGN - if (NULL == wc->collectable.sig.rsa_signature) - { - enum TALER_ErrorCode ec; - - wc->collectable.sig - = TEH_keys_denomination_sign (&wc->denom_pub_hash, - wc->blinded_msg, - wc->blinded_msg_len, - &ec); - if (NULL == wc->collectable.sig.rsa_signature) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - ec, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } -#endif - wc->collectable.denom_pub_hash = wc->denom_pub_hash; - wc->collectable.amount_with_fee = wc->amount_required; - wc->collectable.reserve_pub = wc->wsrd.reserve_pub; - wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; - wc->collectable.reserve_sig = wc->signature; - qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "withdraw details"); - return qs; - } - return qs; -} - - -MHD_RESULT -TEH_handler_withdraw (struct TEH_RequestContext *rc, - const json_t *root, - const char *const args[2]) -{ - struct WithdrawContext wc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_varsize ("coin_ev", - (void **) &wc.blinded_msg, - &wc.blinded_msg_len), - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &wc.signature), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &wc.denom_pub_hash), - GNUNET_JSON_spec_end () - }; - enum TALER_ErrorCode ec; - struct TEH_DenominationKey *dk; - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &wc.wsrd.reserve_pub, - sizeof (wc.wsrd.reserve_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED, - args[0]); - } - - { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (rc->connection, - root, - spec); - if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - } - { - MHD_RESULT mret; - struct GNUNET_TIME_Absolute now; - - dk = TEH_keys_denomination_by_hash (&wc.denom_pub_hash, - rc->connection, - &mret); - if (NULL == dk) - { - GNUNET_JSON_parse_free (spec); - return mret; - } - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw)) - { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - /* This denomination is past the expiration time for withdraws */ - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.denom_pub_hash, - now, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "WITHDRAW"); - } - if (GNUNET_TIME_absolute_is_future (dk->meta.start)) - { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - /* This denomination is not yet valid */ - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.denom_pub_hash, - now, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, - "WITHDRAW"); - } - if (dk->recoup_possible) - { - struct GNUNET_TIME_Absolute now; - - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - /* This denomination has been revoked */ - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.denom_pub_hash, - now, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - "WITHDRAW"); - } - } - - { - if (0 > - TALER_amount_add (&wc.amount_required, - &dk->meta.value, - &dk->meta.fee_withdraw)) - { - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW, - NULL); - } - TALER_amount_hton (&wc.wsrd.amount_with_fee, - &wc.amount_required); - } - - /* verify signature! */ - wc.wsrd.purpose.size - = htonl (sizeof (wc.wsrd)); - wc.wsrd.purpose.purpose - = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - wc.wsrd.h_denomination_pub - = wc.denom_pub_hash; - GNUNET_CRYPTO_hash (wc.blinded_msg, - wc.blinded_msg_len, - &wc.wsrd.h_coin_envelope); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &wc.wsrd, - &wc.signature.eddsa_signature, - &wc.wsrd.reserve_pub.eddsa_pub)) - { - TALER_LOG_WARNING ( - "Client supplied invalid signature for withdraw request\n"); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, - NULL); - } - -#if OPTIMISTIC_SIGN - /* Sign before transaction! */ - wc.collectable.sig - = TEH_keys_denomination_sign (&wc.denom_pub_hash, - wc.blinded_msg, - wc.blinded_msg_len, - &ec); - if (NULL == wc.collectable.sig.rsa_signature) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_ec (rc->connection, - ec, - NULL); - } -#endif - - /* run transaction and sign (if not optimistically signed before) */ - { - MHD_RESULT mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (rc->connection, - "run withdraw", - &mhd_ret, - &withdraw_transaction, - &wc)) - { - /* Even if #withdraw_transaction() failed, it may have created a signature - (or we might have done it optimistically above). */ - if (NULL != wc.collectable.sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); - GNUNET_JSON_parse_free (spec); - return mhd_ret; - } - } - - /* Clean up and send back final (positive) response */ - GNUNET_JSON_parse_free (spec); - - { - MHD_RESULT ret; - - ret = TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_rsa_signature ("ev_sig", - wc.collectable.sig.rsa_signature)); - GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); - return ret; - } -} - - -/* end of taler-exchange-httpd_withdraw.c */ |