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_post-recoup-refresh.c (14107B)


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