LCOV - code coverage report
Current view: top level - lib - exchange_api_withdraw2.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 96 163 58.9 %
Date: 2021-08-30 06:43:37 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see
      15             :   <http://www.gnu.org/licenses/>
      16             : */
      17             : /**
      18             :  * @file lib/exchange_api_withdraw2.c
      19             :  * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests without blinding/unblinding
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <jansson.h>
      24             : #include <microhttpd.h> /* just for HTTP status codes */
      25             : #include <gnunet/gnunet_util_lib.h>
      26             : #include <gnunet/gnunet_json_lib.h>
      27             : #include <gnunet/gnunet_curl_lib.h>
      28             : #include "taler_exchange_service.h"
      29             : #include "taler_json_lib.h"
      30             : #include "exchange_api_handle.h"
      31             : #include "taler_signatures.h"
      32             : #include "exchange_api_curl_defaults.h"
      33             : 
      34             : 
      35             : /**
      36             :  * @brief A Withdraw Handle
      37             :  */
      38             : struct TALER_EXCHANGE_Withdraw2Handle
      39             : {
      40             : 
      41             :   /**
      42             :    * The connection to exchange this request handle will use
      43             :    */
      44             :   struct TALER_EXCHANGE_Handle *exchange;
      45             : 
      46             :   /**
      47             :    * The url for this request.
      48             :    */
      49             :   char *url;
      50             : 
      51             :   /**
      52             :    * Handle for the request.
      53             :    */
      54             :   struct GNUNET_CURL_Job *job;
      55             : 
      56             :   /**
      57             :    * Function to call with the result.
      58             :    */
      59             :   TALER_EXCHANGE_Withdraw2Callback cb;
      60             : 
      61             :   /**
      62             :    * Closure for @a cb.
      63             :    */
      64             :   void *cb_cls;
      65             : 
      66             :   /**
      67             :    * Context for #TEH_curl_easy_post(). Keeps the data that must
      68             :    * persist for Curl to make the upload.
      69             :    */
      70             :   struct TALER_CURL_PostContext post_ctx;
      71             : 
      72             :   /**
      73             :    * Total amount requested (value plus withdraw fee).
      74             :    */
      75             :   struct TALER_Amount requested_amount;
      76             : 
      77             :   /**
      78             :    * Public key of the reserve we are withdrawing from.
      79             :    */
      80             :   struct TALER_ReservePublicKeyP reserve_pub;
      81             : 
      82             : };
      83             : 
      84             : 
      85             : /**
      86             :  * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
      87             :  * Extract the coin's signature and return it to the caller.  The signature we
      88             :  * get from the exchange is for the blinded value.  Thus, we first must
      89             :  * unblind it and then should verify its validity against our coin's hash.
      90             :  *
      91             :  * If everything checks out, we return the unblinded signature
      92             :  * to the application via the callback.
      93             :  *
      94             :  * @param wh operation handle
      95             :  * @param json reply from the exchange
      96             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
      97             :  */
      98             : static int
      99          35 : reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh,
     100             :                      const json_t *json)
     101             : {
     102             :   struct GNUNET_CRYPTO_RsaSignature *blind_sig;
     103             :   struct GNUNET_JSON_Specification spec[] = {
     104          35 :     GNUNET_JSON_spec_rsa_signature ("ev_sig",
     105             :                                     &blind_sig),
     106          35 :     GNUNET_JSON_spec_end ()
     107             :   };
     108          35 :   struct TALER_EXCHANGE_HttpResponse hr = {
     109             :     .reply = json,
     110             :     .http_status = MHD_HTTP_OK
     111             :   };
     112             : 
     113          35 :   if (GNUNET_OK !=
     114          35 :       GNUNET_JSON_parse (json,
     115             :                          spec,
     116             :                          NULL, NULL))
     117             :   {
     118           0 :     GNUNET_break_op (0);
     119           0 :     return GNUNET_SYSERR;
     120             :   }
     121             : 
     122             :   /* signature is valid, return it to the application */
     123          35 :   wh->cb (wh->cb_cls,
     124             :           &hr,
     125             :           blind_sig);
     126             :   /* make sure callback isn't called again after return */
     127          35 :   wh->cb = NULL;
     128          35 :   GNUNET_JSON_parse_free (spec);
     129          35 :   return GNUNET_OK;
     130             : }
     131             : 
     132             : 
     133             : /**
     134             :  * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
     135             :  * Check the signatures on the withdraw transactions in the provided
     136             :  * history and that the balances add up.  We don't do anything directly
     137             :  * with the information, as the JSON will be returned to the application.
     138             :  * However, our job is ensuring that the exchange followed the protocol, and
     139             :  * this in particular means checking all of the signatures in the history.
     140             :  *
     141             :  * @param wh operation handle
     142             :  * @param json reply from the exchange
     143             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     144             :  */
     145             : static int
     146           3 : reserve_withdraw_payment_required (
     147             :   struct TALER_EXCHANGE_Withdraw2Handle *wh,
     148             :   const json_t *json)
     149             : {
     150             :   struct TALER_Amount balance;
     151             :   struct TALER_Amount balance_from_history;
     152             :   json_t *history;
     153             :   size_t len;
     154             :   struct GNUNET_JSON_Specification spec[] = {
     155           3 :     TALER_JSON_spec_amount_any ("balance", &balance),
     156           3 :     GNUNET_JSON_spec_end ()
     157             :   };
     158             : 
     159           3 :   if (GNUNET_OK !=
     160           3 :       GNUNET_JSON_parse (json,
     161             :                          spec,
     162             :                          NULL, NULL))
     163             :   {
     164           0 :     GNUNET_break_op (0);
     165           0 :     return GNUNET_SYSERR;
     166             :   }
     167           3 :   history = json_object_get (json,
     168             :                              "history");
     169           3 :   if (NULL == history)
     170             :   {
     171           0 :     GNUNET_break_op (0);
     172           0 :     return GNUNET_SYSERR;
     173             :   }
     174             : 
     175             :   /* go over transaction history and compute
     176             :      total incoming and outgoing amounts */
     177           3 :   len = json_array_size (history);
     178             :   {
     179             :     struct TALER_EXCHANGE_ReserveHistory *rhistory;
     180             : 
     181             :     /* Use heap allocation as "len" may be very big and thus this may
     182             :        not fit on the stack. Use "GNUNET_malloc_large" as a malicious
     183             :        exchange may theoretically try to crash us by giving a history
     184             :        that does not fit into our memory. */
     185           3 :     rhistory = GNUNET_malloc_large (sizeof (struct
     186             :                                             TALER_EXCHANGE_ReserveHistory)
     187             :                                     * len);
     188           3 :     if (NULL == rhistory)
     189             :     {
     190           0 :       GNUNET_break (0);
     191           0 :       return GNUNET_SYSERR;
     192             :     }
     193             : 
     194           3 :     if (GNUNET_OK !=
     195           3 :         TALER_EXCHANGE_parse_reserve_history (wh->exchange,
     196             :                                               history,
     197           3 :                                               &wh->reserve_pub,
     198             :                                               balance.currency,
     199             :                                               &balance_from_history,
     200             :                                               len,
     201             :                                               rhistory))
     202             :     {
     203           0 :       GNUNET_break_op (0);
     204           0 :       TALER_EXCHANGE_free_reserve_history (rhistory,
     205             :                                            len);
     206           0 :       return GNUNET_SYSERR;
     207             :     }
     208           3 :     TALER_EXCHANGE_free_reserve_history (rhistory,
     209             :                                          len);
     210             :   }
     211             : 
     212           3 :   if (0 !=
     213           3 :       TALER_amount_cmp (&balance_from_history,
     214             :                         &balance))
     215             :   {
     216             :     /* exchange cannot add up balances!? */
     217           0 :     GNUNET_break_op (0);
     218           0 :     return GNUNET_SYSERR;
     219             :   }
     220             :   /* Check that funds were really insufficient */
     221           3 :   if (0 >= TALER_amount_cmp (&wh->requested_amount,
     222             :                              &balance))
     223             :   {
     224             :     /* Requested amount is smaller or equal to reported balance,
     225             :        so this should not have failed. */
     226           0 :     GNUNET_break_op (0);
     227           0 :     return GNUNET_SYSERR;
     228             :   }
     229           3 :   return GNUNET_OK;
     230             : }
     231             : 
     232             : 
     233             : /**
     234             :  * Function called when we're done processing the
     235             :  * HTTP /reserves/$RESERVE_PUB/withdraw request.
     236             :  *
     237             :  * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
     238             :  * @param response_code HTTP response code, 0 on error
     239             :  * @param response parsed JSON result, NULL on error
     240             :  */
     241             : static void
     242          39 : handle_reserve_withdraw_finished (void *cls,
     243             :                                   long response_code,
     244             :                                   const void *response)
     245             : {
     246          39 :   struct TALER_EXCHANGE_Withdraw2Handle *wh = cls;
     247          39 :   const json_t *j = response;
     248          39 :   struct TALER_EXCHANGE_HttpResponse hr = {
     249             :     .reply = j,
     250          39 :     .http_status = (unsigned int) response_code
     251             :   };
     252             : 
     253          39 :   wh->job = NULL;
     254          39 :   switch (response_code)
     255             :   {
     256           0 :   case 0:
     257           0 :     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     258           0 :     break;
     259          35 :   case MHD_HTTP_OK:
     260          35 :     if (GNUNET_OK !=
     261          35 :         reserve_withdraw_ok (wh,
     262             :                              j))
     263             :     {
     264           0 :       GNUNET_break_op (0);
     265           0 :       hr.http_status = 0;
     266           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     267           0 :       break;
     268             :     }
     269          35 :     GNUNET_assert (NULL == wh->cb);
     270          35 :     TALER_EXCHANGE_withdraw2_cancel (wh);
     271          35 :     return;
     272           0 :   case MHD_HTTP_BAD_REQUEST:
     273             :     /* This should never happen, either us or the exchange is buggy
     274             :        (or API version conflict); just pass JSON reply to the application */
     275           0 :     hr.ec = TALER_JSON_get_error_code (j);
     276           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     277           0 :     break;
     278           0 :   case MHD_HTTP_FORBIDDEN:
     279           0 :     GNUNET_break_op (0);
     280             :     /* Nothing really to verify, exchange says one of the signatures is
     281             :        invalid; as we checked them, this should never happen, we
     282             :        should pass the JSON reply to the application */
     283           0 :     hr.ec = TALER_JSON_get_error_code (j);
     284           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     285           0 :     break;
     286           0 :   case MHD_HTTP_NOT_FOUND:
     287             :     /* Nothing really to verify, the exchange basically just says
     288             :        that it doesn't know this reserve.  Can happen if we
     289             :        query before the wire transfer went through.
     290             :        We should simply pass the JSON reply to the application. */
     291           0 :     hr.ec = TALER_JSON_get_error_code (j);
     292           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     293           0 :     break;
     294           3 :   case MHD_HTTP_CONFLICT:
     295             :     /* The exchange says that the reserve has insufficient funds;
     296             :        check the signatures in the history... */
     297           3 :     if (GNUNET_OK !=
     298           3 :         reserve_withdraw_payment_required (wh,
     299             :                                            j))
     300             :     {
     301           0 :       GNUNET_break_op (0);
     302           0 :       hr.http_status = 0;
     303           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     304             :     }
     305             :     else
     306             :     {
     307           3 :       hr.ec = TALER_JSON_get_error_code (j);
     308           3 :       hr.hint = TALER_JSON_get_error_hint (j);
     309             :     }
     310           3 :     break;
     311           1 :   case MHD_HTTP_GONE:
     312             :     /* could happen if denomination was revoked */
     313             :     /* Note: one might want to check /keys for revocation
     314             :        signature here, alas tricky in case our /keys
     315             :        is outdated => left to clients */
     316           1 :     hr.ec = TALER_JSON_get_error_code (j);
     317           1 :     hr.hint = TALER_JSON_get_error_hint (j);
     318           1 :     break;
     319           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     320             :     /* Server had an internal issue; we should retry, but this API
     321             :        leaves this to the application */
     322           0 :     hr.ec = TALER_JSON_get_error_code (j);
     323           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     324           0 :     break;
     325           0 :   default:
     326             :     /* unexpected response code */
     327           0 :     GNUNET_break_op (0);
     328           0 :     hr.ec = TALER_JSON_get_error_code (j);
     329           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     330           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     331             :                 "Unexpected response code %u/%d for exchange withdraw\n",
     332             :                 (unsigned int) response_code,
     333             :                 (int) hr.ec);
     334           0 :     break;
     335             :   }
     336           4 :   if (NULL != wh->cb)
     337             :   {
     338           4 :     wh->cb (wh->cb_cls,
     339             :             &hr,
     340             :             NULL);
     341           4 :     wh->cb = NULL;
     342             :   }
     343           4 :   TALER_EXCHANGE_withdraw2_cancel (wh);
     344             : }
     345             : 
     346             : 
     347             : struct TALER_EXCHANGE_Withdraw2Handle *
     348          39 : TALER_EXCHANGE_withdraw2 (
     349             :   struct TALER_EXCHANGE_Handle *exchange,
     350             :   const struct TALER_PlanchetDetail *pd,
     351             :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     352             :   TALER_EXCHANGE_Withdraw2Callback res_cb,
     353             :   void *res_cb_cls)
     354             : {
     355             :   struct TALER_EXCHANGE_Withdraw2Handle *wh;
     356             :   const struct TALER_EXCHANGE_Keys *keys;
     357             :   const struct TALER_EXCHANGE_DenomPublicKey *dk;
     358             :   struct TALER_ReserveSignatureP reserve_sig;
     359             :   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
     360             : 
     361          39 :   keys = TALER_EXCHANGE_get_keys (exchange);
     362          39 :   if (NULL == keys)
     363             :   {
     364           0 :     GNUNET_break (0);
     365           0 :     return NULL;
     366             :   }
     367          39 :   dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
     368             :                                                     &pd->denom_pub_hash);
     369          39 :   if (NULL == dk)
     370             :   {
     371           0 :     GNUNET_break (0);
     372           0 :     return NULL;
     373             :   }
     374          39 :   wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle);
     375          39 :   wh->exchange = exchange;
     376          39 :   wh->cb = res_cb;
     377          39 :   wh->cb_cls = res_cb_cls;
     378             :   /* Compute how much we expected to charge to the reserve */
     379          39 :   if (0 >
     380          39 :       TALER_amount_add (&wh->requested_amount,
     381             :                         &dk->value,
     382             :                         &dk->fee_withdraw))
     383             :   {
     384             :     /* Overflow here? Very strange, our CPU must be fried... */
     385           0 :     GNUNET_break (0);
     386           0 :     GNUNET_free (wh);
     387           0 :     return NULL;
     388             :   }
     389             : 
     390          39 :   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
     391             :                                       &wh->reserve_pub.eddsa_pub);
     392             : 
     393             :   {
     394             :     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
     395             :     char *end;
     396             : 
     397          39 :     end = GNUNET_STRINGS_data_to_string (
     398          39 :       &wh->reserve_pub,
     399             :       sizeof (struct TALER_ReservePublicKeyP),
     400             :       pub_str,
     401             :       sizeof (pub_str));
     402          39 :     *end = '\0';
     403          39 :     GNUNET_snprintf (arg_str,
     404             :                      sizeof (arg_str),
     405             :                      "/reserves/%s/withdraw",
     406             :                      pub_str);
     407             :   }
     408             :   {
     409          39 :     struct TALER_WithdrawRequestPS req = {
     410          39 :       .purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)),
     411          39 :       .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
     412             :       .reserve_pub = wh->reserve_pub,
     413             :       .h_denomination_pub = pd->denom_pub_hash
     414             :     };
     415             : 
     416          39 :     TALER_amount_hton (&req.amount_with_fee,
     417          39 :                        &wh->requested_amount);
     418          39 :     GNUNET_CRYPTO_hash (pd->coin_ev,
     419             :                         pd->coin_ev_size,
     420             :                         &req.h_coin_envelope);
     421          39 :     GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
     422             :                               &req,
     423             :                               &reserve_sig.eddsa_signature);
     424             :   }
     425             : 
     426             :   {
     427             :     json_t *withdraw_obj;
     428             : 
     429          39 :     withdraw_obj = GNUNET_JSON_PACK (
     430             :       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
     431             :                                   &pd->denom_pub_hash),
     432             :       GNUNET_JSON_pack_data_varsize ("coin_ev",
     433             :                                      pd->coin_ev,
     434             :                                      pd->coin_ev_size),
     435             :       GNUNET_JSON_pack_data_auto ("reserve_sig",
     436             :                                   &reserve_sig));
     437          39 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     438             :                 "Attempting to withdraw from reserve %s\n",
     439             :                 TALER_B2S (&wh->reserve_pub));
     440          39 :     wh->url = TEAH_path_to_url (exchange,
     441             :                                 arg_str);
     442          39 :     if (NULL == wh->url)
     443             :     {
     444           0 :       json_decref (withdraw_obj);
     445           0 :       GNUNET_free (wh);
     446           0 :       return NULL;
     447             :     }
     448             :     {
     449             :       CURL *eh;
     450             :       struct GNUNET_CURL_Context *ctx;
     451             : 
     452          39 :       ctx = TEAH_handle_to_context (exchange);
     453          39 :       eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
     454          78 :       if ( (NULL == eh) ||
     455             :            (GNUNET_OK !=
     456          39 :             TALER_curl_easy_post (&wh->post_ctx,
     457             :                                   eh,
     458             :                                   withdraw_obj)) )
     459             :       {
     460           0 :         GNUNET_break (0);
     461           0 :         if (NULL != eh)
     462           0 :           curl_easy_cleanup (eh);
     463           0 :         json_decref (withdraw_obj);
     464           0 :         GNUNET_free (wh->url);
     465           0 :         GNUNET_free (wh);
     466           0 :         return NULL;
     467             :       }
     468          39 :       json_decref (withdraw_obj);
     469          78 :       wh->job = GNUNET_CURL_job_add2 (ctx,
     470             :                                       eh,
     471          39 :                                       wh->post_ctx.headers,
     472             :                                       &handle_reserve_withdraw_finished,
     473             :                                       wh);
     474             :     }
     475             :   }
     476          39 :   return wh;
     477             : }
     478             : 
     479             : 
     480             : void
     481          39 : TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh)
     482             : {
     483          39 :   if (NULL != wh->job)
     484             :   {
     485           0 :     GNUNET_CURL_job_cancel (wh->job);
     486           0 :     wh->job = NULL;
     487             :   }
     488          39 :   GNUNET_free (wh->url);
     489          39 :   TALER_curl_easy_post_finished (&wh->post_ctx);
     490          39 :   GNUNET_free (wh);
     491          39 : }

Generated by: LCOV version 1.14