exchange

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

taler-exchange-httpd_recoup.c (14106B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017-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 Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_recoup.c
     18  * @brief Handle /recoup requests; parses the POST and JSON and
     19  *        verifies the coin signature before handing things off
     20  *        to the database.
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h>
     28 #include <pthread.h>
     29 #include "taler/taler_json_lib.h"
     30 #include "taler/taler_mhd_lib.h"
     31 #include "taler-exchange-httpd_db.h"
     32 #include "taler-exchange-httpd_recoup.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 #include "taler-exchange-httpd_keys.h"
     35 #include "taler/taler_exchangedb_lib.h"
     36 
     37 /**
     38  * Closure for #recoup_transaction.
     39  */
     40 struct RecoupContext
     41 {
     42   /**
     43    * Hash identifying the withdraw request.
     44    */
     45   struct TALER_BlindedCoinHashP h_coin_ev;
     46 
     47   /**
     48    * Set by #recoup_transaction() to the reserve that will
     49    * receive the recoup, if #refreshed is #GNUNET_NO.
     50    */
     51   struct TALER_ReservePublicKeyP reserve_pub;
     52 
     53   /**
     54    * Details about the coin.
     55    */
     56   const struct TALER_CoinPublicInfo *coin;
     57 
     58   /**
     59    * Key used to blind the coin.
     60    */
     61   const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
     62 
     63   /**
     64    * Signature of the coin requesting recoup.
     65    */
     66   const struct TALER_CoinSpendSignatureP *coin_sig;
     67 
     68   /**
     69    * Unique ID of the withdraw operation in the withdraw table.
     70    */
     71   uint64_t withdraw_serial_id;
     72 
     73   /**
     74    * Unique ID of the coin in the known_coins table.
     75    */
     76   uint64_t known_coin_id;
     77 
     78   /**
     79    * Set by #recoup_transaction to the timestamp when the recoup
     80    * was accepted.
     81    */
     82   struct GNUNET_TIME_Timestamp now;
     83 
     84 };
     85 
     86 
     87 /**
     88  * Execute a "recoup".  The validity of the coin and signature have
     89  * already been checked.  The database must now check that the coin is
     90  * not (double) spent, and execute the transaction.
     91  *
     92  * IF it returns a non-error code, the transaction logic MUST
     93  * NOT queue a MHD response.  IF it returns an hard error, the
     94  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
     95  * it returns the soft error code, the function MAY be called again to
     96  * retry and MUST not queue a MHD response.
     97  *
     98  * @param cls the `struct RecoupContext *`
     99  * @param connection MHD request which triggered the transaction
    100  * @param[out] mhd_ret set to MHD response status for @a connection,
    101  *             if transaction failed (!)
    102  * @return transaction status code
    103  */
    104 static enum GNUNET_DB_QueryStatus
    105 recoup_transaction (void *cls,
    106                     struct MHD_Connection *connection,
    107                     MHD_RESULT *mhd_ret)
    108 {
    109   struct RecoupContext *pc = cls;
    110   enum GNUNET_DB_QueryStatus qs;
    111   bool recoup_ok;
    112   bool internal_failure;
    113 
    114   /* Finally, store new refund data */
    115   pc->now = GNUNET_TIME_timestamp_get ();
    116   qs = TEH_plugin->do_recoup (TEH_plugin->cls,
    117                               &pc->reserve_pub,
    118                               pc->withdraw_serial_id,
    119                               pc->coin_bks,
    120                               &pc->coin->coin_pub,
    121                               pc->known_coin_id,
    122                               pc->coin_sig,
    123                               &pc->now,
    124                               &recoup_ok,
    125                               &internal_failure);
    126   if (0 > qs)
    127   {
    128     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    129       *mhd_ret = TALER_MHD_reply_with_error (
    130         connection,
    131         MHD_HTTP_INTERNAL_SERVER_ERROR,
    132         TALER_EC_GENERIC_DB_FETCH_FAILED,
    133         "do_recoup");
    134     return qs;
    135   }
    136 
    137   if (internal_failure)
    138   {
    139     GNUNET_break (0);
    140     *mhd_ret = TALER_MHD_reply_with_error (
    141       connection,
    142       MHD_HTTP_INTERNAL_SERVER_ERROR,
    143       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    144       "do_recoup");
    145     return GNUNET_DB_STATUS_HARD_ERROR;
    146   }
    147   if (! recoup_ok)
    148   {
    149     *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
    150       connection,
    151       TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
    152       &pc->coin->denom_pub_hash,
    153       &pc->coin->coin_pub);
    154     return GNUNET_DB_STATUS_HARD_ERROR;
    155   }
    156   return qs;
    157 }
    158 
    159 
    160 /**
    161  * We have parsed the JSON information about the recoup request. Do
    162  * some basic sanity checks (especially that the signature on the
    163  * request and coin is valid) and then execute the recoup operation.
    164  * Note that we need the DB to check the fee structure, so this is not
    165  * done here but during the recoup_transaction().
    166  *
    167  * @param connection the MHD connection to handle
    168  * @param coin information about the coin
    169  * @param exchange_vals values contributed by the exchange
    170  *         during withdrawal
    171  * @param coin_bks blinding data of the coin (to be checked)
    172  * @param h_planchets The hash of the commitment of the original withdraw request
    173  * @param nonce coin's nonce if CS is used
    174  * @param coin_sig signature of the coin
    175  * @return MHD result code
    176  */
    177 static MHD_RESULT
    178 verify_and_execute_recoup (
    179   struct MHD_Connection *connection,
    180   const struct TALER_CoinPublicInfo *coin,
    181   const struct TALER_ExchangeBlindingValues *exchange_vals,
    182   const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
    183   const struct TALER_HashBlindedPlanchetsP *h_planchets,
    184   const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
    185   const struct TALER_CoinSpendSignatureP *coin_sig)
    186 {
    187   struct RecoupContext pc;
    188   const struct TEH_DenominationKey *dk;
    189   MHD_RESULT mret;
    190 
    191   /* check denomination exists and is in recoup mode */
    192   dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
    193                                       connection,
    194                                       &mret);
    195   if (NULL == dk)
    196     return mret;
    197   if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
    198   {
    199     /* This denomination is past the expiration time for recoup */
    200     return TEH_RESPONSE_reply_expired_denom_pub_hash (
    201       connection,
    202       &coin->denom_pub_hash,
    203       TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
    204       "RECOUP");
    205   }
    206   if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
    207   {
    208     /* This denomination is not yet valid */
    209     return TEH_RESPONSE_reply_expired_denom_pub_hash (
    210       connection,
    211       &coin->denom_pub_hash,
    212       TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
    213       "RECOUP");
    214   }
    215   if (! dk->recoup_possible)
    216   {
    217     /* This denomination is not eligible for recoup */
    218     return TEH_RESPONSE_reply_expired_denom_pub_hash (
    219       connection,
    220       &coin->denom_pub_hash,
    221       TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
    222       "RECOUP");
    223   }
    224 
    225   /* check denomination signature */
    226   switch (dk->denom_pub.bsign_pub_key->cipher)
    227   {
    228   case GNUNET_CRYPTO_BSA_RSA:
    229     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
    230     break;
    231   case GNUNET_CRYPTO_BSA_CS:
    232     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
    233     break;
    234   default:
    235     break;
    236   }
    237   if (GNUNET_YES !=
    238       TALER_test_coin_valid (coin,
    239                              &dk->denom_pub))
    240   {
    241     GNUNET_break_op (0);
    242     return TALER_MHD_reply_with_error (
    243       connection,
    244       MHD_HTTP_FORBIDDEN,
    245       TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
    246       NULL);
    247   }
    248 
    249   /* check recoup request signature */
    250   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    251   if (GNUNET_OK !=
    252       TALER_wallet_recoup_verify (&coin->denom_pub_hash,
    253                                   coin_bks,
    254                                   &coin->coin_pub,
    255                                   coin_sig))
    256   {
    257     GNUNET_break_op (0);
    258     return TALER_MHD_reply_with_error (
    259       connection,
    260       MHD_HTTP_FORBIDDEN,
    261       TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
    262       NULL);
    263   }
    264 
    265   /* re-compute client-side blinding so we can
    266      (a bit later) check that this coin was indeed
    267      signed by us. */
    268   {
    269     struct TALER_CoinPubHashP c_hash;
    270     struct TALER_BlindedPlanchet blinded_planchet;
    271 
    272     if (GNUNET_OK !=
    273         TALER_denom_blind (&dk->denom_pub,
    274                            coin_bks,
    275                            nonce,
    276                            &coin->h_age_commitment,
    277                            &coin->coin_pub,
    278                            exchange_vals,
    279                            &c_hash,
    280                            &blinded_planchet))
    281     {
    282       GNUNET_break (0);
    283       return TALER_MHD_reply_with_error (
    284         connection,
    285         MHD_HTTP_INTERNAL_SERVER_ERROR,
    286         TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
    287         NULL);
    288     }
    289     TALER_coin_ev_hash (&blinded_planchet,
    290                         &coin->denom_pub_hash,
    291                         &pc.h_coin_ev);
    292     TALER_blinded_planchet_free (&blinded_planchet);
    293   }
    294 
    295   pc.coin_sig = coin_sig;
    296   pc.coin_bks = coin_bks;
    297   pc.coin = coin;
    298 
    299   {
    300     MHD_RESULT mhd_ret = MHD_NO;
    301     enum GNUNET_DB_QueryStatus qs;
    302 
    303     /* make sure coin is 'known' in database */
    304     qs = TEH_make_coin_known (coin,
    305                               connection,
    306                               &pc.known_coin_id,
    307                               &mhd_ret);
    308     /* no transaction => no serialization failures should be possible */
    309     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    310     if (qs < 0)
    311       return mhd_ret;
    312   }
    313 
    314   {
    315     enum GNUNET_DB_QueryStatus qs;
    316 
    317     qs = TEH_plugin->get_reserve_by_h_planchets (
    318       TEH_plugin->cls,
    319       h_planchets,
    320       &pc.reserve_pub,
    321       &pc.withdraw_serial_id);
    322     if (0 > qs)
    323     {
    324       GNUNET_break (0);
    325       return TALER_MHD_reply_with_error (
    326         connection,
    327         MHD_HTTP_INTERNAL_SERVER_ERROR,
    328         TALER_EC_GENERIC_DB_FETCH_FAILED,
    329         "get_reserve_by_commitment");
    330     }
    331     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    332     {
    333       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    334                   "Recoup requested for unknown envelope %s\n",
    335                   GNUNET_h2s (&pc.h_coin_ev.hash));
    336       return TALER_MHD_reply_with_error (
    337         connection,
    338         MHD_HTTP_NOT_FOUND,
    339         TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
    340         NULL);
    341     }
    342   }
    343 
    344   /* Perform actual recoup transaction */
    345   {
    346     MHD_RESULT mhd_ret;
    347 
    348     if (GNUNET_OK !=
    349         TEH_DB_run_transaction (connection,
    350                                 "run recoup",
    351                                 TEH_MT_REQUEST_OTHER,
    352                                 &mhd_ret,
    353                                 &recoup_transaction,
    354                                 &pc))
    355       return mhd_ret;
    356   }
    357   /* Recoup succeeded, return result */
    358   return TALER_MHD_REPLY_JSON_PACK (connection,
    359                                     MHD_HTTP_OK,
    360                                     GNUNET_JSON_pack_data_auto (
    361                                       "reserve_pub",
    362                                       &pc.reserve_pub));
    363 }
    364 
    365 
    366 /**
    367  * Handle a "/coins/$COIN_PUB/recoup" request.  Parses the JSON, and, if
    368  * successful, passes the JSON data to #verify_and_execute_recoup() to further
    369  * check the details of the operation specified.  If everything checks out,
    370  * this will ultimately lead to the refund being executed, or rejected.
    371  *
    372  * @param connection the MHD connection to handle
    373  * @param coin_pub public key of the coin
    374  * @param root uploaded JSON data
    375  * @return MHD result code
    376   */
    377 MHD_RESULT
    378 TEH_handler_recoup (struct MHD_Connection *connection,
    379                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    380                     const json_t *root)
    381 {
    382   enum GNUNET_GenericReturnValue ret;
    383   struct TALER_CoinPublicInfo coin;
    384   union GNUNET_CRYPTO_BlindingSecretP coin_bks;
    385   struct TALER_CoinSpendSignatureP coin_sig;
    386   struct TALER_ExchangeBlindingValues exchange_vals;
    387   struct TALER_HashBlindedPlanchetsP h_planchets;
    388   union GNUNET_CRYPTO_BlindSessionNonce nonce;
    389   bool no_nonce;
    390   struct GNUNET_JSON_Specification spec[] = {
    391     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
    392                                  &coin.denom_pub_hash),
    393     TALER_JSON_spec_denom_sig ("denom_sig",
    394                                &coin.denom_sig),
    395     GNUNET_JSON_spec_fixed_auto ("h_planchets",
    396                                  &h_planchets),
    397     TALER_JSON_spec_exchange_blinding_values ("ewv",
    398                                               &exchange_vals),
    399     GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
    400                                  &coin_bks),
    401     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    402                                  &coin_sig),
    403     GNUNET_JSON_spec_mark_optional (
    404       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    405                                    &coin.h_age_commitment),
    406       &coin.no_age_commitment),
    407     GNUNET_JSON_spec_mark_optional (
    408       GNUNET_JSON_spec_fixed_auto ("nonce",
    409                                    &nonce),
    410       &no_nonce),
    411     GNUNET_JSON_spec_end ()
    412   };
    413 
    414   memset (&coin,
    415           0,
    416           sizeof (coin));
    417   coin.coin_pub = *coin_pub;
    418   ret = TALER_MHD_parse_json_data (connection,
    419                                    root,
    420                                    spec);
    421   if (GNUNET_SYSERR == ret)
    422     return MHD_NO; /* hard failure */
    423   if (GNUNET_NO == ret)
    424     return MHD_YES; /* failure */
    425   {
    426     MHD_RESULT res;
    427 
    428     res = verify_and_execute_recoup (connection,
    429                                      &coin,
    430                                      &exchange_vals,
    431                                      &coin_bks,
    432                                      &h_planchets,
    433                                      no_nonce
    434                                      ? NULL
    435                                      : &nonce,
    436                                      &coin_sig);
    437     GNUNET_JSON_parse_free (spec);
    438     return res;
    439   }
    440 }
    441 
    442 
    443 /* end of taler-exchange-httpd_recoup.c */