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: 0 192 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 5 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2015-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_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             :    * Age commitment and proof of the original coin, might be NULL.
      71             :    * Required to derive the new age commitment and proof.
      72             :    */
      73             :   const struct TALER_AgeCommitmentProof *age_commitment_proof;
      74             : 
      75             : };
      76             : 
      77             : 
      78             : /**
      79             :  * Parse the provided linkage data from the "200 OK" response
      80             :  * for one of the coins.
      81             :  *
      82             :  * @param lh link handle
      83             :  * @param json json reply with the data for one coin
      84             :  * @param trans_pub our transfer public key
      85             :  * @param[out] lci where to return coin details
      86             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
      87             :  */
      88             : static enum GNUNET_GenericReturnValue
      89           0 : parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
      90             :                  const json_t *json,
      91             :                  const struct TALER_TransferPublicKeyP *trans_pub,
      92             :                  struct TALER_EXCHANGE_LinkedCoinInfo *lci)
      93             : {
      94             :   struct TALER_BlindedDenominationSignature bsig;
      95             :   struct TALER_DenominationPublicKey rpub;
      96             :   struct TALER_CoinSpendSignatureP link_sig;
      97             :   union TALER_DenominationBlindingKeyP bks;
      98             :   struct TALER_ExchangeWithdrawValues alg_values;
      99             :   struct TALER_CsNonce nonce;
     100             :   bool no_nonce;
     101             :   uint32_t coin_idx;
     102             :   struct GNUNET_JSON_Specification spec[] = {
     103           0 :     TALER_JSON_spec_denom_pub ("denom_pub",
     104             :                                &rpub),
     105           0 :     TALER_JSON_spec_blinded_denom_sig ("ev_sig",
     106             :                                        &bsig),
     107           0 :     TALER_JSON_spec_exchange_withdraw_values ("ewv",
     108             :                                               &alg_values),
     109           0 :     GNUNET_JSON_spec_fixed_auto ("link_sig",
     110             :                                  &link_sig),
     111           0 :     GNUNET_JSON_spec_uint32 ("coin_idx",
     112             :                              &coin_idx),
     113           0 :     GNUNET_JSON_spec_mark_optional (
     114             :       GNUNET_JSON_spec_fixed_auto ("cs_nonce",
     115             :                                    &nonce),
     116             :       &no_nonce),
     117           0 :     GNUNET_JSON_spec_end ()
     118             :   };
     119             :   struct TALER_TransferSecretP secret;
     120             :   struct TALER_PlanchetDetail pd;
     121             :   struct TALER_CoinPubHashP c_hash;
     122             : 
     123             :   /* parse reply */
     124           0 :   if (GNUNET_OK !=
     125           0 :       GNUNET_JSON_parse (json,
     126             :                          spec,
     127             :                          NULL, NULL))
     128             :   {
     129           0 :     GNUNET_break_op (0);
     130           0 :     return GNUNET_SYSERR;
     131             :   }
     132           0 :   TALER_link_recover_transfer_secret (trans_pub,
     133             :                                       &lh->coin_priv,
     134             :                                       &secret);
     135           0 :   TALER_transfer_secret_to_planchet_secret (&secret,
     136             :                                             coin_idx,
     137             :                                             &lci->ps);
     138           0 :   TALER_planchet_setup_coin_priv (&lci->ps,
     139             :                                   &alg_values,
     140             :                                   &lci->coin_priv);
     141           0 :   TALER_planchet_blinding_secret_create (&lci->ps,
     142             :                                          &alg_values,
     143             :                                          &bks);
     144             : 
     145           0 :   lci->age_commitment_proof = NULL;
     146           0 :   lci->h_age_commitment = NULL;
     147             : 
     148             :   /* Derive the age commitment and calculate the hash */
     149           0 :   if (NULL != lh->age_commitment_proof)
     150             :   {
     151           0 :     lci->age_commitment_proof = GNUNET_new (struct TALER_AgeCommitmentProof);
     152           0 :     lci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
     153             : 
     154           0 :     GNUNET_assert (GNUNET_OK ==
     155             :                    TALER_age_commitment_derive (
     156             :                      lh->age_commitment_proof,
     157             :                      &secret.key,
     158             :                      lci->age_commitment_proof));
     159             : 
     160           0 :     TALER_age_commitment_hash (
     161           0 :       &(lci->age_commitment_proof->commitment),
     162             :       lci->h_age_commitment);
     163             :   }
     164             : 
     165           0 :   if (GNUNET_OK !=
     166           0 :       TALER_planchet_prepare (&rpub,
     167             :                               &alg_values,
     168             :                               &bks,
     169           0 :                               &lci->coin_priv,
     170           0 :                               lci->h_age_commitment,
     171             :                               &c_hash,
     172             :                               &pd))
     173             :   {
     174           0 :     GNUNET_break (0);
     175           0 :     GNUNET_JSON_parse_free (spec);
     176           0 :     return GNUNET_SYSERR;
     177             :   }
     178           0 :   if (TALER_DENOMINATION_CS == alg_values.cipher)
     179             :   {
     180           0 :     if (no_nonce)
     181             :     {
     182           0 :       GNUNET_break_op (0);
     183           0 :       GNUNET_JSON_parse_free (spec);
     184           0 :       return GNUNET_SYSERR;
     185             :     }
     186           0 :     pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonce;
     187             :   }
     188             :   /* extract coin and signature */
     189           0 :   if (GNUNET_OK !=
     190           0 :       TALER_denom_sig_unblind (&lci->sig,
     191             :                                &bsig,
     192             :                                &bks,
     193             :                                &c_hash,
     194             :                                &alg_values,
     195             :                                &rpub))
     196             :   {
     197           0 :     GNUNET_break_op (0);
     198           0 :     return GNUNET_SYSERR;
     199             :   }
     200             :   /* verify link_sig */
     201             :   {
     202             :     struct TALER_CoinSpendPublicKeyP old_coin_pub;
     203             :     struct TALER_BlindedCoinHashP coin_envelope_hash;
     204             : 
     205           0 :     GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
     206             :                                         &old_coin_pub.eddsa_pub);
     207             : 
     208           0 :     TALER_coin_ev_hash (&pd.blinded_planchet,
     209             :                         &pd.denom_pub_hash,
     210             :                         &coin_envelope_hash);
     211           0 :     if (GNUNET_OK !=
     212           0 :         TALER_wallet_link_verify (&pd.denom_pub_hash,
     213             :                                   trans_pub,
     214             :                                   &coin_envelope_hash,
     215             :                                   &old_coin_pub,
     216             :                                   &link_sig))
     217             :     {
     218           0 :       GNUNET_break_op (0);
     219           0 :       TALER_blinded_planchet_free (&pd.blinded_planchet);
     220           0 :       GNUNET_JSON_parse_free (spec);
     221           0 :       return GNUNET_SYSERR;
     222             :     }
     223           0 :     TALER_blinded_planchet_free (&pd.blinded_planchet);
     224             :   }
     225             : 
     226             :   /* clean up */
     227           0 :   TALER_denom_pub_deep_copy (&lci->pub,
     228             :                              &rpub);
     229           0 :   GNUNET_JSON_parse_free (spec);
     230           0 :   return GNUNET_OK;
     231             : }
     232             : 
     233             : 
     234             : /**
     235             :  * Parse the provided linkage data from the "200 OK" response
     236             :  * for one of the coins.
     237             :  *
     238             :  * @param[in,out] lh link handle (callback may be zero'ed out)
     239             :  * @param json json reply with the data for one coin
     240             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     241             :  */
     242             : static enum GNUNET_GenericReturnValue
     243           0 : parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
     244             :                const json_t *json)
     245             : {
     246             :   unsigned int session;
     247             :   unsigned int num_coins;
     248             :   int ret;
     249           0 :   struct TALER_EXCHANGE_LinkResult lr = {
     250             :     .hr.reply = json,
     251             :     .hr.http_status = MHD_HTTP_OK
     252             :   };
     253             : 
     254           0 :   if (! json_is_array (json))
     255             :   {
     256           0 :     GNUNET_break_op (0);
     257           0 :     return GNUNET_SYSERR;
     258             :   }
     259           0 :   num_coins = 0;
     260             :   /* Theoretically, a coin may have been melted repeatedly
     261             :      into different sessions; so the response is an array
     262             :      which contains information by melting session.  That
     263             :      array contains another array.  However, our API returns
     264             :      a single 1d array, so we flatten the 2d array that is
     265             :      returned into a single array. Note that usually a coin
     266             :      is melted at most once, and so we'll only run this
     267             :      loop once for 'session=0' in most cases.
     268             : 
     269             :      num_coins tracks the size of the 1d array we return,
     270             :      whilst 'i' and 'session' track the 2d array. *///
     271           0 :   for (session = 0; session<json_array_size (json); session++)
     272             :   {
     273             :     json_t *jsona;
     274             :     struct GNUNET_JSON_Specification spec[] = {
     275           0 :       GNUNET_JSON_spec_json ("new_coins", &jsona),
     276           0 :       GNUNET_JSON_spec_end ()
     277             :     };
     278             : 
     279           0 :     if (GNUNET_OK !=
     280           0 :         GNUNET_JSON_parse (json_array_get (json,
     281             :                                            session),
     282             :                            spec,
     283             :                            NULL, NULL))
     284             :     {
     285           0 :       GNUNET_break_op (0);
     286           0 :       return GNUNET_SYSERR;
     287             :     }
     288           0 :     if (! json_is_array (jsona))
     289             :     {
     290           0 :       GNUNET_break_op (0);
     291           0 :       GNUNET_JSON_parse_free (spec);
     292           0 :       return GNUNET_SYSERR;
     293             :     }
     294             : 
     295             :     /* count all coins over all sessions */
     296           0 :     num_coins += json_array_size (jsona);
     297           0 :     GNUNET_JSON_parse_free (spec);
     298             :   }
     299             :   /* Now that we know how big the 1d array is, allocate
     300             :      and fill it. */
     301           0 :   {
     302             :     unsigned int off_coin; /* index into 1d array */
     303             :     unsigned int i;
     304           0 :     struct TALER_EXCHANGE_LinkedCoinInfo lcis[GNUNET_NZL (num_coins)];
     305             : 
     306           0 :     memset (lcis, 0, sizeof (lcis));
     307           0 :     off_coin = 0;
     308           0 :     for (session = 0; session<json_array_size (json); session++)
     309             :     {
     310             :       json_t *jsona;
     311             :       struct TALER_TransferPublicKeyP trans_pub;
     312             :       struct GNUNET_JSON_Specification spec[] = {
     313           0 :         GNUNET_JSON_spec_json ("new_coins",
     314             :                                &jsona),
     315           0 :         GNUNET_JSON_spec_fixed_auto ("transfer_pub",
     316             :                                      &trans_pub),
     317           0 :         GNUNET_JSON_spec_end ()
     318             :       };
     319             : 
     320           0 :       if (GNUNET_OK !=
     321           0 :           GNUNET_JSON_parse (json_array_get (json,
     322             :                                              session),
     323             :                              spec,
     324             :                              NULL, NULL))
     325             :       {
     326           0 :         GNUNET_break_op (0);
     327           0 :         return GNUNET_SYSERR;
     328             :       }
     329           0 :       if (! json_is_array (jsona))
     330             :       {
     331           0 :         GNUNET_break_op (0);
     332           0 :         GNUNET_JSON_parse_free (spec);
     333           0 :         return GNUNET_SYSERR;
     334             :       }
     335             : 
     336             :       /* decode all coins */
     337           0 :       for (i = 0; i<json_array_size (jsona); i++)
     338             :       {
     339             :         struct TALER_EXCHANGE_LinkedCoinInfo *lci;
     340             : 
     341           0 :         lci = &lcis[i + off_coin];
     342           0 :         GNUNET_assert (i + off_coin < num_coins);
     343           0 :         if (GNUNET_OK !=
     344           0 :             parse_link_coin (lh,
     345           0 :                              json_array_get (jsona,
     346             :                                              i),
     347             :                              &trans_pub,
     348             :                              lci))
     349             :         {
     350           0 :           GNUNET_break_op (0);
     351           0 :           break;
     352             :         }
     353             :       }
     354             :       /* check if we really got all, then invoke callback */
     355           0 :       off_coin += i;
     356           0 :       if (i != json_array_size (jsona))
     357             :       {
     358           0 :         GNUNET_break_op (0);
     359           0 :         ret = GNUNET_SYSERR;
     360           0 :         GNUNET_JSON_parse_free (spec);
     361           0 :         break;
     362             :       }
     363           0 :       GNUNET_JSON_parse_free (spec);
     364             :     } /* end of for (session) */
     365             : 
     366           0 :     if (off_coin == num_coins)
     367             :     {
     368           0 :       lr.details.success.num_coins = num_coins;
     369           0 :       lr.details.success.coins = lcis;
     370           0 :       lh->link_cb (lh->link_cb_cls,
     371             :                    &lr);
     372           0 :       lh->link_cb = NULL;
     373           0 :       ret = GNUNET_OK;
     374             :     }
     375             :     else
     376             :     {
     377           0 :       GNUNET_break_op (0);
     378           0 :       ret = GNUNET_SYSERR;
     379             :     }
     380             : 
     381             :     /* clean up */
     382           0 :     GNUNET_assert (off_coin <= num_coins);
     383           0 :     for (i = 0; i<off_coin; i++)
     384             :     {
     385           0 :       TALER_denom_sig_free (&lcis[i].sig);
     386           0 :       TALER_denom_pub_free (&lcis[i].pub);
     387             :     }
     388             :   }
     389           0 :   return ret;
     390             : }
     391             : 
     392             : 
     393             : /**
     394             :  * Function called when we're done processing the
     395             :  * HTTP /coins/$COIN_PUB/link request.
     396             :  *
     397             :  * @param cls the `struct TALER_EXCHANGE_LinkHandle`
     398             :  * @param response_code HTTP response code, 0 on error
     399             :  * @param response parsed JSON result, NULL on error
     400             :  */
     401             : static void
     402           0 : handle_link_finished (void *cls,
     403             :                       long response_code,
     404             :                       const void *response)
     405             : {
     406           0 :   struct TALER_EXCHANGE_LinkHandle *lh = cls;
     407           0 :   const json_t *j = response;
     408           0 :   struct TALER_EXCHANGE_LinkResult lr = {
     409             :     .hr.reply = j,
     410           0 :     .hr.http_status = (unsigned int) response_code
     411             :   };
     412             : 
     413           0 :   lh->job = NULL;
     414           0 :   switch (response_code)
     415             :   {
     416           0 :   case 0:
     417           0 :     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     418           0 :     break;
     419           0 :   case MHD_HTTP_OK:
     420           0 :     if (GNUNET_OK !=
     421           0 :         parse_link_ok (lh,
     422             :                        j))
     423             :     {
     424           0 :       GNUNET_break_op (0);
     425           0 :       lr.hr.http_status = 0;
     426           0 :       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     427           0 :       break;
     428             :     }
     429           0 :     GNUNET_assert (NULL == lh->link_cb);
     430           0 :     TALER_EXCHANGE_link_cancel (lh);
     431           0 :     return;
     432           0 :   case MHD_HTTP_BAD_REQUEST:
     433           0 :     lr.hr.ec = TALER_JSON_get_error_code (j);
     434           0 :     lr.hr.hint = TALER_JSON_get_error_hint (j);
     435             :     /* This should never happen, either us or the exchange is buggy
     436             :        (or API version conflict); just pass JSON reply to the application */
     437           0 :     break;
     438           0 :   case MHD_HTTP_NOT_FOUND:
     439           0 :     lr.hr.ec = TALER_JSON_get_error_code (j);
     440           0 :     lr.hr.hint = TALER_JSON_get_error_hint (j);
     441             :     /* Nothing really to verify, exchange says this coin was not melted; we
     442             :        should pass the JSON reply to the application */
     443           0 :     break;
     444           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     445           0 :     lr.hr.ec = TALER_JSON_get_error_code (j);
     446           0 :     lr.hr.hint = TALER_JSON_get_error_hint (j);
     447             :     /* Server had an internal issue; we should retry, but this API
     448             :        leaves this to the application */
     449           0 :     break;
     450           0 :   default:
     451             :     /* unexpected response code */
     452           0 :     GNUNET_break_op (0);
     453           0 :     lr.hr.ec = TALER_JSON_get_error_code (j);
     454           0 :     lr.hr.hint = TALER_JSON_get_error_hint (j);
     455           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     456             :                 "Unexpected response code %u/%d for exchange link\n",
     457             :                 (unsigned int) response_code,
     458             :                 (int) lr.hr.ec);
     459           0 :     break;
     460             :   }
     461           0 :   if (NULL != lh->link_cb)
     462           0 :     lh->link_cb (lh->link_cb_cls,
     463             :                  &lr);
     464           0 :   TALER_EXCHANGE_link_cancel (lh);
     465             : }
     466             : 
     467             : 
     468             : struct TALER_EXCHANGE_LinkHandle *
     469           0 : TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
     470             :                      const struct TALER_CoinSpendPrivateKeyP *coin_priv,
     471             :                      const struct
     472             :                      TALER_AgeCommitmentProof *age_commitment_proof,
     473             :                      TALER_EXCHANGE_LinkCallback link_cb,
     474             :                      void *link_cb_cls)
     475             : {
     476             :   struct TALER_EXCHANGE_LinkHandle *lh;
     477             :   CURL *eh;
     478             :   struct GNUNET_CURL_Context *ctx;
     479             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     480             :   char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
     481             : 
     482           0 :   if (GNUNET_YES !=
     483           0 :       TEAH_handle_is_ready (exchange))
     484             :   {
     485           0 :     GNUNET_break (0);
     486           0 :     return NULL;
     487             :   }
     488             : 
     489           0 :   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
     490             :                                       &coin_pub.eddsa_pub);
     491             :   {
     492             :     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
     493             :     char *end;
     494             : 
     495           0 :     end = GNUNET_STRINGS_data_to_string (
     496             :       &coin_pub,
     497             :       sizeof (struct TALER_CoinSpendPublicKeyP),
     498             :       pub_str,
     499             :       sizeof (pub_str));
     500           0 :     *end = '\0';
     501           0 :     GNUNET_snprintf (arg_str,
     502             :                      sizeof (arg_str),
     503             :                      "/coins/%s/link",
     504             :                      pub_str);
     505             :   }
     506           0 :   lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
     507           0 :   lh->exchange = exchange;
     508           0 :   lh->link_cb = link_cb;
     509           0 :   lh->link_cb_cls = link_cb_cls;
     510           0 :   lh->coin_priv = *coin_priv;
     511           0 :   lh->age_commitment_proof = age_commitment_proof;
     512           0 :   lh->url = TEAH_path_to_url (exchange,
     513             :                               arg_str);
     514           0 :   if (NULL == lh->url)
     515             :   {
     516           0 :     GNUNET_free (lh);
     517           0 :     return NULL;
     518             :   }
     519           0 :   eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
     520           0 :   if (NULL == eh)
     521             :   {
     522           0 :     GNUNET_break (0);
     523           0 :     GNUNET_free (lh->url);
     524           0 :     GNUNET_free (lh);
     525           0 :     return NULL;
     526             :   }
     527           0 :   ctx = TEAH_handle_to_context (exchange);
     528           0 :   lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
     529             :                                               eh,
     530             :                                               &handle_link_finished,
     531             :                                               lh);
     532           0 :   return lh;
     533             : }
     534             : 
     535             : 
     536             : void
     537           0 : TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
     538             : {
     539           0 :   if (NULL != lh->job)
     540             :   {
     541           0 :     GNUNET_CURL_job_cancel (lh->job);
     542           0 :     lh->job = NULL;
     543             :   }
     544           0 :   GNUNET_free (lh->url);
     545           0 :   GNUNET_free (lh);
     546           0 : }
     547             : 
     548             : 
     549             : /* end of exchange_api_link.c */

Generated by: LCOV version 1.14