exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_post-reserves-RESERVE_PUB-attest.c (11560B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2026 Taler Systems SA
      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 lib/exchange_api_post-reserves-RESERVE_PUB-attest.c
     19  * @brief Implementation of the POST /reserves/$RESERVE_PUB/attest requests
     20  * @author Christian Grothoff
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP attest codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_handle.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A POST /reserves/$RID/attest Handle
     35  */
     36 struct TALER_EXCHANGE_PostReservesAttestHandle
     37 {
     38 
     39   /**
     40    * Reference to the execution context.
     41    */
     42   struct GNUNET_CURL_Context *ctx;
     43 
     44   /**
     45    * Base URL of the exchange.
     46    */
     47   char *base_url;
     48 
     49   /**
     50    * The url for this request, set during _start.
     51    */
     52   char *url;
     53 
     54   /**
     55    * Context for #TEH_curl_easy_post(). Keeps the data that must
     56    * persist for Curl to make the upload.
     57    */
     58   struct TALER_CURL_PostContext post_ctx;
     59 
     60   /**
     61    * Handle for the request.
     62    */
     63   struct GNUNET_CURL_Job *job;
     64 
     65   /**
     66    * Function to call with the result.
     67    */
     68   TALER_EXCHANGE_PostReservesAttestCallback cb;
     69 
     70   /**
     71    * Closure for @a cb.
     72    */
     73   TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls;
     74 
     75   /**
     76    * The keys of the exchange this request handle will use.
     77    */
     78   struct TALER_EXCHANGE_Keys *keys;
     79 
     80   /**
     81    * Public key of the reserve we are querying.
     82    */
     83   struct TALER_ReservePublicKeyP reserve_pub;
     84 
     85   /**
     86    * Pre-built JSON body for the request.
     87    */
     88   json_t *body;
     89 
     90 };
     91 
     92 
     93 /**
     94  * We received an #MHD_HTTP_OK attest code. Handle the JSON
     95  * response.
     96  *
     97  * @param prah handle of the request
     98  * @param j JSON response
     99  * @return #GNUNET_OK on success
    100  */
    101 static enum GNUNET_GenericReturnValue
    102 handle_reserves_attest_ok (struct TALER_EXCHANGE_PostReservesAttestHandle *prah,
    103                            const json_t *j)
    104 {
    105   struct TALER_EXCHANGE_PostReservesAttestResponse rs = {
    106     .hr.reply = j,
    107     .hr.http_status = MHD_HTTP_OK
    108   };
    109   const json_t *attributes;
    110   struct GNUNET_JSON_Specification spec[] = {
    111     GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    112                                 &rs.details.ok.exchange_time),
    113     GNUNET_JSON_spec_timestamp ("expiration_time",
    114                                 &rs.details.ok.expiration_time),
    115     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    116                                  &rs.details.ok.exchange_sig),
    117     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    118                                  &rs.details.ok.exchange_pub),
    119     GNUNET_JSON_spec_object_const ("attributes",
    120                                    &attributes),
    121     GNUNET_JSON_spec_end ()
    122   };
    123 
    124   if (GNUNET_OK !=
    125       GNUNET_JSON_parse (j,
    126                          spec,
    127                          NULL,
    128                          NULL))
    129   {
    130     GNUNET_break_op (0);
    131     return GNUNET_SYSERR;
    132   }
    133   if (GNUNET_OK !=
    134       TALER_EXCHANGE_test_signing_key (prah->keys,
    135                                        &rs.details.ok.exchange_pub))
    136   {
    137     GNUNET_break_op (0);
    138     rs.hr.http_status = 0;
    139     rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
    140     prah->cb (prah->cb_cls,
    141               &rs);
    142     prah->cb = NULL;
    143     GNUNET_JSON_parse_free (spec);
    144     return GNUNET_SYSERR;
    145   }
    146   rs.details.ok.attributes = attributes;
    147   if (GNUNET_OK !=
    148       TALER_exchange_online_reserve_attest_details_verify (
    149         rs.details.ok.exchange_time,
    150         rs.details.ok.expiration_time,
    151         &prah->reserve_pub,
    152         attributes,
    153         &rs.details.ok.exchange_pub,
    154         &rs.details.ok.exchange_sig))
    155   {
    156     GNUNET_break_op (0);
    157     GNUNET_JSON_parse_free (spec);
    158     return GNUNET_SYSERR;
    159   }
    160   prah->cb (prah->cb_cls,
    161             &rs);
    162   prah->cb = NULL;
    163   GNUNET_JSON_parse_free (spec);
    164   return GNUNET_OK;
    165 }
    166 
    167 
    168 /**
    169  * Function called when we're done processing the
    170  * HTTP /reserves/$RID/attest request.
    171  *
    172  * @param cls the `struct TALER_EXCHANGE_PostReservesAttestHandle`
    173  * @param response_code HTTP response code, 0 on error
    174  * @param response parsed JSON result, NULL on error
    175  */
    176 static void
    177 handle_reserves_attest_finished (void *cls,
    178                                  long response_code,
    179                                  const void *response)
    180 {
    181   struct TALER_EXCHANGE_PostReservesAttestHandle *prah = cls;
    182   const json_t *j = response;
    183   struct TALER_EXCHANGE_PostReservesAttestResponse rs = {
    184     .hr.reply = j,
    185     .hr.http_status = (unsigned int) response_code
    186   };
    187 
    188   prah->job = NULL;
    189   switch (response_code)
    190   {
    191   case 0:
    192     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    193     break;
    194   case MHD_HTTP_OK:
    195     if (GNUNET_OK !=
    196         handle_reserves_attest_ok (prah,
    197                                    j))
    198     {
    199       rs.hr.http_status = 0;
    200       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    201     }
    202     break;
    203   case MHD_HTTP_BAD_REQUEST:
    204     /* This should never happen, either us or the exchange is buggy
    205        (or API version conflict); just pass JSON reply to the application */
    206     GNUNET_break (0);
    207     rs.hr.ec = TALER_JSON_get_error_code (j);
    208     rs.hr.hint = TALER_JSON_get_error_hint (j);
    209     break;
    210   case MHD_HTTP_FORBIDDEN:
    211     /* This should never happen, either us or the exchange is buggy
    212        (or API version conflict); just pass JSON reply to the application */
    213     GNUNET_break (0);
    214     rs.hr.ec = TALER_JSON_get_error_code (j);
    215     rs.hr.hint = TALER_JSON_get_error_hint (j);
    216     break;
    217   case MHD_HTTP_NOT_FOUND:
    218     /* Nothing really to verify, this should never
    219        happen, we should pass the JSON reply to the application */
    220     rs.hr.ec = TALER_JSON_get_error_code (j);
    221     rs.hr.hint = TALER_JSON_get_error_hint (j);
    222     break;
    223   case MHD_HTTP_CONFLICT:
    224     /* Server doesn't have the requested attributes */
    225     rs.hr.ec = TALER_JSON_get_error_code (j);
    226     rs.hr.hint = TALER_JSON_get_error_hint (j);
    227     break;
    228   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    229     /* Server had an internal issue; we should retry, but this API
    230        leaves this to the application */
    231     rs.hr.ec = TALER_JSON_get_error_code (j);
    232     rs.hr.hint = TALER_JSON_get_error_hint (j);
    233     break;
    234   default:
    235     /* unexpected response code */
    236     GNUNET_break_op (0);
    237     rs.hr.ec = TALER_JSON_get_error_code (j);
    238     rs.hr.hint = TALER_JSON_get_error_hint (j);
    239     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    240                 "Unexpected response code %u/%d for reserves attest\n",
    241                 (unsigned int) response_code,
    242                 (int) rs.hr.ec);
    243     break;
    244   }
    245   if (NULL != prah->cb)
    246   {
    247     prah->cb (prah->cb_cls,
    248               &rs);
    249     prah->cb = NULL;
    250   }
    251   TALER_EXCHANGE_post_reserves_attest_cancel (prah);
    252 }
    253 
    254 
    255 struct TALER_EXCHANGE_PostReservesAttestHandle *
    256 TALER_EXCHANGE_post_reserves_attest_create (
    257   struct GNUNET_CURL_Context *ctx,
    258   const char *url,
    259   struct TALER_EXCHANGE_Keys *keys,
    260   const struct TALER_ReservePrivateKeyP *reserve_priv,
    261   unsigned int attributes_length,
    262   const char *attributes[const static attributes_length])
    263 {
    264   struct TALER_EXCHANGE_PostReservesAttestHandle *prah;
    265   struct TALER_ReserveSignatureP reserve_sig;
    266   json_t *details;
    267   struct GNUNET_TIME_Timestamp ts;
    268 
    269   if (0 == attributes_length)
    270   {
    271     GNUNET_break (0);
    272     return NULL;
    273   }
    274   details = json_array ();
    275   GNUNET_assert (NULL != details);
    276   for (unsigned int i = 0; i < attributes_length; i++)
    277   {
    278     GNUNET_assert (0 ==
    279                    json_array_append_new (details,
    280                                           json_string (attributes[i])));
    281   }
    282   prah = GNUNET_new (struct TALER_EXCHANGE_PostReservesAttestHandle);
    283   prah->ctx = ctx;
    284   prah->base_url = GNUNET_strdup (url);
    285   prah->keys = TALER_EXCHANGE_keys_incref (keys);
    286   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    287                                       &prah->reserve_pub.eddsa_pub);
    288   ts = GNUNET_TIME_timestamp_get ();
    289   TALER_wallet_reserve_attest_request_sign (ts,
    290                                             details,
    291                                             reserve_priv,
    292                                             &reserve_sig);
    293   prah->body = GNUNET_JSON_PACK (
    294     GNUNET_JSON_pack_data_auto ("reserve_sig",
    295                                 &reserve_sig),
    296     GNUNET_JSON_pack_timestamp ("request_timestamp",
    297                                 ts),
    298     GNUNET_JSON_pack_array_steal ("details",
    299                                   details));
    300   if (NULL == prah->body)
    301   {
    302     GNUNET_break (0);
    303     GNUNET_free (prah->base_url);
    304     TALER_EXCHANGE_keys_decref (prah->keys);
    305     GNUNET_free (prah);
    306     return NULL;
    307   }
    308   return prah;
    309 }
    310 
    311 
    312 enum TALER_ErrorCode
    313 TALER_EXCHANGE_post_reserves_attest_start (
    314   struct TALER_EXCHANGE_PostReservesAttestHandle *prah,
    315   TALER_EXCHANGE_PostReservesAttestCallback cb,
    316   TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls)
    317 {
    318   CURL *eh;
    319   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
    320 
    321   prah->cb = cb;
    322   prah->cb_cls = cb_cls;
    323   {
    324     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
    325     char *end;
    326 
    327     end = GNUNET_STRINGS_data_to_string (
    328       &prah->reserve_pub,
    329       sizeof (prah->reserve_pub),
    330       pub_str,
    331       sizeof (pub_str));
    332     *end = '\0';
    333     GNUNET_snprintf (arg_str,
    334                      sizeof (arg_str),
    335                      "reserves/%s/attest",
    336                      pub_str);
    337   }
    338   prah->url = TALER_url_join (prah->base_url,
    339                               arg_str,
    340                               NULL);
    341   if (NULL == prah->url)
    342     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    343   eh = TALER_EXCHANGE_curl_easy_get_ (prah->url);
    344   if ( (NULL == eh) ||
    345        (GNUNET_OK !=
    346         TALER_curl_easy_post (&prah->post_ctx,
    347                               eh,
    348                               prah->body)) )
    349   {
    350     GNUNET_break (0);
    351     if (NULL != eh)
    352       curl_easy_cleanup (eh);
    353     GNUNET_free (prah->url);
    354     prah->url = NULL;
    355     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    356   }
    357   prah->job = GNUNET_CURL_job_add2 (prah->ctx,
    358                                     eh,
    359                                     prah->post_ctx.headers,
    360                                     &handle_reserves_attest_finished,
    361                                     prah);
    362   if (NULL == prah->job)
    363   {
    364     TALER_curl_easy_post_finished (&prah->post_ctx);
    365     GNUNET_free (prah->url);
    366     prah->url = NULL;
    367     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    368   }
    369   return TALER_EC_NONE;
    370 }
    371 
    372 
    373 void
    374 TALER_EXCHANGE_post_reserves_attest_cancel (
    375   struct TALER_EXCHANGE_PostReservesAttestHandle *prah)
    376 {
    377   if (NULL != prah->job)
    378   {
    379     GNUNET_CURL_job_cancel (prah->job);
    380     prah->job = NULL;
    381   }
    382   TALER_curl_easy_post_finished (&prah->post_ctx);
    383   json_decref (prah->body);
    384   GNUNET_free (prah->url);
    385   GNUNET_free (prah->base_url);
    386   TALER_EXCHANGE_keys_decref (prah->keys);
    387   GNUNET_free (prah);
    388 }
    389 
    390 
    391 /* end of exchange_api_post-reserves-RESERVE_PUB-attest.c */