LCOV - code coverage report
Current view: top level - lib - exchange_api_purse_merge.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 0 161 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 3 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :    This file is part of TALER
       3             :    Copyright (C) 2022 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_purse_merge.c
      19             :  * @brief Implementation of the client to merge a purse
      20             :  *        into an account
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "platform.h"
      24             : #include <jansson.h>
      25             : #include <microhttpd.h> /* just for HTTP status codes */
      26             : #include <gnunet/gnunet_util_lib.h>
      27             : #include <gnunet/gnunet_json_lib.h>
      28             : #include <gnunet/gnunet_curl_lib.h>
      29             : #include "taler_json_lib.h"
      30             : #include "taler_exchange_service.h"
      31             : #include "exchange_api_handle.h"
      32             : #include "exchange_api_common.h"
      33             : #include "taler_signatures.h"
      34             : #include "exchange_api_curl_defaults.h"
      35             : 
      36             : 
      37             : /**
      38             :  * @brief A purse merge with deposit handle
      39             :  */
      40             : struct TALER_EXCHANGE_AccountMergeHandle
      41             : {
      42             : 
      43             :   /**
      44             :    * The connection to exchange this request handle will use
      45             :    */
      46             :   struct TALER_EXCHANGE_Handle *exchange;
      47             : 
      48             :   /**
      49             :    * The url for this request.
      50             :    */
      51             :   char *url;
      52             : 
      53             :   /**
      54             :    * Context for #TEH_curl_easy_post(). Keeps the data that must
      55             :    * persist for Curl to make the upload.
      56             :    */
      57             :   struct TALER_CURL_PostContext ctx;
      58             : 
      59             :   /**
      60             :    * Handle for the request.
      61             :    */
      62             :   struct GNUNET_CURL_Job *job;
      63             : 
      64             :   /**
      65             :    * Function to call with the result.
      66             :    */
      67             :   TALER_EXCHANGE_AccountMergeCallback cb;
      68             : 
      69             :   /**
      70             :    * Closure for @a cb.
      71             :    */
      72             :   void *cb_cls;
      73             : 
      74             :   /**
      75             :    * Base URL of the provider hosting the @e reserve_pub.
      76             :    */
      77             :   char *provider_url;
      78             : 
      79             :   /**
      80             :    * Signature for our operation.
      81             :    */
      82             :   struct TALER_PurseMergeSignatureP merge_sig;
      83             : 
      84             :   /**
      85             :    * Expected value in the purse after fees.
      86             :    */
      87             :   struct TALER_Amount purse_value_after_fees;
      88             : 
      89             :   /**
      90             :    * Public key of the reserve public key.
      91             :    */
      92             :   struct TALER_ReservePublicKeyP reserve_pub;
      93             : 
      94             :   /**
      95             :    * Public key of the purse.
      96             :    */
      97             :   struct TALER_PurseContractPublicKeyP purse_pub;
      98             : 
      99             :   /**
     100             :    * Hash over the purse's contrac terms.
     101             :    */
     102             :   struct TALER_PrivateContractHashP h_contract_terms;
     103             : 
     104             :   /**
     105             :    * When does the purse expire.
     106             :    */
     107             :   struct GNUNET_TIME_Timestamp purse_expiration;
     108             : 
     109             :   /**
     110             :    * Our merge key.
     111             :    */
     112             :   struct TALER_PurseMergePrivateKeyP merge_priv;
     113             : 
     114             :   /**
     115             :    * Reserve signature affirming the merge.
     116             :    */
     117             :   struct TALER_ReserveSignatureP reserve_sig;
     118             : 
     119             : };
     120             : 
     121             : 
     122             : /**
     123             :  * Function called when we're done processing the
     124             :  * HTTP /purse/$PID/merge request.
     125             :  *
     126             :  * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
     127             :  * @param response_code HTTP response code, 0 on error
     128             :  * @param response parsed JSON result, NULL on error
     129             :  */
     130             : static void
     131           0 : handle_purse_merge_finished (void *cls,
     132             :                              long response_code,
     133             :                              const void *response)
     134             : {
     135           0 :   struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
     136           0 :   const json_t *j = response;
     137           0 :   struct TALER_EXCHANGE_AccountMergeResponse dr = {
     138             :     .hr.reply = j,
     139           0 :     .hr.http_status = (unsigned int) response_code,
     140           0 :     .reserve_sig = &pch->reserve_sig
     141             :   };
     142             : 
     143           0 :   pch->job = NULL;
     144           0 :   switch (response_code)
     145             :   {
     146           0 :   case 0:
     147           0 :     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     148           0 :     break;
     149           0 :   case MHD_HTTP_OK:
     150             :     {
     151             :       const struct TALER_EXCHANGE_Keys *key_state;
     152             :       struct TALER_Amount total_deposited;
     153             :       struct GNUNET_JSON_Specification spec[] = {
     154           0 :         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     155             :                                      &dr.details.success.exchange_sig),
     156           0 :         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     157             :                                      &dr.details.success.exchange_pub),
     158           0 :         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
     159             :                                     &dr.details.success.etime),
     160           0 :         TALER_JSON_spec_amount ("merge_amount",
     161           0 :                                 pch->purse_value_after_fees.currency,
     162             :                                 &total_deposited),
     163           0 :         GNUNET_JSON_spec_end ()
     164             :       };
     165             : 
     166           0 :       if (GNUNET_OK !=
     167           0 :           GNUNET_JSON_parse (j,
     168             :                              spec,
     169             :                              NULL, NULL))
     170             :       {
     171           0 :         GNUNET_break_op (0);
     172           0 :         dr.hr.http_status = 0;
     173           0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     174           0 :         break;
     175             :       }
     176           0 :       key_state = TALER_EXCHANGE_get_keys (pch->exchange);
     177           0 :       if (GNUNET_OK !=
     178           0 :           TALER_EXCHANGE_test_signing_key (key_state,
     179             :                                            &dr.details.success.exchange_pub))
     180             :       {
     181           0 :         GNUNET_break_op (0);
     182           0 :         dr.hr.http_status = 0;
     183           0 :         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
     184           0 :         break;
     185             :       }
     186           0 :       if (GNUNET_OK !=
     187           0 :           TALER_exchange_online_purse_merged_verify (
     188             :             dr.details.success.etime,
     189             :             pch->purse_expiration,
     190           0 :             &pch->purse_value_after_fees,
     191           0 :             &pch->purse_pub,
     192           0 :             &pch->h_contract_terms,
     193           0 :             &pch->reserve_pub,
     194           0 :             pch->provider_url,
     195             :             &dr.details.success.exchange_pub,
     196             :             &dr.details.success.exchange_sig))
     197             :       {
     198           0 :         GNUNET_break_op (0);
     199           0 :         dr.hr.http_status = 0;
     200           0 :         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
     201           0 :         break;
     202             :       }
     203             :     }
     204           0 :     break;
     205           0 :   case MHD_HTTP_BAD_REQUEST:
     206             :     /* This should never happen, either us or the exchange is buggy
     207             :        (or API version conflict); just pass JSON reply to the application */
     208           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     209           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     210           0 :     break;
     211           0 :   case MHD_HTTP_PAYMENT_REQUIRED:
     212             :     /* purse was not (yet) full */
     213           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     214           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     215           0 :     break;
     216           0 :   case MHD_HTTP_FORBIDDEN:
     217           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     218           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     219             :     /* Nothing really to verify, exchange says one of the signatures is
     220             :        invalid; as we checked them, this should never happen, we
     221             :        should pass the JSON reply to the application */
     222           0 :     break;
     223           0 :   case MHD_HTTP_NOT_FOUND:
     224           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     225           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     226             :     /* Nothing really to verify, this should never
     227             :        happen, we should pass the JSON reply to the application */
     228           0 :     break;
     229           0 :   case MHD_HTTP_CONFLICT:
     230             :     {
     231             :       struct TALER_PurseMergePublicKeyP merge_pub;
     232             : 
     233           0 :       GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
     234             :                                           &merge_pub.eddsa_pub);
     235           0 :       if (GNUNET_OK !=
     236           0 :           TALER_EXCHANGE_check_purse_merge_conflict_ (
     237           0 :             &pch->merge_sig,
     238             :             &merge_pub,
     239           0 :             &pch->purse_pub,
     240           0 :             pch->provider_url,
     241             :             j))
     242             :       {
     243           0 :         GNUNET_break_op (0);
     244           0 :         dr.hr.http_status = 0;
     245           0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     246           0 :         break;
     247             :       }
     248           0 :       break;
     249             :     }
     250             :     break;
     251           0 :   case MHD_HTTP_GONE:
     252             :     /* could happen if denomination was revoked */
     253             :     /* Note: one might want to check /keys for revocation
     254             :        signature here, alas tricky in case our /keys
     255             :        is outdated => left to clients */
     256           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     257           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     258           0 :     break;
     259           0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     260             :     {
     261             :       struct GNUNET_JSON_Specification spec[] = {
     262           0 :         GNUNET_JSON_spec_uint64 (
     263             :           "requirement_row",
     264             :           &dr.details.unavailable_for_legal_reasons.requirement_row),
     265           0 :         GNUNET_JSON_spec_end ()
     266             :       };
     267             : 
     268           0 :       if (GNUNET_OK !=
     269           0 :           GNUNET_JSON_parse (j,
     270             :                              spec,
     271             :                              NULL, NULL))
     272             :       {
     273           0 :         GNUNET_break_op (0);
     274           0 :         dr.hr.http_status = 0;
     275           0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     276           0 :         break;
     277             :       }
     278             :     }
     279           0 :     break;
     280           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     281           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     282           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     283             :     /* Server had an internal issue; we should retry, but this API
     284             :        leaves this to the application */
     285           0 :     break;
     286           0 :   default:
     287             :     /* unexpected response code */
     288           0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     289           0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     290           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     291             :                 "Unexpected response code %u/%d for exchange deposit\n",
     292             :                 (unsigned int) response_code,
     293             :                 dr.hr.ec);
     294           0 :     GNUNET_break_op (0);
     295           0 :     break;
     296             :   }
     297           0 :   pch->cb (pch->cb_cls,
     298             :            &dr);
     299           0 :   TALER_EXCHANGE_account_merge_cancel (pch);
     300           0 : }
     301             : 
     302             : 
     303             : struct TALER_EXCHANGE_AccountMergeHandle *
     304           0 : TALER_EXCHANGE_account_merge (
     305             :   struct TALER_EXCHANGE_Handle *exchange,
     306             :   const char *reserve_exchange_url,
     307             :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     308             :   const struct TALER_PurseContractPublicKeyP *purse_pub,
     309             :   const struct TALER_PurseMergePrivateKeyP *merge_priv,
     310             :   const struct TALER_PrivateContractHashP *h_contract_terms,
     311             :   uint8_t min_age,
     312             :   const struct TALER_Amount *purse_value_after_fees,
     313             :   struct GNUNET_TIME_Timestamp purse_expiration,
     314             :   struct GNUNET_TIME_Timestamp merge_timestamp,
     315             :   TALER_EXCHANGE_AccountMergeCallback cb,
     316             :   void *cb_cls)
     317             : {
     318             :   struct TALER_EXCHANGE_AccountMergeHandle *pch;
     319             :   struct GNUNET_CURL_Context *ctx;
     320             :   json_t *merge_obj;
     321             :   CURL *eh;
     322             :   char arg_str[sizeof (pch->purse_pub) * 2 + 32];
     323             :   char *reserve_url;
     324             : 
     325           0 :   pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
     326           0 :   pch->exchange = exchange;
     327           0 :   pch->merge_priv = *merge_priv;
     328           0 :   pch->cb = cb;
     329           0 :   pch->cb_cls = cb_cls;
     330           0 :   pch->purse_pub = *purse_pub;
     331           0 :   pch->h_contract_terms = *h_contract_terms;
     332           0 :   pch->purse_expiration = purse_expiration;
     333           0 :   pch->purse_value_after_fees = *purse_value_after_fees;
     334           0 :   if (NULL == reserve_exchange_url)
     335           0 :     pch->provider_url = GNUNET_strdup (exchange->url);
     336             :   else
     337           0 :     pch->provider_url = GNUNET_strdup (reserve_exchange_url);
     338           0 :   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
     339             :                                       &pch->reserve_pub.eddsa_pub);
     340             : 
     341           0 :   GNUNET_assert (GNUNET_YES ==
     342             :                  TEAH_handle_is_ready (exchange));
     343             :   {
     344             :     char pub_str[sizeof (*purse_pub) * 2];
     345             :     char *end;
     346             : 
     347           0 :     end = GNUNET_STRINGS_data_to_string (
     348             :       purse_pub,
     349             :       sizeof (*purse_pub),
     350             :       pub_str,
     351             :       sizeof (pub_str));
     352           0 :     *end = '\0';
     353           0 :     GNUNET_snprintf (arg_str,
     354             :                      sizeof (arg_str),
     355             :                      "/purses/%s/merge",
     356             :                      pub_str);
     357             :   }
     358           0 :   reserve_url = TALER_reserve_make_payto (pch->provider_url,
     359           0 :                                           &pch->reserve_pub);
     360           0 :   if (NULL == reserve_url)
     361             :   {
     362           0 :     GNUNET_break (0);
     363           0 :     GNUNET_free (pch->provider_url);
     364           0 :     GNUNET_free (pch);
     365           0 :     return NULL;
     366             :   }
     367           0 :   pch->url = TEAH_path_to_url (exchange,
     368             :                                arg_str);
     369           0 :   if (NULL == pch->url)
     370             :   {
     371           0 :     GNUNET_break (0);
     372           0 :     GNUNET_free (reserve_url);
     373           0 :     GNUNET_free (pch->provider_url);
     374           0 :     GNUNET_free (pch);
     375           0 :     return NULL;
     376             :   }
     377           0 :   TALER_wallet_purse_merge_sign (reserve_url,
     378             :                                  merge_timestamp,
     379             :                                  purse_pub,
     380             :                                  merge_priv,
     381             :                                  &pch->merge_sig);
     382             :   {
     383             :     struct TALER_Amount zero_purse_fee;
     384             : 
     385           0 :     GNUNET_assert (GNUNET_OK ==
     386             :                    TALER_amount_set_zero (purse_value_after_fees->currency,
     387             :                                           &zero_purse_fee));
     388           0 :     TALER_wallet_account_merge_sign (merge_timestamp,
     389             :                                      purse_pub,
     390             :                                      purse_expiration,
     391             :                                      h_contract_terms,
     392             :                                      purse_value_after_fees,
     393             :                                      &zero_purse_fee,
     394             :                                      min_age,
     395             :                                      TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
     396             :                                      reserve_priv,
     397             :                                      &pch->reserve_sig);
     398             :   }
     399           0 :   merge_obj = GNUNET_JSON_PACK (
     400             :     GNUNET_JSON_pack_string ("payto_uri",
     401             :                              reserve_url),
     402             :     GNUNET_JSON_pack_data_auto ("merge_sig",
     403             :                                 &pch->merge_sig),
     404             :     GNUNET_JSON_pack_data_auto ("reserve_sig",
     405             :                                 &pch->reserve_sig),
     406             :     GNUNET_JSON_pack_timestamp ("merge_timestamp",
     407             :                                 merge_timestamp));
     408           0 :   GNUNET_assert (NULL != merge_obj);
     409           0 :   eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
     410           0 :   if ( (NULL == eh) ||
     411             :        (GNUNET_OK !=
     412           0 :         TALER_curl_easy_post (&pch->ctx,
     413             :                               eh,
     414             :                               merge_obj)) )
     415             :   {
     416           0 :     GNUNET_break (0);
     417           0 :     if (NULL != eh)
     418           0 :       curl_easy_cleanup (eh);
     419           0 :     json_decref (merge_obj);
     420           0 :     GNUNET_free (pch->provider_url);
     421           0 :     GNUNET_free (pch->url);
     422           0 :     GNUNET_free (pch);
     423           0 :     return NULL;
     424             :   }
     425           0 :   json_decref (merge_obj);
     426           0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     427             :               "URL for purse merge: `%s'\n",
     428             :               pch->url);
     429           0 :   ctx = TEAH_handle_to_context (exchange);
     430           0 :   pch->job = GNUNET_CURL_job_add2 (ctx,
     431             :                                    eh,
     432           0 :                                    pch->ctx.headers,
     433             :                                    &handle_purse_merge_finished,
     434             :                                    pch);
     435           0 :   return pch;
     436             : }
     437             : 
     438             : 
     439             : void
     440           0 : TALER_EXCHANGE_account_merge_cancel (
     441             :   struct TALER_EXCHANGE_AccountMergeHandle *pch)
     442             : {
     443           0 :   if (NULL != pch->job)
     444             :   {
     445           0 :     GNUNET_CURL_job_cancel (pch->job);
     446           0 :     pch->job = NULL;
     447             :   }
     448           0 :   GNUNET_free (pch->url);
     449           0 :   GNUNET_free (pch->provider_url);
     450           0 :   TALER_curl_easy_post_finished (&pch->ctx);
     451           0 :   GNUNET_free (pch);
     452           0 : }
     453             : 
     454             : 
     455             : /* end of exchange_api_purse_merge.c */

Generated by: LCOV version 1.14