exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_post-purses-PURSE_PUB-merge.c (14429B)


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