diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-04-12 19:22:45 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-04-12 19:22:45 +0200 |
commit | 9e3371ae9b67a154f1c03f7003a10aa75f0e9470 (patch) | |
tree | efd81c1788359b61925970e2ecead35a17ec31c1 /src/lib/exchange_api_withdraw.c | |
parent | 00350bc228af8f79df56df13aa10e5f86de5a5a5 (diff) | |
download | exchange-9e3371ae9b67a154f1c03f7003a10aa75f0e9470.tar.xz |
adapt withdraw2 API to support new tip-pickup design (#6173)
Diffstat (limited to 'src/lib/exchange_api_withdraw.c')
-rw-r--r-- | src/lib/exchange_api_withdraw.c | 551 |
1 files changed, 60 insertions, 491 deletions
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index 8cd1ac012..6b20fb84f 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -16,7 +16,7 @@ */ /** * @file lib/exchange_api_withdraw.c - * @brief Implementation of the /reserves/$RESERVE_PUB/withdraw requests + * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests with blinding/unblinding * @author Christian Grothoff */ #include "platform.h" @@ -44,25 +44,19 @@ struct TALER_EXCHANGE_WithdrawHandle struct TALER_EXCHANGE_Handle *exchange; /** - * The url for this request. + * Handle for the actual (internal) withdraw operation. */ - char *url; + struct TALER_EXCHANGE_Withdraw2Handle *wh2; /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. + * Function to call with the result. */ - struct GNUNET_CURL_Job *job; + TALER_EXCHANGE_WithdrawCallback cb; /** - * Function to call with the result. + * Closure for @a cb. */ - TALER_EXCHANGE_WithdrawCallback cb; + void *cb_cls; /** * Secrets of the planchet. @@ -75,408 +69,67 @@ struct TALER_EXCHANGE_WithdrawHandle 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 /reserves/$RESERVE_PUB/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 wh 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_WithdrawHandle *wh, - 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 () - }; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_planchet_to_coin (&wh->pk.key, - blind_sig, - &wh->ps, - &wh->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 */ - wh->cb (wh->cb_cls, - &hr, - &fc.sig); - /* make sure callback isn't called again after return */ - wh->cb = NULL; - GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); - return GNUNET_OK; -} - - -/** - * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/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 wh 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_WithdrawHandle *wh, - 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 != - TALER_EXCHANGE_parse_reserve_history (wh->exchange, - history, - &wh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - TALER_EXCHANGE_free_reserve_history (rhistory, - len); - return GNUNET_SYSERR; - } - TALER_EXCHANGE_free_reserve_history (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 (0 > - TALER_amount_add (&requested_amount, - &wh->pk.value, - &wh->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 /reserves/$RESERVE_PUB/withdraw request. * * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error + * @param hr HTTP response data + * @param blind_sig blind signature over the coin, NULL on error */ static void -handle_reserve_withdraw_finished (void *cls, - long response_code, - const void *response) +handle_reserve_withdraw_finished ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + const struct GNUNET_CRYPTO_RsaSignature *blind_sig) { struct TALER_EXCHANGE_WithdrawHandle *wh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code - }; - wh->job = NULL; - switch (response_code) + wh->wh2 = NULL; + if (MHD_HTTP_OK != hr->http_status) { - case 0: - hr.ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - reserve_withdraw_ok (wh, - j)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_WITHDRAW_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == wh->cb); - TALER_EXCHANGE_withdraw_cancel (wh); - return; - 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 */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ + wh->cb (wh->cb_cls, + hr, + NULL); + } + else + { + struct TALER_FreshCoin fc; + if (GNUNET_OK != - reserve_withdraw_payment_required (wh, - j)) + TALER_planchet_to_coin (&wh->pk.key, + blind_sig, + &wh->ps, + &wh->c_hash, + &fc)) { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_WITHDRAW_REPLY_MALFORMED; + struct TALER_EXCHANGE_HttpResponse hrx = { + .reply = hr->reply, + .http_status = 0, + .ec = TALER_EC_WITHDRAW_UNBLIND_FAILURE + }; + + wh->cb (wh->cb_cls, + &hrx, + NULL); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + wh->cb (wh->cb_cls, + hr, + &fc.sig); + GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); } - break; - case MHD_HTTP_FORBIDDEN: - GNUNET_break_op (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 */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - 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. */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &hr, - NULL); - wh->cb = NULL; - } - TALER_EXCHANGE_withdraw_cancel (wh); -} - - -/** - * Helper function for #TALER_EXCHANGE_withdraw2() and - * #TALER_EXCHANGE_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 c_hash hash over the coin's public key - * @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_WithdrawHandle * -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, - const struct GNUNET_HashCode *c_hash, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - struct GNUNET_CURL_Context *ctx; - json_t *withdraw_obj; - CURL *eh; - struct GNUNET_HashCode h_denom_pub; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (struct - TALER_ReservePublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/reserves/%s/withdraw", - pub_str); } - wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); - wh->exchange = exchange; - wh->cb = res_cb; - wh->cb_cls = res_cb_cls; - wh->pk = *pk; - wh->pk.key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key); - wh->reserve_pub = *reserve_pub; - wh->c_hash = *c_hash; - GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key, - &h_denom_pub); - withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */ - " s:o}",/* reserve_pub and reserve_sig */ - "denom_pub_hash", GNUNET_JSON_from_data_auto ( - &h_denom_pub), - "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, - pd->coin_ev_size), - "reserve_sig", GNUNET_JSON_from_data_auto ( - reserve_sig)); - if (NULL == withdraw_obj) - { - GNUNET_break (0); - GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); - GNUNET_free (wh); - return NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Attempting to withdraw from reserve %s\n", - TALER_B2S (reserve_pub)); - wh->ps = *ps; - wh->url = TEAH_path_to_url (exchange, - arg_str); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->ctx, - eh, - withdraw_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (withdraw_obj); - GNUNET_free (wh->url); - GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); - GNUNET_free (wh); - return NULL; - } - json_decref (withdraw_obj); - ctx = TEAH_handle_to_context (exchange); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->ctx.headers, - &handle_reserve_withdraw_finished, - wh); - return wh; + TALER_EXCHANGE_withdraw_cancel (wh); } @@ -507,114 +160,32 @@ TALER_EXCHANGE_withdraw ( TALER_EXCHANGE_WithdrawCallback 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_WithdrawHandle *wh; - struct GNUNET_HashCode c_hash; - - 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 (0 > - 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, - &c_hash, - &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_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, - &req, - &reserve_sig.eddsa_signature); - wh = reserve_withdraw_internal (exchange, - pk, - &reserve_sig, - &req.reserve_pub, - ps, - &pd, - &c_hash, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wh; -} - - -/** - * 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_WithdrawHandle * -TALER_EXCHANGE_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_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - struct GNUNET_HashCode c_hash; - struct TALER_PlanchetDetail pd; + wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); + wh->exchange = exchange; + wh->cb = res_cb; + wh->cb_cls = res_cb_cls; + wh->pk = *pk; + wh->ps = *ps; if (GNUNET_OK != TALER_planchet_prepare (&pk->key, ps, - &c_hash, + &wh->c_hash, &pd)) { GNUNET_break_op (0); + GNUNET_free (wh); return NULL; } - wh = reserve_withdraw_internal (exchange, - pk, - reserve_sig, - reserve_pub, - ps, - &pd, - &c_hash, - res_cb, - res_cb_cls); + wh->pk.key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key); + wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange, + &pd, + reserve_priv, + &handle_reserve_withdraw_finished, + wh); GNUNET_free (pd.coin_ev); return wh; } @@ -629,13 +200,11 @@ TALER_EXCHANGE_withdraw2 ( void TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh) { - if (NULL != wh->job) + if (NULL != wh->wh2) { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; + TALER_EXCHANGE_withdraw2_cancel (wh->wh2); + wh->wh2 = NULL; } - GNUNET_free (wh->url); - TALER_curl_easy_post_finished (&wh->ctx); GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); GNUNET_free (wh); } |