LCOV - code coverage report
Current view: top level - exchange-lib - exchange_api_payback.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 69 99 69.7 %
Date: 2017-11-25 11:31:41 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2017 GNUnet e.V. and Inria
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU 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 General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see
      15             :   <http://www.gnu.org/licenses/>
      16             : */
      17             : /**
      18             :  * @file exchange-lib/exchange_api_payback.c
      19             :  * @brief Implementation of the /payback request of the exchange's HTTP API
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <curl/curl.h>
      24             : #include <jansson.h>
      25             : #include <microhttpd.h> /* just for HTTP status codes */
      26             : #include <gnunet/gnunet_util_lib.h>
      27             : #include <gnunet/gnunet_json_lib.h>
      28             : #include <gnunet/gnunet_curl_lib.h>
      29             : #include "taler_json_lib.h"
      30             : #include "taler_exchange_service.h"
      31             : #include "exchange_api_handle.h"
      32             : #include "taler_signatures.h"
      33             : 
      34             : 
      35             : /**
      36             :  * @brief A Payback Handle
      37             :  */
      38             : struct TALER_EXCHANGE_PaybackHandle
      39             : {
      40             : 
      41             :   /**
      42             :    * The connection to exchange this request handle will use
      43             :    */
      44             :   struct TALER_EXCHANGE_Handle *exchange;
      45             : 
      46             :   /**
      47             :    * The url for this request.
      48             :    */
      49             :   char *url;
      50             : 
      51             :   /**
      52             :    * JSON encoding of the request to POST.
      53             :    */
      54             :   char *json_enc;
      55             : 
      56             :   /**
      57             :    * Denomination key of the coin.
      58             :    */
      59             :   const struct TALER_EXCHANGE_DenomPublicKey *pk;
      60             : 
      61             :   /**
      62             :    * Handle for the request.
      63             :    */
      64             :   struct GNUNET_CURL_Job *job;
      65             : 
      66             :   /**
      67             :    * Function to call with the result.
      68             :    */
      69             :   TALER_EXCHANGE_PaybackResultCallback cb;
      70             : 
      71             :   /**
      72             :    * Closure for @a cb.
      73             :    */
      74             :   void *cb_cls;
      75             : 
      76             :   /**
      77             :    * Public key of the coin we are trying to get paid back.
      78             :    */
      79             :   struct TALER_CoinSpendPublicKeyP coin_pub;
      80             : 
      81             : };
      82             : 
      83             : 
      84             : /**
      85             :  * Verify that the signature on the "200 OK" response
      86             :  * from the exchange is valid. If it is, call the
      87             :  * callback.
      88             :  *
      89             :  * @param ph payback handle
      90             :  * @param json json reply with the signature
      91             :  * @return #GNUNET_OK if the signature is valid and we called the callback;
      92             :  *         #GNUNET_SYSERR if not (callback must still be called)
      93             :  */
      94             : static int
      95           2 : verify_payback_signature_ok (const struct TALER_EXCHANGE_PaybackHandle *ph,
      96             :                              const json_t *json)
      97             : {
      98             :   struct TALER_PaybackConfirmationPS pc;
      99             :   struct TALER_ExchangePublicKeyP exchange_pub;
     100             :   struct TALER_ExchangeSignatureP exchange_sig;
     101             :   struct TALER_Amount amount;
     102             :   struct GNUNET_TIME_Absolute timestamp;
     103             :   const struct TALER_EXCHANGE_Keys *key_state;
     104           2 :   struct GNUNET_JSON_Specification spec[] = {
     105             :     GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
     106             :     GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub),
     107             :     TALER_JSON_spec_amount ("amount", &amount),
     108             :     GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
     109             :     GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub),
     110             :     GNUNET_JSON_spec_end()
     111             :   };
     112             : 
     113           2 :   if (GNUNET_OK !=
     114           2 :       GNUNET_JSON_parse (json,
     115             :                          spec,
     116             :                          NULL, NULL))
     117             :   {
     118           0 :     GNUNET_break_op (0);
     119           0 :     return GNUNET_SYSERR;
     120             :   }
     121           2 :   key_state = TALER_EXCHANGE_get_keys (ph->exchange);
     122           2 :   if (GNUNET_OK !=
     123           2 :       TALER_EXCHANGE_test_signing_key (key_state,
     124             :                                        &exchange_pub))
     125             :   {
     126           0 :     GNUNET_break_op (0);
     127           0 :     return GNUNET_SYSERR;
     128             :   }
     129           2 :   pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
     130           2 :   pc.purpose.size = htonl (sizeof (pc));
     131           2 :   pc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
     132           2 :   TALER_amount_hton (&pc.payback_amount,
     133             :                      &amount);
     134           2 :   pc.coin_pub = ph->coin_pub;
     135           2 :   if (GNUNET_OK !=
     136           2 :       GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK,
     137             :                                   &pc.purpose,
     138             :                                   &exchange_sig.eddsa_signature,
     139             :                                   &exchange_pub.eddsa_pub))
     140             :   {
     141           0 :     GNUNET_break_op (0);
     142           0 :     return GNUNET_SYSERR;
     143             :   }
     144           2 :   ph->cb (ph->cb_cls,
     145             :           MHD_HTTP_OK,
     146             :           TALER_EC_NONE,
     147             :           &amount,
     148             :           timestamp,
     149             :           &pc.reserve_pub,
     150             :           json);
     151           2 :   return GNUNET_OK;
     152             : }
     153             : 
     154             : 
     155             : /**
     156             :  * Function called when we're done processing the
     157             :  * HTTP /payback request.
     158             :  *
     159             :  * @param cls the `struct TALER_EXCHANGE_PaybackHandle`
     160             :  * @param response_code HTTP response code, 0 on error
     161             :  * @param json parsed JSON result, NULL on error
     162             :  */
     163             : static void
     164           3 : handle_payback_finished (void *cls,
     165             :                          long response_code,
     166             :                          const json_t *json)
     167             : {
     168           3 :   struct TALER_EXCHANGE_PaybackHandle *ph = cls;
     169             : 
     170           3 :   ph->job = NULL;
     171           3 :   switch (response_code)
     172             :   {
     173             :   case 0:
     174           0 :     break;
     175             :   case MHD_HTTP_OK:
     176           2 :     if (GNUNET_OK !=
     177           2 :         verify_payback_signature_ok (ph,
     178             :                                      json))
     179             :     {
     180           0 :       GNUNET_break_op (0);
     181           0 :       response_code = 0;
     182             :     }
     183           2 :     TALER_EXCHANGE_payback_cancel (ph);
     184           2 :     return;
     185             :   case MHD_HTTP_BAD_REQUEST:
     186             :     /* This should never happen, either us or the exchange is buggy
     187             :        (or API version conflict); just pass JSON reply to the application */
     188           0 :     break;
     189             :   case MHD_HTTP_FORBIDDEN:
     190             :     {
     191             :       /* Insufficient funds, proof attached */
     192             :       json_t *history;
     193             :       struct TALER_Amount total;
     194             :       const struct TALER_EXCHANGE_DenomPublicKey *dki;
     195             : 
     196           1 :       dki = ph->pk;
     197           1 :       history = json_object_get (json,
     198             :                                  "history");
     199           1 :       if (GNUNET_OK !=
     200           1 :           TALER_EXCHANGE_verify_coin_history (dki->fee_deposit.currency,
     201           1 :                                               &ph->coin_pub,
     202             :                                               history,
     203             :                                               &total))
     204             :       {
     205           0 :         GNUNET_break_op (0);
     206           0 :         response_code = 0;
     207             :       }
     208           1 :       ph->cb (ph->cb_cls,
     209             :               response_code,
     210             :               TALER_JSON_get_error_code (json),
     211             :               &total,
     212             :               GNUNET_TIME_UNIT_FOREVER_ABS,
     213             :               NULL,
     214             :               json);
     215           1 :       TALER_EXCHANGE_payback_cancel (ph);
     216           1 :       return;
     217             :     }
     218             :   case MHD_HTTP_UNAUTHORIZED:
     219             :     /* Nothing really to verify, exchange says one of the signatures is
     220             :        invalid; as we checked them, this should never happen, we
     221             :        should pass the JSON reply to the application */
     222           0 :     break;
     223             :   case MHD_HTTP_NOT_FOUND:
     224             :     /* Nothing really to verify, this should never
     225             :        happen, we should pass the JSON reply to the application */
     226           0 :     break;
     227             :   case MHD_HTTP_GONE:
     228             :     /* Kind of normal: the money was already sent to the merchant
     229             :        (it was too late for the refund). */
     230           0 :     break;
     231             :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     232             :     /* Server had an internal issue; we should retry, but this API
     233             :        leaves this to the application */
     234           0 :     break;
     235             :   default:
     236             :     /* unexpected response code */
     237           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     238             :                 "Unexpected response code %u\n",
     239             :                 (unsigned int) response_code);
     240           0 :     GNUNET_break (0);
     241           0 :     response_code = 0;
     242           0 :     break;
     243             :   }
     244           0 :   ph->cb (ph->cb_cls,
     245             :           response_code,
     246             :           TALER_JSON_get_error_code (json),
     247             :           NULL,
     248             :           GNUNET_TIME_UNIT_FOREVER_ABS,
     249             :           NULL,
     250             :           json);
     251           0 :   TALER_EXCHANGE_payback_cancel (ph);
     252             : }
     253             : 
     254             : 
     255             : /**
     256             :  * Ask the exchange to pay back a coin due to the exchange triggering
     257             :  * the emergency payback protocol for a given denomination.  The value
     258             :  * of the coin will be refunded to the original customer (without fees).
     259             :  *
     260             :  * @param exchange the exchange handle; the exchange must be ready to operate
     261             :  * @param pk kind of coin to pay back
     262             :  * @param denom_sig signature over the coin by the exchange using @a pk
     263             :  * @param ps secret internals of the original planchet
     264             :  * @param payback_cb the callback to call when the final result for this request is available
     265             :  * @param payback_cb_cls closure for @a payback_cb
     266             :  * @return NULL
     267             :  *         if the inputs are invalid (i.e. denomination key not with this exchange).
     268             :  *         In this case, the callback is not called.
     269             :  */
     270             : struct TALER_EXCHANGE_PaybackHandle *
     271           3 : TALER_EXCHANGE_payback (struct TALER_EXCHANGE_Handle *exchange,
     272             :                         const struct TALER_EXCHANGE_DenomPublicKey *pk,
     273             :                         const struct TALER_DenominationSignature *denom_sig,
     274             :                         const struct TALER_PlanchetSecretsP *ps,
     275             :                         TALER_EXCHANGE_PaybackResultCallback payback_cb,
     276             :                         void *payback_cb_cls)
     277             : {
     278             :   struct TALER_EXCHANGE_PaybackHandle *ph;
     279             :   struct GNUNET_CURL_Context *ctx;
     280             :   struct TALER_PaybackRequestPS pr;
     281             :   struct TALER_CoinSpendSignatureP coin_sig;
     282             :   json_t *payback_obj;
     283             :   CURL *eh;
     284             : 
     285           3 :   GNUNET_assert (GNUNET_YES ==
     286             :                  MAH_handle_is_ready (exchange));
     287           3 :   pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK);
     288           3 :   pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS));
     289           3 :   GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
     290             :                                       &pr.coin_pub.eddsa_pub);
     291           3 :   pr.h_denom_pub = pk->h_key;
     292           3 :   pr.coin_blind = ps->blinding_key;
     293           3 :   GNUNET_assert (GNUNET_OK ==
     294             :                  GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv,
     295             :                                            &pr.purpose,
     296             :                                            &coin_sig.eddsa_signature));
     297             : 
     298           9 :   payback_obj = json_pack ("{s:o, s:o," /* denom pub/sig */
     299             :                            " s:o, s:o," /* coin pub/sig */
     300             :                            " s:o}", /* coin_bks */
     301           3 :                            "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key),
     302           3 :                            "denom_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature),
     303             :                            "coin_pub", GNUNET_JSON_from_data_auto (&pr.coin_pub),
     304             :                            "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig),
     305           3 :                            "coin_blind_key_secret", GNUNET_JSON_from_data_auto (&ps->blinding_key)
     306             :                           );
     307           3 :   if (NULL == payback_obj)
     308             :   {
     309           0 :     GNUNET_break (0);
     310           0 :     return NULL;
     311             :   }
     312             : 
     313           3 :   ph = GNUNET_new (struct TALER_EXCHANGE_PaybackHandle);
     314           3 :   ph->coin_pub = pr.coin_pub;
     315           3 :   ph->exchange = exchange;
     316           3 :   ph->pk = pk;
     317           3 :   ph->cb = payback_cb;
     318           3 :   ph->cb_cls = payback_cb_cls;
     319           3 :   ph->url = MAH_path_to_url (exchange, "/payback");
     320             : 
     321           3 :   eh = curl_easy_init ();
     322           3 :   ph->json_enc = json_dumps (payback_obj,
     323             :                              JSON_COMPACT);
     324           3 :   json_decref (payback_obj);
     325           3 :   if (NULL == ph->json_enc)
     326             :   {
     327           0 :     GNUNET_break (0);
     328           0 :     GNUNET_free (ph->url);
     329           0 :     GNUNET_free (ph);
     330           0 :     return NULL;
     331             :   }
     332           3 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     333             :               "URL for payback: `%s'\n",
     334             :               ph->url);
     335           3 :   GNUNET_assert (CURLE_OK ==
     336             :                  curl_easy_setopt (eh,
     337             :                                    CURLOPT_URL,
     338             :                                    ph->url));
     339           3 :   GNUNET_assert (CURLE_OK ==
     340             :                  curl_easy_setopt (eh,
     341             :                                    CURLOPT_POSTFIELDS,
     342             :                                    ph->json_enc));
     343           3 :   GNUNET_assert (CURLE_OK ==
     344             :                  curl_easy_setopt (eh,
     345             :                                    CURLOPT_POSTFIELDSIZE,
     346             :                                    strlen (ph->json_enc)));
     347           3 :   ctx = MAH_handle_to_context (exchange);
     348           3 :   ph->job = GNUNET_CURL_job_add (ctx,
     349             :                                  eh,
     350             :                                  GNUNET_YES,
     351             :                                  &handle_payback_finished,
     352             :                                  ph);
     353           3 :   return ph;
     354             : }
     355             : 
     356             : 
     357             : /**
     358             :  * Cancel a payback request.  This function cannot be used on a
     359             :  * request handle if the callback was already invoked.
     360             :  *
     361             :  * @param ph the payback handle
     362             :  */
     363             : void
     364           3 : TALER_EXCHANGE_payback_cancel (struct TALER_EXCHANGE_PaybackHandle *ph)
     365             : {
     366           3 :   if (NULL != ph->job)
     367             :   {
     368           0 :     GNUNET_CURL_job_cancel (ph->job);
     369           0 :     ph->job = NULL;
     370             :   }
     371           3 :   GNUNET_free (ph->url);
     372           3 :   GNUNET_free (ph->json_enc);
     373           3 :   GNUNET_free (ph);
     374           3 : }
     375             : 
     376             : 
     377             : /* end of exchange_api_payback.c */

Generated by: LCOV version 1.13