LCOV - code coverage report
Current view: top level - lib - exchange_api_link.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 107 174 61.5 %
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) 2015-2020 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_link.c
      19             :  * @brief Implementation of the /coins/$COIN_PUB/link request
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <microhttpd.h> /* just for HTTP status codes */
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <gnunet/gnunet_curl_lib.h>
      26             : #include "taler_exchange_service.h"
      27             : #include "taler_json_lib.h"
      28             : #include "exchange_api_handle.h"
      29             : #include "taler_signatures.h"
      30             : #include "exchange_api_curl_defaults.h"
      31             : 
      32             : 
      33             : /**
      34             :  * @brief A /coins/$COIN_PUB/link Handle
      35             :  */
      36             : struct TALER_EXCHANGE_LinkHandle
      37             : {
      38             : 
      39             :   /**
      40             :    * The connection to exchange this request handle will use
      41             :    */
      42             :   struct TALER_EXCHANGE_Handle *exchange;
      43             : 
      44             :   /**
      45             :    * The url for this request.
      46             :    */
      47             :   char *url;
      48             : 
      49             :   /**
      50             :    * Handle for the request.
      51             :    */
      52             :   struct GNUNET_CURL_Job *job;
      53             : 
      54             :   /**
      55             :    * Function to call with the result.
      56             :    */
      57             :   TALER_EXCHANGE_LinkCallback link_cb;
      58             : 
      59             :   /**
      60             :    * Closure for @e cb.
      61             :    */
      62             :   void *link_cb_cls;
      63             : 
      64             :   /**
      65             :    * Private key of the coin, required to decode link information.
      66             :    */
      67             :   struct TALER_CoinSpendPrivateKeyP coin_priv;
      68             : 
      69             : };
      70             : 
      71             : 
      72             : /**
      73             :  * Parse the provided linkage data from the "200 OK" response
      74             :  * for one of the coins.
      75             :  *
      76             :  * @param lh link handle
      77             :  * @param json json reply with the data for one coin
      78             :  * @param coin_num number of the coin
      79             :  * @param trans_pub our transfer public key
      80             :  * @param[out] coin_priv where to return private coin key
      81             :  * @param[out] sig where to return private coin signature
      82             :  * @param[out] pub where to return the public key for the coin
      83             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
      84             :  */
      85             : static int
      86           4 : parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
      87             :                  const json_t *json,
      88             :                  uint32_t coin_num,
      89             :                  const struct TALER_TransferPublicKeyP *trans_pub,
      90             :                  struct TALER_CoinSpendPrivateKeyP *coin_priv,
      91             :                  struct TALER_DenominationSignature *sig,
      92             :                  struct TALER_DenominationPublicKey *pub)
      93             : {
      94             :   struct GNUNET_CRYPTO_RsaSignature *bsig;
      95             :   struct GNUNET_CRYPTO_RsaPublicKey *rpub;
      96             :   struct TALER_CoinSpendSignatureP link_sig;
      97             :   struct GNUNET_JSON_Specification spec[] = {
      98           4 :     GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub),
      99           4 :     GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig),
     100           4 :     GNUNET_JSON_spec_fixed_auto ("link_sig", &link_sig),
     101           4 :     GNUNET_JSON_spec_end ()
     102             :   };
     103             :   struct TALER_TransferSecretP secret;
     104             :   struct TALER_PlanchetSecretsP fc;
     105             : 
     106             :   /* parse reply */
     107           4 :   if (GNUNET_OK !=
     108           4 :       GNUNET_JSON_parse (json,
     109             :                          spec,
     110             :                          NULL, NULL))
     111             :   {
     112           0 :     GNUNET_break_op (0);
     113           0 :     return GNUNET_SYSERR;
     114             :   }
     115           4 :   TALER_link_recover_transfer_secret (trans_pub,
     116             :                                       &lh->coin_priv,
     117             :                                       &secret);
     118           4 :   TALER_planchet_setup_refresh (&secret,
     119             :                                 coin_num,
     120             :                                 &fc);
     121             : 
     122             :   /* extract coin and signature */
     123           4 :   *coin_priv = fc.coin_priv;
     124             :   sig->rsa_signature
     125           4 :     = TALER_rsa_unblind (bsig,
     126             :                          &fc.blinding_key.bks,
     127             :                          rpub);
     128             :   /* verify link_sig */
     129             :   {
     130             :     struct TALER_PlanchetDetail pd;
     131             :     struct GNUNET_HashCode c_hash;
     132             :     struct TALER_CoinSpendPublicKeyP old_coin_pub;
     133             : 
     134           4 :     GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
     135             :                                         &old_coin_pub.eddsa_pub);
     136           4 :     pub->rsa_public_key = rpub;
     137           4 :     if (GNUNET_OK !=
     138           4 :         TALER_planchet_prepare (pub,
     139             :                                 &fc,
     140             :                                 &c_hash,
     141             :                                 &pd))
     142             :     {
     143           0 :       GNUNET_break (0);
     144           0 :       GNUNET_JSON_parse_free (spec);
     145           0 :       return GNUNET_SYSERR;
     146             :     }
     147           4 :     if (GNUNET_OK !=
     148           4 :         TALER_wallet_link_verify (&pd.denom_pub_hash,
     149             :                                   trans_pub,
     150           4 :                                   pd.coin_ev,
     151             :                                   pd.coin_ev_size,
     152             :                                   &old_coin_pub,
     153             :                                   &link_sig))
     154             :     {
     155           0 :       GNUNET_break_op (0);
     156           0 :       GNUNET_free (pd.coin_ev);
     157           0 :       GNUNET_JSON_parse_free (spec);
     158           0 :       return GNUNET_SYSERR;
     159             :     }
     160           4 :     GNUNET_free (pd.coin_ev);
     161             :   }
     162             : 
     163             :   /* clean up */
     164           4 :   pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub);
     165           4 :   GNUNET_JSON_parse_free (spec);
     166           4 :   return GNUNET_OK;
     167             : }
     168             : 
     169             : 
     170             : /**
     171             :  * Parse the provided linkage data from the "200 OK" response
     172             :  * for one of the coins.
     173             :  *
     174             :  * @param[in,out] lh link handle (callback may be zero'ed out)
     175             :  * @param json json reply with the data for one coin
     176             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     177             :  */
     178             : static int
     179           1 : parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
     180             :                const json_t *json)
     181             : {
     182             :   unsigned int session;
     183             :   unsigned int num_coins;
     184             :   int ret;
     185           1 :   struct TALER_EXCHANGE_HttpResponse hr = {
     186             :     .reply = json,
     187             :     .http_status = MHD_HTTP_OK
     188             :   };
     189             : 
     190           1 :   if (! json_is_array (json))
     191             :   {
     192           0 :     GNUNET_break_op (0);
     193           0 :     return GNUNET_SYSERR;
     194             :   }
     195           1 :   num_coins = 0;
     196             :   /* Theoretically, a coin may have been melted repeatedly
     197             :      into different sessions; so the response is an array
     198             :      which contains information by melting session.  That
     199             :      array contains another array.  However, our API returns
     200             :      a single 1d array, so we flatten the 2d array that is
     201             :      returned into a single array. Note that usually a coin
     202             :      is melted at most once, and so we'll only run this
     203             :      loop once for 'session=0' in most cases.
     204             : 
     205             :      num_coins tracks the size of the 1d array we return,
     206             :      whilst 'i' and 'session' track the 2d array. *///
     207           2 :   for (session = 0; session<json_array_size (json); session++)
     208             :   {
     209             :     json_t *jsona;
     210             :     struct GNUNET_JSON_Specification spec[] = {
     211           1 :       GNUNET_JSON_spec_json ("new_coins", &jsona),
     212           1 :       GNUNET_JSON_spec_end ()
     213             :     };
     214             : 
     215           1 :     if (GNUNET_OK !=
     216           1 :         GNUNET_JSON_parse (json_array_get (json,
     217             :                                            session),
     218             :                            spec,
     219             :                            NULL, NULL))
     220             :     {
     221           0 :       GNUNET_break_op (0);
     222           0 :       return GNUNET_SYSERR;
     223             :     }
     224           1 :     if (! json_is_array (jsona))
     225             :     {
     226           0 :       GNUNET_break_op (0);
     227           0 :       GNUNET_JSON_parse_free (spec);
     228           0 :       return GNUNET_SYSERR;
     229             :     }
     230             : 
     231             :     /* count all coins over all sessions */
     232           1 :     num_coins += json_array_size (jsona);
     233           1 :     GNUNET_JSON_parse_free (spec);
     234             :   }
     235             :   /* Now that we know how big the 1d array is, allocate
     236             :      and fill it. */
     237           1 :   {
     238             :     unsigned int off_coin; /* index into 1d array */
     239             :     unsigned int i;
     240           1 :     struct TALER_CoinSpendPrivateKeyP coin_privs[GNUNET_NZL (num_coins)];
     241           1 :     struct TALER_DenominationSignature sigs[GNUNET_NZL (num_coins)];
     242           1 :     struct TALER_DenominationPublicKey pubs[GNUNET_NZL (num_coins)];
     243             : 
     244           1 :     memset (sigs, 0, sizeof (sigs));
     245           1 :     memset (pubs, 0, sizeof (pubs));
     246           1 :     off_coin = 0;
     247           2 :     for (session = 0; session<json_array_size (json); session++)
     248             :     {
     249             :       json_t *jsona;
     250             :       struct TALER_TransferPublicKeyP trans_pub;
     251             :       struct GNUNET_JSON_Specification spec[] = {
     252           1 :         GNUNET_JSON_spec_json ("new_coins",
     253             :                                &jsona),
     254           1 :         GNUNET_JSON_spec_fixed_auto ("transfer_pub",
     255             :                                      &trans_pub),
     256           1 :         GNUNET_JSON_spec_end ()
     257             :       };
     258             : 
     259           1 :       if (GNUNET_OK !=
     260           1 :           GNUNET_JSON_parse (json_array_get (json,
     261             :                                              session),
     262             :                              spec,
     263             :                              NULL, NULL))
     264             :       {
     265           0 :         GNUNET_break_op (0);
     266           0 :         return GNUNET_SYSERR;
     267             :       }
     268           1 :       if (! json_is_array (jsona))
     269             :       {
     270           0 :         GNUNET_break_op (0);
     271           0 :         GNUNET_JSON_parse_free (spec);
     272           0 :         return GNUNET_SYSERR;
     273             :       }
     274             : 
     275             :       /* decode all coins */
     276           5 :       for (i = 0; i<json_array_size (jsona); i++)
     277             :       {
     278           4 :         GNUNET_assert (i + off_coin < num_coins);
     279           4 :         if (GNUNET_OK !=
     280           4 :             parse_link_coin (lh,
     281           4 :                              json_array_get (jsona,
     282             :                                              i),
     283             :                              i,
     284             :                              &trans_pub,
     285           4 :                              &coin_privs[i + off_coin],
     286           4 :                              &sigs[i + off_coin],
     287           4 :                              &pubs[i + off_coin]))
     288             :         {
     289           0 :           GNUNET_break_op (0);
     290           0 :           break;
     291             :         }
     292             :       }
     293             :       /* check if we really got all, then invoke callback */
     294           1 :       off_coin += i;
     295           1 :       if (i != json_array_size (jsona))
     296             :       {
     297           0 :         GNUNET_break_op (0);
     298           0 :         ret = GNUNET_SYSERR;
     299           0 :         GNUNET_JSON_parse_free (spec);
     300           0 :         break;
     301             :       }
     302           1 :       GNUNET_JSON_parse_free (spec);
     303             :     } /* end of for (session) */
     304             : 
     305           1 :     if (off_coin == num_coins)
     306             :     {
     307           1 :       lh->link_cb (lh->link_cb_cls,
     308             :                    &hr,
     309             :                    num_coins,
     310             :                    coin_privs,
     311             :                    sigs,
     312             :                    pubs);
     313           1 :       lh->link_cb = NULL;
     314           1 :       ret = GNUNET_OK;
     315             :     }
     316             :     else
     317             :     {
     318           0 :       GNUNET_break_op (0);
     319           0 :       ret = GNUNET_SYSERR;
     320             :     }
     321             : 
     322             :     /* clean up */
     323           1 :     GNUNET_assert (off_coin <= num_coins);
     324           5 :     for (i = 0; i<off_coin; i++)
     325             :     {
     326           4 :       if (NULL != sigs[i].rsa_signature)
     327           4 :         GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
     328           4 :       if (NULL != pubs[i].rsa_public_key)
     329           4 :         GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key);
     330             :     }
     331             :   }
     332           1 :   return ret;
     333             : }
     334             : 
     335             : 
     336             : /**
     337             :  * Function called when we're done processing the
     338             :  * HTTP /coins/$COIN_PUB/link request.
     339             :  *
     340             :  * @param cls the `struct TALER_EXCHANGE_LinkHandle`
     341             :  * @param response_code HTTP response code, 0 on error
     342             :  * @param response parsed JSON result, NULL on error
     343             :  */
     344             : static void
     345           1 : handle_link_finished (void *cls,
     346             :                       long response_code,
     347             :                       const void *response)
     348             : {
     349           1 :   struct TALER_EXCHANGE_LinkHandle *lh = cls;
     350           1 :   const json_t *j = response;
     351           1 :   struct TALER_EXCHANGE_HttpResponse hr = {
     352             :     .reply = j,
     353           1 :     .http_status = (unsigned int) response_code
     354             :   };
     355             : 
     356           1 :   lh->job = NULL;
     357           1 :   switch (response_code)
     358             :   {
     359           0 :   case 0:
     360           0 :     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     361           0 :     break;
     362           1 :   case MHD_HTTP_OK:
     363           1 :     if (GNUNET_OK !=
     364           1 :         parse_link_ok (lh,
     365             :                        j))
     366             :     {
     367           0 :       GNUNET_break_op (0);
     368           0 :       hr.http_status = 0;
     369           0 :       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     370           0 :       break;
     371             :     }
     372           1 :     GNUNET_assert (NULL == lh->link_cb);
     373           1 :     TALER_EXCHANGE_link_cancel (lh);
     374           1 :     return;
     375           0 :   case MHD_HTTP_BAD_REQUEST:
     376           0 :     hr.ec = TALER_JSON_get_error_code (j);
     377           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     378             :     /* This should never happen, either us or the exchange is buggy
     379             :        (or API version conflict); just pass JSON reply to the application */
     380           0 :     break;
     381           0 :   case MHD_HTTP_NOT_FOUND:
     382           0 :     hr.ec = TALER_JSON_get_error_code (j);
     383           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     384             :     /* Nothing really to verify, exchange says this coin was not melted; we
     385             :        should pass the JSON reply to the application */
     386           0 :     break;
     387           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     388           0 :     hr.ec = TALER_JSON_get_error_code (j);
     389           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     390             :     /* Server had an internal issue; we should retry, but this API
     391             :        leaves this to the application */
     392           0 :     break;
     393           0 :   default:
     394             :     /* unexpected response code */
     395           0 :     GNUNET_break_op (0);
     396           0 :     hr.ec = TALER_JSON_get_error_code (j);
     397           0 :     hr.hint = TALER_JSON_get_error_hint (j);
     398           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     399             :                 "Unexpected response code %u/%d for exchange link\n",
     400             :                 (unsigned int) response_code,
     401             :                 (int) hr.ec);
     402           0 :     break;
     403             :   }
     404           0 :   if (NULL != lh->link_cb)
     405           0 :     lh->link_cb (lh->link_cb_cls,
     406             :                  &hr,
     407             :                  0,
     408             :                  NULL,
     409             :                  NULL,
     410             :                  NULL);
     411           0 :   TALER_EXCHANGE_link_cancel (lh);
     412             : }
     413             : 
     414             : 
     415             : /**
     416             :  * Submit a link request to the exchange and get the exchange's response.
     417             :  *
     418             :  * This API is typically not used by anyone, it is more a threat against those
     419             :  * trying to receive a funds transfer by abusing the refresh protocol.
     420             :  *
     421             :  * @param exchange the exchange handle; the exchange must be ready to operate
     422             :  * @param coin_priv private key to request link data for
     423             :  * @param link_cb the callback to call with the useful result of the
     424             :  *        refresh operation the @a coin_priv was involved in (if any)
     425             :  * @param link_cb_cls closure for @a link_cb
     426             :  * @return a handle for this request
     427             :  */
     428             : struct TALER_EXCHANGE_LinkHandle *
     429           1 : TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
     430             :                      const struct TALER_CoinSpendPrivateKeyP *coin_priv,
     431             :                      TALER_EXCHANGE_LinkCallback link_cb,
     432             :                      void *link_cb_cls)
     433             : {
     434             :   struct TALER_EXCHANGE_LinkHandle *lh;
     435             :   CURL *eh;
     436             :   struct GNUNET_CURL_Context *ctx;
     437             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     438             :   char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
     439             : 
     440           1 :   if (GNUNET_YES !=
     441           1 :       TEAH_handle_is_ready (exchange))
     442             :   {
     443           0 :     GNUNET_break (0);
     444           0 :     return NULL;
     445             :   }
     446             : 
     447           1 :   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
     448             :                                       &coin_pub.eddsa_pub);
     449             :   {
     450             :     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
     451             :     char *end;
     452             : 
     453           1 :     end = GNUNET_STRINGS_data_to_string (
     454             :       &coin_pub,
     455             :       sizeof (struct TALER_CoinSpendPublicKeyP),
     456             :       pub_str,
     457             :       sizeof (pub_str));
     458           1 :     *end = '\0';
     459           1 :     GNUNET_snprintf (arg_str,
     460             :                      sizeof (arg_str),
     461             :                      "/coins/%s/link",
     462             :                      pub_str);
     463             :   }
     464           1 :   lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
     465           1 :   lh->exchange = exchange;
     466           1 :   lh->link_cb = link_cb;
     467           1 :   lh->link_cb_cls = link_cb_cls;
     468           1 :   lh->coin_priv = *coin_priv;
     469           1 :   lh->url = TEAH_path_to_url (exchange,
     470             :                               arg_str);
     471           1 :   if (NULL == lh->url)
     472             :   {
     473           0 :     GNUNET_free (lh);
     474           0 :     return NULL;
     475             :   }
     476           1 :   eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
     477           1 :   if (NULL == eh)
     478             :   {
     479           0 :     GNUNET_break (0);
     480           0 :     GNUNET_free (lh->url);
     481           0 :     GNUNET_free (lh);
     482           0 :     return NULL;
     483             :   }
     484           1 :   ctx = TEAH_handle_to_context (exchange);
     485           1 :   lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
     486             :                                               eh,
     487             :                                               &handle_link_finished,
     488             :                                               lh);
     489           1 :   return lh;
     490             : }
     491             : 
     492             : 
     493             : /**
     494             :  * Cancel a link request.  This function cannot be used
     495             :  * on a request handle if the callback was already invoked.
     496             :  *
     497             :  * @param lh the link handle
     498             :  */
     499             : void
     500           1 : TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
     501             : {
     502           1 :   if (NULL != lh->job)
     503             :   {
     504           0 :     GNUNET_CURL_job_cancel (lh->job);
     505           0 :     lh->job = NULL;
     506             :   }
     507           1 :   GNUNET_free (lh->url);
     508           1 :   GNUNET_free (lh);
     509           1 : }
     510             : 
     511             : 
     512             : /* end of exchange_api_link.c */

Generated by: LCOV version 1.14