diff options
author | Christian Grothoff <christian@grothoff.org> | 2019-01-11 21:27:34 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2019-01-11 21:43:15 +0100 |
commit | 54fc83ee6b910d482948c6ec8185df7aab1b0cb1 (patch) | |
tree | 10c04cad1392659a9ccef469271f866e393ebb48 /src/exchange-lib/exchange_api_reserve.c | |
parent | 57ab9f9fdba607fcc3817adf58f37c5390f8d220 (diff) | |
download | exchange-54fc83ee6b910d482948c6ec8185df7aab1b0cb1.tar.xz |
fix cyclic dependency by combining exchange-lib and auditor-lib directories
Diffstat (limited to 'src/exchange-lib/exchange_api_reserve.c')
-rw-r--r-- | src/exchange-lib/exchange_api_reserve.c | 1203 |
1 files changed, 0 insertions, 1203 deletions
diff --git a/src/exchange-lib/exchange_api_reserve.c b/src/exchange-lib/exchange_api_reserve.c deleted file mode 100644 index aa153f210..000000000 --- a/src/exchange-lib/exchange_api_reserve.c +++ /dev/null @@ -1,1203 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file exchange-lib/exchange_api_reserve.c - * @brief Implementation of the /reserve requests of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "curl_defaults.h" - - -/* ********************** /reserve/status ********************** */ - -/** - * @brief A Withdraw Status Handle - */ -struct TALER_EXCHANGE_ReserveStatusHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReserveStatusResultCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Parse history given in JSON format and return it in binary - * format. - * - * @param exchange connection to the exchange we can use - * @param history JSON array with the history - * @param reserve_pub public key of the reserve to inspect - * @param currency currency we expect the balance to be in - * @param[out] balance final balance - * @param history_length number of entries in @a history - * @param[out] rhistory array of length @a history_length, set to the - * parsed history entries - * @return #GNUNET_OK if history was valid and @a rhistory and @a balance - * were set, - * #GNUNET_SYSERR if there was a protocol violation in @a history - */ -static int -parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *balance, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistory *rhistory) -{ - struct GNUNET_HashCode uuid[history_length]; - unsigned int uuid_off; - struct TALER_Amount total_in; - struct TALER_Amount total_out; - size_t off; - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_out)); - uuid_off = 0; - for (off=0;off<history_length;off++) - { - json_t *transaction; - struct TALER_Amount amount; - const char *type; - struct GNUNET_JSON_Specification hist_spec[] = { - GNUNET_JSON_spec_string ("type", &type), - TALER_JSON_spec_amount ("amount", - &amount), - /* 'wire' and 'signature' are optional depending on 'type'! */ - GNUNET_JSON_spec_end() - }; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - hist_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].amount = amount; - - if (0 == strcasecmp (type, - "DEPOSIT")) - { - const char *wire_url; - void *wire_reference; - size_t wire_reference_size; - struct GNUNET_TIME_Absolute timestamp; - - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_varsize ("wire_reference", - &wire_reference, - &wire_reference_size), - GNUNET_JSON_spec_absolute_time ("timestamp", - ×tamp), - GNUNET_JSON_spec_string ("sender_account_url", - &wire_url), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT; - if (GNUNET_OK != - TALER_amount_add (&total_in, - &total_in, - &amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url); - rhistory[off].details.in_details.wire_reference = wire_reference; - rhistory[off].details.in_details.wire_reference_size = wire_reference_size; - rhistory[off].details.in_details.timestamp = timestamp; - /* end type==DEPOSIT */ - } - else if (0 == strcasecmp (type, - "WITHDRAW")) - { - struct TALER_ReserveSignatureP sig; - struct TALER_WithdrawRequestPS withdraw_purpose; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &sig), - TALER_JSON_spec_amount_nbo ("withdraw_fee", - &withdraw_purpose.withdraw_fee), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &withdraw_purpose.h_denomination_pub), - GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", - &withdraw_purpose.h_coin_envelope), - GNUNET_JSON_spec_end() - }; - unsigned int i; - - rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - withdraw_purpose.purpose.size - = htonl (sizeof (withdraw_purpose)); - withdraw_purpose.purpose.purpose - = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - withdraw_purpose.reserve_pub = *reserve_pub; - TALER_amount_hton (&withdraw_purpose.amount_with_fee, - &amount); - /* Check that the signature is a valid withdraw request */ - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &withdraw_purpose.purpose, - &sig.eddsa_signature, - &reserve_pub->eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - /* TODO: check that withdraw fee matches expectations! */ - rhistory[off].details.out_authorization_sig - = json_object_get (transaction, - "signature"); - /* Check check that the same withdraw transaction - isn't listed twice by the exchange. We use the - "uuid" array to remember the hashes of all - purposes, and compare the hashes to find - duplicates. */ - GNUNET_CRYPTO_hash (&withdraw_purpose, - ntohl (withdraw_purpose.purpose.size), - &uuid[uuid_off]); - for (i=0;i<uuid_off;i++) - { - if (0 == memcmp (&uuid[uuid_off], - &uuid[i], - sizeof (struct GNUNET_HashCode))) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - } - uuid_off++; - - if (GNUNET_OK != - TALER_amount_add (&total_out, - &total_out, - &amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - /* end type==WITHDRAW */ - } - else if (0 == strcasecmp (type, - "PAYBACK")) - { - struct TALER_PaybackConfirmationPS pc; - struct GNUNET_TIME_Absolute timestamp; - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification payback_spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &pc.coin_pub), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rhistory[off].details.payback_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rhistory[off].details.payback_details.exchange_pub), - GNUNET_JSON_spec_absolute_time_nbo ("timestamp", - &pc.timestamp), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK; - rhistory[off].amount = amount; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - payback_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].details.payback_details.coin_pub = pc.coin_pub; - TALER_amount_hton (&pc.payback_amount, - &amount); - pc.purpose.size = htonl (sizeof (pc)); - pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); - pc.reserve_pub = *reserve_pub; - timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp); - rhistory[off].details.payback_details.timestamp = timestamp; - - key_state = TALER_EXCHANGE_get_keys (exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rhistory[off].details.payback_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, - &pc.purpose, - &rhistory[off].details.payback_details.exchange_sig.eddsa_signature, - &rhistory[off].details.payback_details.exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_in, - &total_in, - &rhistory[off].amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* end type==PAYBACK */ - } - else if (0 == strcasecmp (type, - "CLOSING")) - { - const struct TALER_EXCHANGE_Keys *key_state; - struct TALER_ReserveCloseConfirmationPS rcc; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_JSON_Specification closing_spec[] = { - GNUNET_JSON_spec_string ("receiver_account_details", - &rhistory[off].details.close_details.receiver_account_details), - GNUNET_JSON_spec_fixed_auto ("wtid", - &rhistory[off].details.close_details.wtid), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rhistory[off].details.close_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rhistory[off].details.close_details.exchange_pub), - TALER_JSON_spec_amount_nbo ("closing_fee", - &rcc.closing_fee), - GNUNET_JSON_spec_absolute_time_nbo ("timestamp", - &rcc.timestamp), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE; - rhistory[off].amount = amount; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - closing_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - TALER_amount_hton (&rcc.closing_amount, - &amount); - GNUNET_CRYPTO_hash (rhistory[off].details.close_details.receiver_account_details, - strlen (rhistory[off].details.close_details.receiver_account_details) + 1, - &rcc.h_wire); - rcc.wtid = rhistory[off].details.close_details.wtid; - rcc.purpose.size = htonl (sizeof (rcc)); - rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); - rcc.reserve_pub = *reserve_pub; - timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp); - rhistory[off].details.close_details.timestamp = timestamp; - - key_state = TALER_EXCHANGE_get_keys (exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rhistory[off].details.close_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED, - &rcc.purpose, - &rhistory[off].details.close_details.exchange_sig.eddsa_signature, - &rhistory[off].details.close_details.exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_out, - &total_out, - &rhistory[off].amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* end type==CLOSING */ - } - else - { - /* unexpected 'type', protocol incompatibility, complain! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - - /* check balance = total_in - total_out < withdraw-amount */ - if (GNUNET_SYSERR == - TALER_amount_subtract (balance, - &total_in, - &total_out)) - { - /* total_in < total_out, why did the exchange ever allow this!? */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Free memory (potentially) allocated by #parse_reserve_history(). - * - * @param rhistory result to free - * @param len number of entries in @a rhistory - */ -static void -free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory, - unsigned int len) -{ - for (unsigned int i=0;i<len;i++) - { - switch (rhistory[i].type) - { - case TALER_EXCHANGE_RTT_DEPOSIT: - GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference); - GNUNET_free_non_null (rhistory[i].details.in_details.sender_url); - break; - case TALER_EXCHANGE_RTT_WITHDRAWAL: - break; - case TALER_EXCHANGE_RTT_PAYBACK: - break; - case TALER_EXCHANGE_RTT_CLOSE: - // should we free "receiver_account_details" ? - break; - } - } -} - - -/** - * Function called when we're done processing the - * HTTP /reserve/status request. - * - * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_status_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls; - const json_t *j = response; - - rsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - /* TODO: move into separate function... */ - json_t *history; - unsigned int len; - struct TALER_Amount balance; - struct TALER_Amount balance_from_history; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("balance", &balance), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - history = json_object_get (j, - "history"); - if (NULL == history) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistory rhistory[len]; - - memset (rhistory, 0, sizeof (rhistory)); - if (GNUNET_OK != - parse_reserve_history (rsh->exchange, - history, - &rsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - response_code = 0; - } - if ( (0 != response_code) && - (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) ) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - response_code = 0; - } - if (0 != response_code) - { - rsh->cb (rsh->cb_cls, - response_code, - TALER_EC_NONE, - j, - &balance, - len, - rhistory); - rsh->cb = NULL; - } - free_rhistory (rhistory, - len); - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - j, - NULL, - 0, NULL); - rsh->cb = NULL; - } - TALER_EXCHANGE_reserve_status_cancel (rsh); -} - - -/** - * Submit a request to obtain the transaction history of a reserve - * from the exchange. Note that while we return the full response to the - * caller for further processing, we do already verify that the - * response is well-formed (i.e. that signatures included in the - * response are all valid and add up to the balance). If the exchange's - * reply is not well-formed, we return an HTTP status code of zero to - * @a cb. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param reserve_pub public key of the reserve to inspect - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveStatusHandle * -TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_ReservePublicKeyP *reserve_pub, - TALER_EXCHANGE_ReserveStatusResultCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReserveStatusHandle *rsh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - char *pub_str; - char *arg_str; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, - sizeof (struct TALER_ReservePublicKeyP)); - GNUNET_asprintf (&arg_str, - "/reserve/status?reserve_pub=%s", - pub_str); - GNUNET_free (pub_str); - rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); - rsh->exchange = exchange; - rsh->cb = cb; - rsh->cb_cls = cb_cls; - rsh->reserve_pub = *reserve_pub; - rsh->url = TEAH_path_to_url (exchange, - arg_str); - GNUNET_free (arg_str); - - eh = TEL_curl_easy_get (rsh->url); - ctx = TEAH_handle_to_context (exchange); - rsh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_reserve_status_finished, - rsh); - return rsh; -} - - -/** - * Cancel a reserve status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param rsh the reserve status request handle - */ -void -TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - GNUNET_free (rsh->url); - GNUNET_free (rsh); -} - - -/* ********************** /reserve/withdraw ********************** */ - -/** - * @brief A Withdraw Sign Handle - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReserveWithdrawResultCallback cb; - - /** - * Secrets of the planchet. - */ - struct TALER_PlanchetSecretsP ps; - - /** - * Denomination key we are withdrawing. - */ - const struct TALER_EXCHANGE_DenomPublicKey *pk; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Hash of the public key of the coin we are signing. - */ - struct GNUNET_HashCode c_hash; - - /** - * Public key of the reserve we are withdrawing from. - */ - struct TALER_ReservePublicKeyP reserve_pub; - -}; - - -/** - * We got a 200 OK response for the /reserve/withdraw operation. - * Extract the coin's signature and return it to the caller. - * The signature we get from the exchange is for the blinded value. - * Thus, we first must unblind it and then should verify its - * validity against our coin's hash. - * - * If everything checks out, we return the unblinded signature - * to the application via the callback. - * - * @param wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, - const json_t *json) -{ - struct GNUNET_CRYPTO_RsaSignature *blind_sig; - struct TALER_FreshCoin fc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_signature ("ev_sig", - &blind_sig), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_planchet_to_coin (&wsh->pk->key, - blind_sig, - &wsh->ps, - &wsh->c_hash, - &fc)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - /* signature is valid, return it to the application */ - wsh->cb (wsh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &fc.sig, - json); - /* make sure callback isn't called again after return */ - wsh->cb = NULL; - GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); - return GNUNET_OK; -} - - -/** - * We got a 403 FORBIDDEN response for the /reserve/withdraw operation. - * Check the signatures on the withdraw transactions in the provided - * history and that the balances add up. We don't do anything directly - * with the information, as the JSON will be returned to the application. - * However, our job is ensuring that the exchange followed the protocol, and - * this in particular means checking all of the signatures in the history. - * - * @param wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, - const json_t *json) -{ - struct TALER_Amount balance; - struct TALER_Amount balance_from_history; - struct TALER_Amount requested_amount; - json_t *history; - size_t len; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("balance", &balance), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - history = json_object_get (json, - "history"); - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* go over transaction history and compute - total incoming and outgoing amounts */ - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistory *rhistory; - - /* Use heap allocation as "len" may be very big and thus this may - not fit on the stack. Use "GNUNET_malloc_large" as a malicious - exchange may theoretically try to crash us by giving a history - that does not fit into our memory. */ - rhistory = GNUNET_malloc_large (sizeof (struct TALER_EXCHANGE_ReserveHistory) * len); - if (NULL == rhistory) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - parse_reserve_history (wsh->exchange, - history, - &wsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - free_rhistory (rhistory, - len); - return GNUNET_SYSERR; - } - free_rhistory (rhistory, - len); - } - - if (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* Compute how much we expected to charge to the reserve */ - if (GNUNET_OK != - TALER_amount_add (&requested_amount, - &wsh->pk->value, - &wsh->pk->fee_withdraw)) - { - /* Overflow here? Very strange, our CPU must be fried... */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check that funds were really insufficient */ - if (0 >= TALER_amount_cmp (&requested_amount, - &balance)) - { - /* Requested amount is smaller or equal to reported balance, - so this should not have failed. */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserve/withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_withdraw_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls; - const json_t *j = response; - - wsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - reserve_withdraw_ok (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ - if (GNUNET_OK != - reserve_withdraw_payment_required (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_UNAUTHORIZED: - GNUNET_break (0); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this reserve. Can happen if we - query before the wire transfer went through. - We should simply pass the JSON reply to the application. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != wsh->cb) - { - wsh->cb (wsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - j); - wsh->cb = NULL; - } - TALER_EXCHANGE_reserve_withdraw_cancel (wsh); -} - - -/** - * Helper function for #TALER_EXCHANGE_reserve_withdraw2() and - * #TALER_EXCHANGE_reserve_withdraw(). - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param pd planchet details matching @a ps - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReserveSignatureP *reserve_sig, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - const struct TALER_PlanchetDetail *pd, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - struct GNUNET_CURL_Context *ctx; - json_t *withdraw_obj; - CURL *eh; - - wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle); - wsh->exchange = exchange; - wsh->cb = res_cb; - wsh->cb_cls = res_cb_cls; - wsh->pk = pk; - wsh->reserve_pub = *reserve_pub; - wsh->c_hash = pd->c_hash; - withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */ - " s:o, s:o}",/* reserve_pub and reserve_sig */ - "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), - "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, - pd->coin_ev_size), - "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), - "reserve_sig", GNUNET_JSON_from_data_auto (reserve_sig)); - if (NULL == withdraw_obj) - { - GNUNET_break (0); - return NULL; - } - - wsh->ps = *ps; - wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw"); - - eh = TEL_curl_easy_get (wsh->url); - GNUNET_assert (NULL != (wsh->json_enc = - json_dumps (withdraw_obj, - JSON_COMPACT))); - json_decref (withdraw_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - wsh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (wsh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - wsh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_reserve_withdraw_finished, - wsh); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw request. Note - * that to ensure that no money is lost in case of hardware failures, - * the caller must have committed (most of) the arguments to disk - * before calling, and be ready to repeat the request with the same - * arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_priv private key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for the above callback - * @return handle for the operation on success, NULL on error, i.e. - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_Amount amount_with_fee; - struct TALER_ReserveSignatureP reserve_sig; - struct TALER_WithdrawRequestPS req; - struct TALER_PlanchetDetail pd; - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &req.reserve_pub.eddsa_pub); - req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); - req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - if (GNUNET_OK != - TALER_amount_add (&amount_with_fee, - &pk->fee_withdraw, - &pk->value)) - { - /* exchange gave us denomination keys that overflow like this!? */ - GNUNET_break_op (0); - return NULL; - } - TALER_amount_hton (&req.amount_with_fee, - &amount_with_fee); - TALER_amount_hton (&req.withdraw_fee, - &pk->fee_withdraw); - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - req.h_denomination_pub = pd.denom_pub_hash; - GNUNET_CRYPTO_hash (pd.coin_ev, - pd.coin_ev_size, - &req.h_coin_envelope); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, - &req.purpose, - &reserve_sig.eddsa_signature)); - wsh = reserve_withdraw_internal (exchange, - pk, - &reserve_sig, - &req.reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw - * request. This API is typically used by a wallet to withdraw a tip - * where the reserve's signature was created by the merchant already. - * - * Note that to ensure that no money is lost in case of hardware - * failures, the caller must have committed (most of) the arguments to - * disk before calling, and be ready to repeat the request with the - * same arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -TALER_EXCHANGE_reserve_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReserveSignatureP *reserve_sig, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - struct TALER_PlanchetDetail pd; - - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - wsh = reserve_withdraw_internal (exchange, - pk, - reserve_sig, - reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Cancel a withdraw status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param sign the withdraw sign request handle - */ -void -TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign) -{ - if (NULL != sign->job) - { - GNUNET_CURL_job_cancel (sign->job); - sign->job = NULL; - } - GNUNET_free (sign->url); - GNUNET_free (sign->json_enc); - GNUNET_free (sign); -} - - -/* end of exchange_api_reserve.c */ |