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

Generated by: LCOV version 1.13