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

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2022 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful,
      11             :   but WITHOUT ANY WARRANTY; without even the implied warranty
      12             :   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13             :   See the GNU Affero General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU Affero General
      16             :   Public License along with TALER; see the file COPYING.  If not,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : /**
      20             :  * @file taler-exchange-httpd_withdraw.c
      21             :  * @brief Handle /reserves/$RESERVE_PUB/withdraw requests
      22             :  * @author Florian Dold
      23             :  * @author Benedikt Mueller
      24             :  * @author Christian Grothoff
      25             :  */
      26             : #include "platform.h"
      27             : #include <gnunet/gnunet_util_lib.h>
      28             : #include <jansson.h>
      29             : #include "taler_json_lib.h"
      30             : #include "taler_kyclogic_lib.h"
      31             : #include "taler_mhd_lib.h"
      32             : #include "taler-exchange-httpd_withdraw.h"
      33             : #include "taler-exchange-httpd_responses.h"
      34             : #include "taler-exchange-httpd_keys.h"
      35             : 
      36             : 
      37             : /**
      38             :  * Context for #withdraw_transaction.
      39             :  */
      40             : struct WithdrawContext
      41             : {
      42             : 
      43             :   /**
      44             :    * Hash of the (blinded) message to be signed by the Exchange.
      45             :    */
      46             :   struct TALER_BlindedCoinHashP h_coin_envelope;
      47             : 
      48             :   /**
      49             :    * Blinded planchet.
      50             :    */
      51             :   struct TALER_BlindedPlanchet blinded_planchet;
      52             : 
      53             :   /**
      54             :    * Set to the resulting signed coin data to be returned to the client.
      55             :    */
      56             :   struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
      57             : 
      58             :   /**
      59             :    * KYC status for the operation.
      60             :    */
      61             :   struct TALER_EXCHANGEDB_KycStatus kyc;
      62             : 
      63             :   /**
      64             :    * Hash of the payto-URI representing the reserve
      65             :    * from which we are withdrawing.
      66             :    */
      67             :   struct TALER_PaytoHashP h_payto;
      68             : 
      69             :   /**
      70             :    * Current time for the DB transaction.
      71             :    */
      72             :   struct GNUNET_TIME_Timestamp now;
      73             : 
      74             : };
      75             : 
      76             : 
      77             : /**
      78             :  * Function called to iterate over KYC-relevant
      79             :  * transaction amounts for a particular time range.
      80             :  * Called within a database transaction, so must
      81             :  * not start a new one.
      82             :  *
      83             :  * @param cls closure, identifies the event type and
      84             :  *        account to iterate over events for
      85             :  * @param limit maximum time-range for which events
      86             :  *        should be fetched (timestamp in the past)
      87             :  * @param cb function to call on each event found,
      88             :  *        events must be returned in reverse chronological
      89             :  *        order
      90             :  * @param cb_cls closure for @a cb
      91             :  */
      92             : static void
      93           0 : withdraw_amount_cb (void *cls,
      94             :                     struct GNUNET_TIME_Absolute limit,
      95             :                     TALER_EXCHANGEDB_KycAmountCallback cb,
      96             :                     void *cb_cls)
      97             : {
      98           0 :   struct WithdrawContext *wc = cls;
      99             :   enum GNUNET_DB_QueryStatus qs;
     100             : 
     101           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     102             :               "Signaling amount %s for KYC check\n",
     103             :               TALER_amount2s (&wc->collectable.amount_with_fee));
     104           0 :   if (GNUNET_OK !=
     105           0 :       cb (cb_cls,
     106           0 :           &wc->collectable.amount_with_fee,
     107             :           wc->now.abs_time))
     108           0 :     return;
     109           0 :   qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
     110           0 :     TEH_plugin->cls,
     111           0 :     &wc->h_payto,
     112             :     limit,
     113             :     cb,
     114             :     cb_cls);
     115           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     116             :               "Got %d additional transactions for this withdrawal and limit %llu\n",
     117             :               qs,
     118             :               (unsigned long long) limit.abs_value_us);
     119           0 :   GNUNET_break (qs >= 0);
     120             : }
     121             : 
     122             : 
     123             : /**
     124             :  * Function implementing withdraw transaction.  Runs the
     125             :  * transaction logic; IF it returns a non-error code, the transaction
     126             :  * logic MUST NOT queue a MHD response.  IF it returns an hard error,
     127             :  * the transaction logic MUST queue a MHD response and set @a mhd_ret.
     128             :  * IF it returns the soft error code, the function MAY be called again
     129             :  * to retry and MUST not queue a MHD response.
     130             :  *
     131             :  * Note that "wc->collectable.sig" is set before entering this function as we
     132             :  * signed before entering the transaction.
     133             :  *
     134             :  * @param cls a `struct WithdrawContext *`
     135             :  * @param connection MHD request which triggered the transaction
     136             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     137             :  *             if transaction failed (!)
     138             :  * @return transaction status
     139             :  */
     140             : static enum GNUNET_DB_QueryStatus
     141           0 : withdraw_transaction (void *cls,
     142             :                       struct MHD_Connection *connection,
     143             :                       MHD_RESULT *mhd_ret)
     144             : {
     145           0 :   struct WithdrawContext *wc = cls;
     146             :   enum GNUNET_DB_QueryStatus qs;
     147           0 :   bool found = false;
     148           0 :   bool balance_ok = false;
     149           0 :   bool nonce_ok = false;
     150             :   uint64_t ruuid;
     151             :   const struct TALER_CsNonce *nonce;
     152             :   const struct TALER_BlindedPlanchet *bp;
     153             : 
     154           0 :   wc->now = GNUNET_TIME_timestamp_get ();
     155           0 :   qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
     156           0 :                                         &wc->collectable.reserve_pub,
     157             :                                         &wc->h_payto);
     158           0 :   if (qs < 0)
     159           0 :     return qs;
     160             :   /* If no results, reserve was created by merge,
     161             :      in which case no KYC check is required as the
     162             :      merge already did that. */
     163           0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     164             :   {
     165             :     const char *kyc_required;
     166             : 
     167           0 :     kyc_required = TALER_KYCLOGIC_kyc_test_required (
     168             :       TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
     169           0 :       &wc->h_payto,
     170           0 :       TEH_plugin->select_satisfied_kyc_processes,
     171           0 :       TEH_plugin->cls,
     172             :       &withdraw_amount_cb,
     173             :       wc);
     174           0 :     if (NULL != kyc_required)
     175             :     {
     176             :       /* insert KYC requirement into DB! */
     177           0 :       wc->kyc.ok = false;
     178           0 :       return TEH_plugin->insert_kyc_requirement_for_account (
     179           0 :         TEH_plugin->cls,
     180             :         kyc_required,
     181           0 :         &wc->h_payto,
     182             :         &wc->kyc.requirement_row);
     183             :     }
     184             :   }
     185           0 :   wc->kyc.ok = true;
     186           0 :   bp = &wc->blinded_planchet;
     187           0 :   nonce = (TALER_DENOMINATION_CS == bp->cipher)
     188             :     ? &bp->details.cs_blinded_planchet.nonce
     189           0 :     : NULL;
     190           0 :   qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
     191             :                                 nonce,
     192           0 :                                 &wc->collectable,
     193             :                                 wc->now,
     194             :                                 &found,
     195             :                                 &balance_ok,
     196             :                                 &nonce_ok,
     197             :                                 &ruuid);
     198           0 :   if (0 > qs)
     199             :   {
     200           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     201           0 :       *mhd_ret = TALER_MHD_reply_with_error (connection,
     202             :                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     203             :                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
     204             :                                              "do_withdraw");
     205           0 :     return qs;
     206             :   }
     207           0 :   if (! found)
     208             :   {
     209           0 :     *mhd_ret = TALER_MHD_reply_with_error (connection,
     210             :                                            MHD_HTTP_NOT_FOUND,
     211             :                                            TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
     212             :                                            NULL);
     213           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     214             :   }
     215           0 :   if (! balance_ok)
     216             :   {
     217           0 :     TEH_plugin->rollback (TEH_plugin->cls);
     218           0 :     *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
     219             :       connection,
     220           0 :       &wc->collectable.amount_with_fee,
     221           0 :       &wc->collectable.reserve_pub);
     222           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     223             :   }
     224           0 :   if (! nonce_ok)
     225             :   {
     226           0 :     TEH_plugin->rollback (TEH_plugin->cls);
     227           0 :     *mhd_ret = TALER_MHD_reply_with_error (connection,
     228             :                                            MHD_HTTP_CONFLICT,
     229             :                                            TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
     230             :                                            NULL);
     231           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     232             :   }
     233           0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     234           0 :     TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
     235           0 :   return qs;
     236             : }
     237             : 
     238             : 
     239             : /**
     240             :  * Check if the @a rc is replayed and we already have an
     241             :  * answer. If so, replay the existing answer and return the
     242             :  * HTTP response.
     243             :  *
     244             :  * @param rc request context
     245             :  * @param[in,out] wc parsed request data
     246             :  * @param[out] mret HTTP status, set if we return true
     247             :  * @return true if the request is idempotent with an existing request
     248             :  *    false if we did not find the request in the DB and did not set @a mret
     249             :  */
     250             : static bool
     251           0 : check_request_idempotent (struct TEH_RequestContext *rc,
     252             :                           struct WithdrawContext *wc,
     253             :                           MHD_RESULT *mret)
     254             : {
     255             :   enum GNUNET_DB_QueryStatus qs;
     256             : 
     257           0 :   qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
     258           0 :                                       &wc->h_coin_envelope,
     259             :                                       &wc->collectable);
     260           0 :   if (0 > qs)
     261             :   {
     262           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     263           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     264           0 :       *mret = TALER_MHD_reply_with_error (rc->connection,
     265             :                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
     266             :                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
     267             :                                           "get_withdraw_info");
     268           0 :     return true; /* well, kind-of */
     269             :   }
     270           0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     271           0 :     return false;
     272             :   /* generate idempotent reply */
     273           0 :   TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
     274           0 :   *mret = TALER_MHD_REPLY_JSON_PACK (
     275             :     rc->connection,
     276             :     MHD_HTTP_OK,
     277             :     TALER_JSON_pack_blinded_denom_sig ("ev_sig",
     278             :                                        &wc->collectable.sig));
     279           0 :   TALER_blinded_denom_sig_free (&wc->collectable.sig);
     280           0 :   return true;
     281             : }
     282             : 
     283             : 
     284             : MHD_RESULT
     285           0 : TEH_handler_withdraw (struct TEH_RequestContext *rc,
     286             :                       const struct TALER_ReservePublicKeyP *reserve_pub,
     287             :                       const json_t *root)
     288             : {
     289             :   struct WithdrawContext wc;
     290             :   struct GNUNET_JSON_Specification spec[] = {
     291           0 :     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     292             :                                  &wc.collectable.reserve_sig),
     293           0 :     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
     294             :                                  &wc.collectable.denom_pub_hash),
     295           0 :     TALER_JSON_spec_blinded_planchet ("coin_ev",
     296             :                                       &wc.blinded_planchet),
     297           0 :     GNUNET_JSON_spec_end ()
     298             :   };
     299             :   enum TALER_ErrorCode ec;
     300             :   struct TEH_DenominationKey *dk;
     301             : 
     302           0 :   memset (&wc,
     303             :           0,
     304             :           sizeof (wc));
     305           0 :   wc.collectable.reserve_pub = *reserve_pub;
     306             :   {
     307             :     enum GNUNET_GenericReturnValue res;
     308             : 
     309           0 :     res = TALER_MHD_parse_json_data (rc->connection,
     310             :                                      root,
     311             :                                      spec);
     312           0 :     if (GNUNET_OK != res)
     313           0 :       return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
     314             :   }
     315             :   {
     316             :     MHD_RESULT mret;
     317             :     struct TEH_KeyStateHandle *ksh;
     318             : 
     319           0 :     ksh = TEH_keys_get_state ();
     320           0 :     if (NULL == ksh)
     321             :     {
     322           0 :       if (! check_request_idempotent (rc,
     323             :                                       &wc,
     324             :                                       &mret))
     325             :       {
     326           0 :         GNUNET_JSON_parse_free (spec);
     327           0 :         return TALER_MHD_reply_with_error (rc->connection,
     328             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     329             :                                            TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
     330             :                                            NULL);
     331             :       }
     332           0 :       GNUNET_JSON_parse_free (spec);
     333           0 :       return mret;
     334             :     }
     335           0 :     dk = TEH_keys_denomination_by_hash2 (ksh,
     336             :                                          &wc.collectable.denom_pub_hash,
     337             :                                          NULL,
     338             :                                          NULL);
     339           0 :     if (NULL == dk)
     340             :     {
     341           0 :       if (! check_request_idempotent (rc,
     342             :                                       &wc,
     343             :                                       &mret))
     344             :       {
     345           0 :         GNUNET_JSON_parse_free (spec);
     346           0 :         return TEH_RESPONSE_reply_unknown_denom_pub_hash (
     347             :           rc->connection,
     348             :           &wc.collectable.denom_pub_hash);
     349             :       }
     350           0 :       GNUNET_JSON_parse_free (spec);
     351           0 :       return mret;
     352             :     }
     353           0 :     if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
     354             :     {
     355             :       /* This denomination is past the expiration time for withdraws */
     356           0 :       if (! check_request_idempotent (rc,
     357             :                                       &wc,
     358             :                                       &mret))
     359             :       {
     360           0 :         GNUNET_JSON_parse_free (spec);
     361           0 :         return TEH_RESPONSE_reply_expired_denom_pub_hash (
     362             :           rc->connection,
     363             :           &wc.collectable.denom_pub_hash,
     364             :           TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
     365             :           "WITHDRAW");
     366             :       }
     367           0 :       GNUNET_JSON_parse_free (spec);
     368           0 :       return mret;
     369             :     }
     370           0 :     if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
     371             :     {
     372             :       /* This denomination is not yet valid, no need to check
     373             :          for idempotency! */
     374           0 :       GNUNET_JSON_parse_free (spec);
     375           0 :       return TEH_RESPONSE_reply_expired_denom_pub_hash (
     376             :         rc->connection,
     377             :         &wc.collectable.denom_pub_hash,
     378             :         TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
     379             :         "WITHDRAW");
     380             :     }
     381           0 :     if (dk->recoup_possible)
     382             :     {
     383             :       /* This denomination has been revoked */
     384           0 :       if (! check_request_idempotent (rc,
     385             :                                       &wc,
     386             :                                       &mret))
     387             :       {
     388           0 :         GNUNET_JSON_parse_free (spec);
     389           0 :         return TEH_RESPONSE_reply_expired_denom_pub_hash (
     390             :           rc->connection,
     391             :           &wc.collectable.denom_pub_hash,
     392             :           TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
     393             :           "WITHDRAW");
     394             :       }
     395           0 :       GNUNET_JSON_parse_free (spec);
     396           0 :       return mret;
     397             :     }
     398           0 :     if (dk->denom_pub.cipher != wc.blinded_planchet.cipher)
     399             :     {
     400             :       /* denomination cipher and blinded planchet cipher not the same */
     401           0 :       GNUNET_JSON_parse_free (spec);
     402           0 :       return TALER_MHD_reply_with_error (rc->connection,
     403             :                                          MHD_HTTP_BAD_REQUEST,
     404             :                                          TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
     405             :                                          NULL);
     406             :     }
     407             :   }
     408             : 
     409           0 :   if (0 >
     410           0 :       TALER_amount_add (&wc.collectable.amount_with_fee,
     411           0 :                         &dk->meta.value,
     412           0 :                         &dk->meta.fees.withdraw))
     413             :   {
     414           0 :     GNUNET_JSON_parse_free (spec);
     415           0 :     return TALER_MHD_reply_with_error (rc->connection,
     416             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     417             :                                        TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
     418             :                                        NULL);
     419             :   }
     420             : 
     421           0 :   if (GNUNET_OK !=
     422           0 :       TALER_coin_ev_hash (&wc.blinded_planchet,
     423             :                           &wc.collectable.denom_pub_hash,
     424             :                           &wc.collectable.h_coin_envelope))
     425             :   {
     426           0 :     GNUNET_break (0);
     427           0 :     GNUNET_JSON_parse_free (spec);
     428           0 :     return TALER_MHD_reply_with_error (rc->connection,
     429             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     430             :                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     431             :                                        NULL);
     432             :   }
     433             : 
     434           0 :   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
     435           0 :   if (GNUNET_OK !=
     436           0 :       TALER_wallet_withdraw_verify (&wc.collectable.denom_pub_hash,
     437             :                                     &wc.collectable.amount_with_fee,
     438             :                                     &wc.collectable.h_coin_envelope,
     439             :                                     &wc.collectable.reserve_pub,
     440             :                                     &wc.collectable.reserve_sig))
     441             :   {
     442           0 :     GNUNET_break_op (0);
     443           0 :     GNUNET_JSON_parse_free (spec);
     444           0 :     return TALER_MHD_reply_with_error (rc->connection,
     445             :                                        MHD_HTTP_FORBIDDEN,
     446             :                                        TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
     447             :                                        NULL);
     448             :   }
     449             : 
     450             :   /* Sign before transaction! */
     451           0 :   ec = TEH_keys_denomination_sign_withdraw (
     452             :     &wc.collectable.denom_pub_hash,
     453             :     &wc.blinded_planchet,
     454             :     &wc.collectable.sig);
     455           0 :   if (TALER_EC_NONE != ec)
     456             :   {
     457           0 :     GNUNET_break (0);
     458           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     459             :                 "Failed to sign coin: %d\n",
     460             :                 ec);
     461           0 :     GNUNET_JSON_parse_free (spec);
     462           0 :     return TALER_MHD_reply_with_ec (rc->connection,
     463             :                                     ec,
     464             :                                     NULL);
     465             :   }
     466             : 
     467             :   /* run transaction */
     468             :   {
     469             :     MHD_RESULT mhd_ret;
     470             : 
     471           0 :     if (GNUNET_OK !=
     472           0 :         TEH_DB_run_transaction (rc->connection,
     473             :                                 "run withdraw",
     474             :                                 TEH_MT_REQUEST_WITHDRAW,
     475             :                                 &mhd_ret,
     476             :                                 &withdraw_transaction,
     477             :                                 &wc))
     478             :     {
     479             :       /* Even if #withdraw_transaction() failed, it may have created a signature
     480             :          (or we might have done it optimistically above). */
     481           0 :       TALER_blinded_denom_sig_free (&wc.collectable.sig);
     482           0 :       GNUNET_JSON_parse_free (spec);
     483           0 :       return mhd_ret;
     484             :     }
     485             :   }
     486             : 
     487             :   /* Clean up and send back final response */
     488           0 :   GNUNET_JSON_parse_free (spec);
     489             : 
     490           0 :   if (! wc.kyc.ok)
     491           0 :     return TEH_RESPONSE_reply_kyc_required (rc->connection,
     492             :                                             &wc.h_payto,
     493             :                                             &wc.kyc);
     494             :   {
     495             :     MHD_RESULT ret;
     496             : 
     497           0 :     ret = TALER_MHD_REPLY_JSON_PACK (
     498             :       rc->connection,
     499             :       MHD_HTTP_OK,
     500             :       TALER_JSON_pack_blinded_denom_sig ("ev_sig",
     501             :                                          &wc.collectable.sig));
     502           0 :     TALER_blinded_denom_sig_free (&wc.collectable.sig);
     503           0 :     return ret;
     504             :   }
     505             : }
     506             : 
     507             : 
     508             : /* end of taler-exchange-httpd_withdraw.c */

Generated by: LCOV version 1.14