LCOV - code coverage report
Current view: top level - lib - exchange_api_recoup.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 91 152 59.9 %
Date: 2021-08-30 06:43:37 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2017-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_recoup.c
      19             :  * @brief Implementation of the /recoup request of the exchange's HTTP API
      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_json_lib.h"
      29             : #include "taler_exchange_service.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 Recoup Handle
      37             :  */
      38             : struct TALER_EXCHANGE_RecoupHandle
      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             :    * Context for #TEH_curl_easy_post(). Keeps the data that must
      53             :    * persist for Curl to make the upload.
      54             :    */
      55             :   struct TALER_CURL_PostContext ctx;
      56             : 
      57             :   /**
      58             :    * Denomination key of the coin.
      59             :    */
      60             :   struct TALER_EXCHANGE_DenomPublicKey pk;
      61             : 
      62             :   /**
      63             :    * Handle for the request.
      64             :    */
      65             :   struct GNUNET_CURL_Job *job;
      66             : 
      67             :   /**
      68             :    * Function to call with the result.
      69             :    */
      70             :   TALER_EXCHANGE_RecoupResultCallback cb;
      71             : 
      72             :   /**
      73             :    * Closure for @a cb.
      74             :    */
      75             :   void *cb_cls;
      76             : 
      77             :   /**
      78             :    * Public key of the coin we are trying to get paid back.
      79             :    */
      80             :   struct TALER_CoinSpendPublicKeyP coin_pub;
      81             : 
      82             :   /**
      83             :    * #GNUNET_YES if the coin was refreshed
      84             :    */
      85             :   int was_refreshed;
      86             : 
      87             : };
      88             : 
      89             : 
      90             : /**
      91             :  * Parse a recoup response.  If it is valid, call the callback.
      92             :  *
      93             :  * @param ph recoup handle
      94             :  * @param json json reply with the signature
      95             :  * @return #GNUNET_OK if the signature is valid and we called the callback;
      96             :  *         #GNUNET_SYSERR if not (callback must still be called)
      97             :  */
      98             : static int
      99          15 : process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
     100             :                          const json_t *json)
     101             : {
     102             :   int refreshed;
     103             :   struct TALER_ReservePublicKeyP reserve_pub;
     104             :   struct TALER_CoinSpendPublicKeyP old_coin_pub;
     105             :   struct GNUNET_JSON_Specification spec_withdraw[] = {
     106          15 :     GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
     107          15 :     GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
     108          15 :     GNUNET_JSON_spec_end ()
     109             :   };
     110             :   struct GNUNET_JSON_Specification spec_refresh[] = {
     111          15 :     GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
     112          15 :     GNUNET_JSON_spec_fixed_auto ("old_coin_pub", &old_coin_pub),
     113          15 :     GNUNET_JSON_spec_end ()
     114             :   };
     115          15 :   struct TALER_EXCHANGE_HttpResponse hr = {
     116             :     .reply = json,
     117             :     .http_status = MHD_HTTP_OK
     118             :   };
     119             : 
     120          15 :   if (GNUNET_OK !=
     121          15 :       GNUNET_JSON_parse (json,
     122          15 :                          ph->was_refreshed ? spec_refresh : spec_withdraw,
     123             :                          NULL, NULL))
     124             :   {
     125           0 :     GNUNET_break_op (0);
     126           0 :     return GNUNET_SYSERR;
     127             :   }
     128          15 :   if (ph->was_refreshed != refreshed)
     129             :   {
     130           0 :     GNUNET_break_op (0);
     131           0 :     return GNUNET_SYSERR;
     132             :   }
     133          30 :   ph->cb (ph->cb_cls,
     134             :           &hr,
     135          15 :           ph->was_refreshed ? NULL : &reserve_pub,
     136          15 :           ph->was_refreshed ? &old_coin_pub : NULL);
     137          15 :   return GNUNET_OK;
     138             : }
     139             : 
     140             : 
     141             : /**
     142             :  * Function called when we're done processing the
     143             :  * HTTP /recoup request.
     144             :  *
     145             :  * @param cls the `struct TALER_EXCHANGE_RecoupHandle`
     146             :  * @param response_code HTTP response code, 0 on error
     147             :  * @param response parsed JSON result, NULL on error
     148             :  */
     149             : static void
     150          18 : handle_recoup_finished (void *cls,
     151             :                         long response_code,
     152             :                         const void *response)
     153             : {
     154          18 :   struct TALER_EXCHANGE_RecoupHandle *ph = cls;
     155          18 :   const json_t *j = response;
     156          18 :   struct TALER_EXCHANGE_HttpResponse hr = {
     157             :     .reply = j,
     158          18 :     .http_status = (unsigned int) response_code
     159             :   };
     160             : 
     161          18 :   ph->job = NULL;
     162          18 :   switch (response_code)
     163             :   {
     164           0 :   case 0:
     165           0 :     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     166           0 :     break;
     167          15 :   case MHD_HTTP_OK:
     168          15 :     if (GNUNET_OK !=
     169          15 :         process_recoup_response (ph,
     170             :                                  j))
     171             :     {
     172           0 :       GNUNET_break_op (0);
     173           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     174           0 :       hr.http_status = 0;
     175           0 :       break;
     176             :     }
     177          15 :     TALER_EXCHANGE_recoup_cancel (ph);
     178          17 :     return;
     179           0 :   case MHD_HTTP_BAD_REQUEST:
     180             :     /* This should never happen, either us or the exchange is buggy
     181             :        (or API version conflict); just pass JSON reply to the application */
     182           0 :     hr.ec = TALER_JSON_get_error_code (j);
     183           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     184           0 :     break;
     185           2 :   case MHD_HTTP_CONFLICT:
     186             :     {
     187             :       /* Insufficient funds, proof attached */
     188             :       json_t *history;
     189             :       struct TALER_Amount total;
     190             :       struct GNUNET_HashCode h_denom_pub;
     191             :       const struct TALER_EXCHANGE_DenomPublicKey *dki;
     192             :       enum TALER_ErrorCode ec;
     193             : 
     194           2 :       dki = &ph->pk;
     195           2 :       history = json_object_get (j,
     196             :                                  "history");
     197           2 :       if (GNUNET_OK !=
     198           2 :           TALER_EXCHANGE_verify_coin_history (dki,
     199           2 :                                               dki->fee_deposit.currency,
     200           2 :                                               &ph->coin_pub,
     201             :                                               history,
     202             :                                               &h_denom_pub,
     203             :                                               &total))
     204             :       {
     205           0 :         GNUNET_break_op (0);
     206           0 :         hr.http_status = 0;
     207           0 :         hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     208             :       }
     209             :       else
     210             :       {
     211           2 :         hr.ec = TALER_JSON_get_error_code (j);
     212           2 :         hr.hint = TALER_JSON_get_error_hint (j);
     213             :       }
     214           2 :       ec = TALER_JSON_get_error_code (j);
     215             :       switch (ec)
     216             :       {
     217           1 :       case TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO:
     218           1 :         if (0 > TALER_amount_cmp (&total,
     219             :                                   &dki->value))
     220             :         {
     221             :           /* recoup MAY have still been possible */
     222             :           /* FIXME: This code may falsely complain, as we do not
     223             :              know that the smallest denomination offered by the
     224             :              exchange is here. We should look at the key
     225             :              structure of ph->exchange, and find the smallest
     226             :              _currently withdrawable_ denomination and check
     227             :              if the value remaining would suffice... *///
     228           0 :           GNUNET_break_op (0);
     229           0 :           hr.http_status = 0;
     230           0 :           hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     231           0 :           break;
     232             :         }
     233           1 :         break;
     234           1 :       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
     235           1 :         if (0 == GNUNET_memcmp (&ph->pk.h_key,
     236             :                                 &h_denom_pub))
     237             :         {
     238             :           /* invalid proof provided */
     239           0 :           GNUNET_break_op (0);
     240           0 :           hr.http_status = 0;
     241           0 :           hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     242           0 :           break;
     243             :         }
     244             :         /* valid error from exchange */
     245           1 :         break;
     246           0 :       default:
     247           0 :         GNUNET_break_op (0);
     248           0 :         hr.http_status = 0;
     249           0 :         hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     250           0 :         break;
     251             :       }
     252           2 :       ph->cb (ph->cb_cls,
     253             :               &hr,
     254             :               NULL,
     255             :               NULL);
     256           2 :       TALER_EXCHANGE_recoup_cancel (ph);
     257           2 :       return;
     258             :     }
     259           0 :   case MHD_HTTP_FORBIDDEN:
     260             :     /* Nothing really to verify, exchange says one of the signatures is
     261             :        invalid; as we checked them, this should never happen, we
     262             :        should pass the JSON reply to the application */
     263           0 :     hr.ec = TALER_JSON_get_error_code (j);
     264           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     265           0 :     break;
     266           0 :   case MHD_HTTP_NOT_FOUND:
     267             :     /* Nothing really to verify, this should never
     268             :        happen, we should pass the JSON reply to the application */
     269           0 :     hr.ec = TALER_JSON_get_error_code (j);
     270           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     271           0 :     break;
     272           1 :   case MHD_HTTP_GONE:
     273             :     /* Kind of normal: the money was already sent to the merchant
     274             :        (it was too late for the refund). */
     275           1 :     hr.ec = TALER_JSON_get_error_code (j);
     276           1 :     hr.hint = TALER_JSON_get_error_hint (j);
     277           1 :     break;
     278           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     279             :     /* Server had an internal issue; we should retry, but this API
     280             :        leaves this to the application */
     281           0 :     hr.ec = TALER_JSON_get_error_code (j);
     282           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     283           0 :     break;
     284           0 :   default:
     285             :     /* unexpected response code */
     286           0 :     hr.ec = TALER_JSON_get_error_code (j);
     287           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     288           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     289             :                 "Unexpected response code %u/%d for exchange recoup\n",
     290             :                 (unsigned int) response_code,
     291             :                 (int) hr.ec);
     292           0 :     GNUNET_break (0);
     293           0 :     break;
     294             :   }
     295           1 :   ph->cb (ph->cb_cls,
     296             :           &hr,
     297             :           NULL,
     298             :           NULL);
     299           1 :   TALER_EXCHANGE_recoup_cancel (ph);
     300             : }
     301             : 
     302             : 
     303             : struct TALER_EXCHANGE_RecoupHandle *
     304          18 : TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
     305             :                        const struct TALER_EXCHANGE_DenomPublicKey *pk,
     306             :                        const struct TALER_DenominationSignature *denom_sig,
     307             :                        const struct TALER_PlanchetSecretsP *ps,
     308             :                        bool was_refreshed,
     309             :                        TALER_EXCHANGE_RecoupResultCallback recoup_cb,
     310             :                        void *recoup_cb_cls)
     311             : {
     312             :   struct TALER_EXCHANGE_RecoupHandle *ph;
     313             :   struct GNUNET_CURL_Context *ctx;
     314             :   struct TALER_RecoupRequestPS pr;
     315             :   struct TALER_CoinSpendSignatureP coin_sig;
     316             :   struct GNUNET_HashCode h_denom_pub;
     317             :   json_t *recoup_obj;
     318             :   CURL *eh;
     319             :   char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
     320             : 
     321          18 :   GNUNET_assert (GNUNET_YES ==
     322             :                  TEAH_handle_is_ready (exchange));
     323          18 :   pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP);
     324          18 :   pr.purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS));
     325          18 :   GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
     326             :                                       &pr.coin_pub.eddsa_pub);
     327          18 :   GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
     328             :                                      &h_denom_pub);
     329          18 :   pr.h_denom_pub = pk->h_key;
     330          18 :   pr.coin_blind = ps->blinding_key;
     331          18 :   GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv,
     332             :                             &pr,
     333             :                             &coin_sig.eddsa_signature);
     334          18 :   recoup_obj = GNUNET_JSON_PACK (
     335             :     GNUNET_JSON_pack_data_auto ("denom_pub_hash",
     336             :                                 &h_denom_pub),
     337             :     TALER_JSON_pack_denomination_signature ("denom_sig",
     338             :                                             denom_sig),
     339             :     GNUNET_JSON_pack_data_auto ("coin_sig",
     340             :                                 &coin_sig),
     341             :     GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
     342             :                                 &ps->blinding_key),
     343             :     GNUNET_JSON_pack_bool ("refreshed",
     344             :                            was_refreshed));
     345             :   {
     346             :     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
     347             :     char *end;
     348             : 
     349          18 :     end = GNUNET_STRINGS_data_to_string (&pr.coin_pub,
     350             :                                          sizeof (struct
     351             :                                                  TALER_CoinSpendPublicKeyP),
     352             :                                          pub_str,
     353             :                                          sizeof (pub_str));
     354          18 :     *end = '\0';
     355          18 :     GNUNET_snprintf (arg_str,
     356             :                      sizeof (arg_str),
     357             :                      "/coins/%s/recoup",
     358             :                      pub_str);
     359             :   }
     360             : 
     361          18 :   ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
     362          18 :   ph->coin_pub = pr.coin_pub;
     363          18 :   ph->exchange = exchange;
     364          18 :   ph->pk = *pk;
     365          18 :   ph->pk.key.rsa_public_key = NULL; /* zero out, as lifetime cannot be warranted */
     366          18 :   ph->cb = recoup_cb;
     367          18 :   ph->cb_cls = recoup_cb_cls;
     368          18 :   ph->url = TEAH_path_to_url (exchange,
     369             :                               arg_str);
     370          18 :   if (NULL == ph->url)
     371             :   {
     372           0 :     json_decref (recoup_obj);
     373           0 :     GNUNET_free (ph);
     374           0 :     return NULL;
     375             :   }
     376          18 :   ph->was_refreshed = was_refreshed;
     377          18 :   eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
     378          36 :   if ( (NULL == eh) ||
     379             :        (GNUNET_OK !=
     380          18 :         TALER_curl_easy_post (&ph->ctx,
     381             :                               eh,
     382             :                               recoup_obj)) )
     383             :   {
     384           0 :     GNUNET_break (0);
     385           0 :     if (NULL != eh)
     386           0 :       curl_easy_cleanup (eh);
     387           0 :     json_decref (recoup_obj);
     388           0 :     GNUNET_free (ph->url);
     389           0 :     GNUNET_free (ph);
     390           0 :     return NULL;
     391             :   }
     392          18 :   json_decref (recoup_obj);
     393          18 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     394             :               "URL for recoup: `%s'\n",
     395             :               ph->url);
     396          18 :   ctx = TEAH_handle_to_context (exchange);
     397          36 :   ph->job = GNUNET_CURL_job_add2 (ctx,
     398             :                                   eh,
     399          18 :                                   ph->ctx.headers,
     400             :                                   &handle_recoup_finished,
     401             :                                   ph);
     402          18 :   return ph;
     403             : }
     404             : 
     405             : 
     406             : void
     407          18 : TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph)
     408             : {
     409          18 :   if (NULL != ph->job)
     410             :   {
     411           0 :     GNUNET_CURL_job_cancel (ph->job);
     412           0 :     ph->job = NULL;
     413             :   }
     414          18 :   GNUNET_free (ph->url);
     415          18 :   TALER_curl_easy_post_finished (&ph->ctx);
     416          18 :   GNUNET_free (ph);
     417          18 : }
     418             : 
     419             : 
     420             : /* end of exchange_api_recoup.c */

Generated by: LCOV version 1.14