LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_payback.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 79 117 67.5 %
Date: 2017-09-17 17:24:28 Functions: 4 5 80.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2017 Inria and GNUnet e.V.
       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_payback.c
      18             :  * @brief Handle /payback 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 "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_json_lib.h"
      30             : #include "taler-exchange-httpd_parsing.h"
      31             : #include "taler-exchange-httpd_payback.h"
      32             : #include "taler-exchange-httpd_responses.h"
      33             : #include "taler-exchange-httpd_keystate.h"
      34             : #include "taler-exchange-httpd_validation.h"
      35             : 
      36             : 
      37             : /**
      38             :  * A wallet asked for /payback, but we do not know anything about the
      39             :  * original withdraw operation specified. Generates a 404 reply.
      40             :  *
      41             :  * @param connection connection to the client
      42             :  * @param ec Taler error code
      43             :  * @return MHD result code
      44             :  */
      45             : static int
      46           0 : reply_payback_unknown (struct MHD_Connection *connection,
      47             :                        enum TALER_ErrorCode ec)
      48             : {
      49           0 :   return TEH_RESPONSE_reply_json_pack (connection,
      50             :                                        MHD_HTTP_NOT_FOUND,
      51             :                                        "{s:s, s:I}",
      52             :                                        "error", "blinded coin unknown",
      53             :                                        "code", (json_int_t) ec);
      54             : }
      55             : 
      56             : 
      57             : /**
      58             :  * A wallet asked for /payback, return the successful response.
      59             :  *
      60             :  * @param connection connection to the client
      61             :  * @param coin_pub coin for which we are processing the payback request
      62             :  * @param reserve_pub public key of the reserve that will receive the payback
      63             :  * @param amount the amount we will wire back
      64             :  * @param timestamp when did the exchange receive the /payback request
      65             :  * @return MHD result code
      66             :  */
      67             : static int
      68           2 : reply_payback_success (struct MHD_Connection *connection,
      69             :                        const struct TALER_CoinSpendPublicKeyP *coin_pub,
      70             :                        const struct TALER_ReservePublicKeyP *reserve_pub,
      71             :                        const struct TALER_Amount *amount,
      72             :                        struct GNUNET_TIME_Absolute timestamp)
      73             : {
      74             :   struct TALER_PaybackConfirmationPS pc;
      75             :   struct TALER_ExchangePublicKeyP pub;
      76             :   struct TALER_ExchangeSignatureP sig;
      77             : 
      78           2 :   pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
      79           2 :   pc.purpose.size = htonl (sizeof (struct TALER_PaybackConfirmationPS));
      80           2 :   pc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
      81           2 :   TALER_amount_hton (&pc.payback_amount,
      82             :                      amount);
      83           2 :   pc.coin_pub = *coin_pub;
      84           2 :   pc.reserve_pub = *reserve_pub;
      85           2 :   TEH_KS_sign (&pc.purpose,
      86             :                &pub,
      87             :                &sig);
      88           2 :   return TEH_RESPONSE_reply_json_pack (connection,
      89             :                                        MHD_HTTP_OK,
      90             :                                        "{s:o, s:o, s:o, s:o, s:o}",
      91             :                                        "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub),
      92             :                                        "timestamp", GNUNET_JSON_from_time_abs (timestamp),
      93             :                                        "amount", TALER_JSON_from_amount (amount),
      94             :                                        "exchange_sig", GNUNET_JSON_from_data_auto (&sig),
      95             :                                        "exchange_pub", GNUNET_JSON_from_data_auto (&pub));
      96             : }
      97             : 
      98             : 
      99             : /**
     100             :  * Closure for #payback_transaction.
     101             :  */
     102             : struct PaybackContext
     103             : {
     104             :   /**
     105             :    * Hash of the blinded coin.
     106             :    */
     107             :   struct GNUNET_HashCode h_blind;
     108             : 
     109             :   /**
     110             :    * Full value of the coin.
     111             :    */
     112             :   struct TALER_Amount value;
     113             : 
     114             :   /**
     115             :    * Details about the coin.
     116             :    */
     117             :   const struct TALER_CoinPublicInfo *coin;
     118             :   
     119             :   /**
     120             :    * Key used to blind the coin.
     121             :    */
     122             :   const struct TALER_DenominationBlindingKeyP *coin_bks;
     123             : 
     124             :   /**
     125             :    * Signature of the coin requesting payback.
     126             :    */
     127             :   const struct TALER_CoinSpendSignatureP *coin_sig;
     128             : 
     129             :   /**
     130             :    * Set by #payback_transaction() to the reserve that will
     131             :    * receive the payback.
     132             :    */
     133             :   struct TALER_ReservePublicKeyP reserve_pub;
     134             : 
     135             :   /**
     136             :    * Set by #payback_transaction() to the amount that will be paid back
     137             :    */
     138             :   struct TALER_Amount amount;
     139             : 
     140             :   /**
     141             :    * Set by #payback_transaction to the timestamp when the payback
     142             :    * was accepted.
     143             :    */
     144             :   struct GNUNET_TIME_Absolute now;
     145             : 
     146             : };
     147             : 
     148             : 
     149             : /**
     150             :  * Execute a "/payback".  The validity of the coin and signature have
     151             :  * already been checked.  The database must now check that the coin is
     152             :  * not (double) spent, and execute the transaction.
     153             :  *
     154             :  * IF it returns a non-error code, the transaction logic MUST
     155             :  * NOT queue a MHD response.  IF it returns an hard error, the
     156             :  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
     157             :  * it returns the soft error code, the function MAY be called again to
     158             :  * retry and MUST not queue a MHD response.
     159             :  *
     160             :  * @param cls the `struct PaybackContext *`
     161             :  * @param connection MHD request which triggered the transaction
     162             :  * @param session database session to use
     163             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     164             :  *             if transaction failed (!)
     165             :  * @return transaction status code
     166             :  */
     167             : static enum GNUNET_DB_QueryStatus
     168           3 : payback_transaction (void *cls,
     169             :                      struct MHD_Connection *connection,
     170             :                      struct TALER_EXCHANGEDB_Session *session,
     171             :                      int *mhd_ret)
     172             : {
     173           3 :   struct PaybackContext *pc = cls;
     174             :   struct TALER_EXCHANGEDB_TransactionList *tl;
     175             :   struct TALER_Amount spent;
     176             :   enum GNUNET_DB_QueryStatus qs;
     177             :   
     178             :   /* Check whether a payback is allowed, and if so, to which
     179             :      reserve / account the money should go */
     180           6 :   qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
     181             :                                            session,
     182           3 :                                            &pc->h_blind,
     183             :                                            &pc->reserve_pub);
     184           3 :   if (0 > qs)
     185             :   {
     186           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     187             :     {
     188           0 :       GNUNET_break (0);
     189           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     190             :                                                        TALER_EC_PAYBACK_DB_FETCH_FAILED);
     191             :     }
     192           0 :     return qs;
     193             :   }
     194           3 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     195             :   {
     196           0 :     GNUNET_break_op (0);
     197           0 :     *mhd_ret = reply_payback_unknown (connection,
     198             :                                       TALER_EC_PAYBACK_WITHDRAW_NOT_FOUND);
     199           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     200             :   }
     201             : 
     202             :   /* Calculate remaining balance. */
     203           6 :   qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
     204             :                                           session,
     205           3 :                                           &pc->coin->coin_pub,
     206             :                                           &tl);
     207           3 :   if (0 > qs)
     208             :   {
     209           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     210             :     {
     211           0 :       GNUNET_break (0);
     212           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     213             :                                                        TALER_EC_PAYBACK_DB_FETCH_FAILED);
     214             :     }
     215           0 :     return qs;
     216             :   }
     217           3 :   TALER_amount_get_zero (pc->value.currency,
     218             :                          &spent);
     219           3 :   if (GNUNET_OK !=
     220           3 :       TEH_DB_calculate_transaction_list_totals (tl,
     221             :                                                 &spent,
     222             :                                                 &spent))
     223             :   {
     224           0 :     GNUNET_break (0);
     225           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     226             :                                             tl);
     227           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     228             :                                                      TALER_EC_PAYBACK_HISTORY_DB_ERROR);
     229           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     230             :   }
     231           3 :   if (GNUNET_SYSERR ==
     232           3 :       TALER_amount_subtract (&pc->amount,
     233           3 :                              &pc->value,
     234             :                              &spent))
     235             :   {
     236           0 :     GNUNET_break (0);
     237           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     238             :                                             tl);
     239           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     240             :                                                      TALER_EC_PAYBACK_COIN_BALANCE_NEGATIVE);
     241           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     242             :   }
     243           5 :   if ( (0 == pc->amount.fraction) &&
     244           2 :        (0 == pc->amount.value) )
     245             :   {
     246           1 :     TEH_plugin->rollback (TEH_plugin->cls,
     247             :                           session);
     248           1 :     *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
     249             :                                                            TALER_EC_PAYBACK_COIN_BALANCE_ZERO,
     250             :                                                            tl);
     251           1 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     252             :                                             tl);
     253           1 :     return GNUNET_DB_STATUS_HARD_ERROR;
     254             :   }
     255           2 :   TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     256             :                                           tl);
     257           2 :   pc->now = GNUNET_TIME_absolute_get ();
     258           2 :   (void) GNUNET_TIME_round_abs (&pc->now);
     259             : 
     260             :   /* add coin to list of wire transfers for payback */
     261           6 :   qs = TEH_plugin->insert_payback_request (TEH_plugin->cls,
     262             :                                            session,
     263           2 :                                            &pc->reserve_pub,
     264             :                                            pc->coin,
     265             :                                            pc->coin_sig,
     266             :                                            pc->coin_bks,
     267           2 :                                            &pc->amount,
     268           2 :                                            &pc->h_blind,
     269             :                                            pc->now);
     270           2 :   if (0 > qs)
     271             :   {
     272           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     273             :     {
     274           0 :       TALER_LOG_WARNING ("Failed to store /payback information in database\n");
     275           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     276             :                                                        TALER_EC_PAYBACK_DB_PUT_FAILED);
     277             :     }
     278           0 :     return qs;
     279             :   }
     280           2 :   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     281             : }
     282             : 
     283             : 
     284             : /**
     285             :  * We have parsed the JSON information about the payback request. Do
     286             :  * some basic sanity checks (especially that the signature on the
     287             :  * request and coin is valid) and then execute the payback operation.
     288             :  * Note that we need the DB to check the fee structure, so this is not
     289             :  * done here.
     290             :  *
     291             :  * @param connection the MHD connection to handle
     292             :  * @param coin information about the coin
     293             :  * @param coin_bks blinding data of the coin (to be checked)
     294             :  * @param coin_sig signature of the coin
     295             :  * @return MHD result code
     296             :  */
     297             : static int
     298           3 : verify_and_execute_payback (struct MHD_Connection *connection,
     299             :                             const struct TALER_CoinPublicInfo *coin,
     300             :                             const struct TALER_DenominationBlindingKeyP *coin_bks,
     301             :                             const struct TALER_CoinSpendSignatureP *coin_sig)
     302             : {
     303             :   struct PaybackContext pc;
     304             :   struct TEH_KS_StateHandle *key_state;
     305             :   const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
     306             :   struct TALER_PaybackRequestPS pr;
     307             :   struct GNUNET_HashCode c_hash;
     308             :   char *coin_ev;
     309             :   size_t coin_ev_size;
     310             :   int mhd_ret;
     311             : 
     312             :   /* check denomination exists and is in payback mode */
     313           3 :   key_state = TEH_KS_acquire ();
     314           3 :   dki = TEH_KS_denomination_key_lookup (key_state,
     315             :                                         &coin->denom_pub,
     316             :                                         TEH_KS_DKU_PAYBACK);
     317           3 :   if (NULL == dki)
     318             :   {
     319           0 :     TEH_KS_release (key_state);
     320           0 :     TALER_LOG_WARNING ("Denomination key in /payback request not in payback mode\n");
     321           0 :     return TEH_RESPONSE_reply_arg_unknown (connection,
     322             :                                            TALER_EC_PAYBACK_DENOMINATION_KEY_UNKNOWN,
     323             :                                            "denom_pub");
     324             :   }
     325           3 :   TALER_amount_ntoh (&pc.value,
     326             :                      &dki->issue.properties.value);
     327             : 
     328             :   /* check denomination signature */
     329           3 :   if (GNUNET_YES !=
     330           3 :       TALER_test_coin_valid (coin))
     331             :   {
     332           0 :     TALER_LOG_WARNING ("Invalid coin passed for /payback\n");
     333           0 :     TEH_KS_release (key_state);
     334           0 :     return TEH_RESPONSE_reply_signature_invalid (connection,
     335             :                                                  TALER_EC_PAYBACK_DENOMINATION_SIGNATURE_INVALID,
     336             :                                                  "denom_sig");
     337             :   }
     338             : 
     339             :   /* check payback request signature */
     340           3 :   pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK);
     341           3 :   pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS));
     342           3 :   pr.coin_pub = coin->coin_pub;
     343           3 :   pr.h_denom_pub = dki->issue.properties.denom_hash;
     344           3 :   pr.coin_blind = *coin_bks;
     345             : 
     346           3 :   TEH_KS_release (key_state);
     347             : 
     348           3 :   if (GNUNET_OK !=
     349           3 :       GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_PAYBACK,
     350             :                                   &pr.purpose,
     351             :                                   &coin_sig->eddsa_signature,
     352             :                                   &coin->coin_pub.eddsa_pub))
     353             :   {
     354           0 :     TALER_LOG_WARNING ("Invalid signature on /payback request\n");
     355           0 :     return TEH_RESPONSE_reply_signature_invalid (connection,
     356             :                                                  TALER_EC_PAYBACK_SIGNATURE_INVALID,
     357             :                                                  "coin_sig");
     358             :   }
     359             : 
     360           3 :   GNUNET_CRYPTO_hash (&coin->coin_pub.eddsa_pub,
     361             :                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
     362             :                       &c_hash);
     363           3 :   if (GNUNET_YES !=
     364           3 :       GNUNET_CRYPTO_rsa_blind (&c_hash,
     365             :                                &coin_bks->bks,
     366             :                                coin->denom_pub.rsa_public_key,
     367             :                                &coin_ev,
     368             :                                &coin_ev_size))
     369             :   {
     370           0 :     GNUNET_break (0);
     371           0 :     return TEH_RESPONSE_reply_internal_error (connection,
     372             :                                               TALER_EC_PAYBACK_BLINDING_FAILED,
     373             :                                               "coin_bks");
     374             :   }
     375           3 :   GNUNET_CRYPTO_hash (coin_ev,
     376             :                       coin_ev_size,
     377             :                       &pc.h_blind);
     378           3 :   GNUNET_free (coin_ev);
     379             : 
     380           3 :   pc.coin_sig = coin_sig;
     381           3 :   pc.coin_bks = coin_bks;
     382           3 :   pc.coin = coin;
     383           3 :   if (GNUNET_OK !=
     384           3 :       TEH_DB_run_transaction (connection,
     385             :                               &mhd_ret,
     386             :                               &payback_transaction,
     387             :                               &pc))
     388           1 :     return mhd_ret;
     389             :   
     390           2 :   return reply_payback_success (connection,
     391             :                                 &coin->coin_pub,
     392             :                                 &pc.reserve_pub,
     393             :                                 &pc.amount,
     394             :                                 pc.now);
     395             : }
     396             : 
     397             : 
     398             : /**
     399             :  * Handle a "/payback" request.  Parses the JSON, and, if successful,
     400             :  * passes the JSON data to #verify_and_execute_payback() to
     401             :  * further check the details of the operation specified.  If
     402             :  * everything checks out, this will ultimately lead to the "/refund"
     403             :  * being executed, or rejected.
     404             :  *
     405             :  * @param rh context of the handler
     406             :  * @param connection the MHD connection to handle
     407             :  * @param[in,out] connection_cls the connection's closure (can be updated)
     408             :  * @param upload_data upload data
     409             :  * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
     410             :  * @return MHD result code
     411             :   */
     412             : int
     413           9 : TEH_PAYBACK_handler_payback (struct TEH_RequestHandler *rh,
     414             :                              struct MHD_Connection *connection,
     415             :                              void **connection_cls,
     416             :                              const char *upload_data,
     417             :                              size_t *upload_data_size)
     418             : {
     419             :   json_t *json;
     420             :   int res;
     421             :   struct TALER_CoinPublicInfo coin;
     422             :   struct TALER_DenominationBlindingKeyP coin_bks;
     423             :   struct TALER_CoinSpendSignatureP coin_sig;
     424           9 :   struct GNUNET_JSON_Specification spec[] = {
     425             :     TALER_JSON_spec_denomination_public_key ("denom_pub",
     426             :                                              &coin.denom_pub),
     427             :     TALER_JSON_spec_denomination_signature ("denom_sig",
     428             :                                             &coin.denom_sig),
     429             :     GNUNET_JSON_spec_fixed_auto ("coin_pub",
     430             :                                  &coin.coin_pub),
     431             :     GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
     432             :                                  &coin_bks),
     433             :     GNUNET_JSON_spec_fixed_auto ("coin_sig",
     434             :                                  &coin_sig),
     435             :     GNUNET_JSON_spec_end ()
     436             :   };
     437             : 
     438           9 :   res = TEH_PARSE_post_json (connection,
     439             :                              connection_cls,
     440             :                              upload_data,
     441             :                              upload_data_size,
     442             :                              &json);
     443           9 :   if (GNUNET_SYSERR == res)
     444           0 :     return MHD_NO;
     445           9 :   if ( (GNUNET_NO == res) || (NULL == json) )
     446           6 :     return MHD_YES;
     447           3 :   res = TEH_PARSE_json_data (connection,
     448             :                              json,
     449             :                              spec);
     450           3 :   json_decref (json);
     451           3 :   if (GNUNET_SYSERR == res)
     452           0 :     return MHD_NO; /* hard failure */
     453           3 :   if (GNUNET_NO == res)
     454           0 :     return MHD_YES; /* failure */
     455           3 :   res = verify_and_execute_payback (connection,
     456             :                                     &coin,
     457             :                                     &coin_bks,
     458             :                                     &coin_sig);
     459           3 :   GNUNET_JSON_parse_free (spec);
     460           3 :   return res;
     461             : }
     462             : 
     463             : 
     464             : /* end of taler-exchange-httpd_payback.c */

Generated by: LCOV version 1.13