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 (14283B)


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