exchange

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

exchange_api_get-keys.c (17572B)


      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_get-keys.c
     19  * @brief Implementation of GET /keys
     20  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
     21  * @author Christian Grothoff
     22  */
     23 #include "platform.h"  /* UNNECESSARY? */
     24 #include <microhttpd.h>
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "taler/taler_exchange_service.h"  /* UNNECESSARY? */
     28 #include "taler/taler_signatures.h"
     29 #include "exchange_api_handle.h"
     30 #include "exchange_api_curl_defaults.h"
     31 #include "taler/taler_curl_lib.h"
     32 
     33 /**
     34  * If the "Expire" cache control header is missing, for
     35  * how long do we assume the reply to be valid at least?
     36  */
     37 #define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
     38 
     39 /**
     40  * If the "Expire" cache control header is missing, for
     41  * how long do we assume the reply to be valid at least?
     42  */
     43 #define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
     44           GNUNET_TIME_UNIT_MINUTES, 2)
     45 
     46 /**
     47  * Define a max length for the HTTP "Expire:" header
     48  */
     49 #define MAX_DATE_LINE_LEN 32
     50 
     51 
     52 /**
     53  * Handle for a GET /keys request.
     54  */
     55 struct TALER_EXCHANGE_GetKeysHandle
     56 {
     57 
     58   /**
     59    * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
     60    */
     61   char *exchange_url;
     62 
     63   /**
     64    * The url for the /keys request, set during _start.
     65    */
     66   char *url;
     67 
     68   /**
     69    * Previous /keys response, NULL for none.
     70    */
     71   struct TALER_EXCHANGE_Keys *prev_keys;
     72 
     73   /**
     74    * Entry for this request with the `struct GNUNET_CURL_Context`.
     75    */
     76   struct GNUNET_CURL_Job *job;
     77 
     78   /**
     79    * Expiration time according to "Expire:" header.
     80    * 0 if not provided by the server.
     81    */
     82   struct GNUNET_TIME_Timestamp expire;
     83 
     84   /**
     85    * Function to call with the exchange's certification data,
     86    * NULL if this has already been done.
     87    */
     88   TALER_EXCHANGE_GetKeysCallback cert_cb;
     89 
     90   /**
     91    * Closure to pass to @e cert_cb.
     92    */
     93   TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls;
     94 
     95   /**
     96    * Reference to the execution context.
     97    */
     98   struct GNUNET_CURL_Context *ctx;
     99 
    100 };
    101 
    102 
    103 /**
    104  * Parse HTTP timestamp.
    105  *
    106  * @param dateline header to parse header
    107  * @param[out] at where to write the result
    108  * @return #GNUNET_OK on success
    109  */
    110 static enum GNUNET_GenericReturnValue
    111 parse_date_string (const char *dateline,
    112                    struct GNUNET_TIME_Timestamp *at)
    113 {
    114   static const char *MONTHS[] =
    115   { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    116     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
    117   int year;
    118   int mon;
    119   int day;
    120   int hour;
    121   int min;
    122   int sec;
    123   char month[4];
    124   struct tm tm;
    125   time_t t;
    126 
    127   /* We recognize the three formats in RFC2616, section 3.3.1.  Month
    128      names are always in English.  The formats are:
    129       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
    130       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
    131       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
    132      Note that the first is preferred.
    133    */
    134 
    135   if (strlen (dateline) > MAX_DATE_LINE_LEN)
    136   {
    137     GNUNET_break_op (0);
    138     return GNUNET_SYSERR;
    139   }
    140   while (*dateline == ' ')
    141     ++dateline;
    142   while (*dateline && *dateline != ' ')
    143     ++dateline;
    144   while (*dateline == ' ')
    145     ++dateline;
    146   /* We just skipped over the day of the week. Now we have:*/
    147   if ( (sscanf (dateline,
    148                 "%d %3s %d %d:%d:%d",
    149                 &day, month, &year, &hour, &min, &sec) != 6) &&
    150        (sscanf (dateline,
    151                 "%d-%3s-%d %d:%d:%d",
    152                 &day, month, &year, &hour, &min, &sec) != 6) &&
    153        (sscanf (dateline,
    154                 "%3s %d %d:%d:%d %d",
    155                 month, &day, &hour, &min, &sec, &year) != 6) )
    156   {
    157     GNUNET_break (0);
    158     return GNUNET_SYSERR;
    159   }
    160   /* Two digit dates are defined to be relative to 1900; all other dates
    161    * are supposed to be represented as four digits. */
    162   if (year < 100)
    163     year += 1900;
    164 
    165   for (mon = 0; ; mon++)
    166   {
    167     if (! MONTHS[mon])
    168     {
    169       GNUNET_break_op (0);
    170       return GNUNET_SYSERR;
    171     }
    172     if (0 == strcasecmp (month,
    173                          MONTHS[mon]))
    174       break;
    175   }
    176 
    177   memset (&tm, 0, sizeof(tm));
    178   tm.tm_year = year - 1900;
    179   tm.tm_mon = mon;
    180   tm.tm_mday = day;
    181   tm.tm_hour = hour;
    182   tm.tm_min = min;
    183   tm.tm_sec = sec;
    184 
    185   t = mktime (&tm);
    186   if (((time_t) -1) == t)
    187   {
    188     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    189                          "mktime");
    190     return GNUNET_SYSERR;
    191   }
    192   if (t < 0)
    193     t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
    194   *at = GNUNET_TIME_timestamp_from_s (t);
    195   return GNUNET_OK;
    196 }
    197 
    198 
    199 /**
    200  * Function called for each header in the HTTP /keys response.
    201  * Finds the "Expire:" header and parses it, storing the result
    202  * in the "expire" field of the keys request.
    203  *
    204  * @param buffer header data received
    205  * @param size size of an item in @a buffer
    206  * @param nitems number of items in @a buffer
    207  * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
    208  * @return `size * nitems` on success (everything else aborts)
    209  */
    210 static size_t
    211 header_cb (char *buffer,
    212            size_t size,
    213            size_t nitems,
    214            void *userdata)
    215 {
    216   struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
    217   size_t total = size * nitems;
    218   char *val;
    219 
    220   if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
    221     return total;
    222   if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
    223                         buffer,
    224                         strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
    225     return total;
    226   val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
    227                         total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
    228   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    229               "Found %s header `%s'\n",
    230               MHD_HTTP_HEADER_EXPIRES,
    231               val);
    232   if (GNUNET_OK !=
    233       parse_date_string (val,
    234                          &kr->expire))
    235   {
    236     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    237                 "Failed to parse %s-header `%s'\n",
    238                 MHD_HTTP_HEADER_EXPIRES,
    239                 val);
    240     kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
    241   }
    242   GNUNET_free (val);
    243   return total;
    244 }
    245 
    246 
    247 /**
    248  * Callback used when downloading the reply to a /keys request
    249  * is complete.
    250  *
    251  * @param cls the `struct TALER_EXCHANGE_GetKeysHandle`
    252  * @param response_code HTTP response code, 0 on error
    253  * @param resp_obj parsed JSON result, NULL on error
    254  */
    255 static void
    256 keys_completed_cb (void *cls,
    257                    long response_code,
    258                    const void *resp_obj)
    259 {
    260   struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
    261   const json_t *j = resp_obj;
    262   struct TALER_EXCHANGE_Keys *kd = NULL;
    263   struct TALER_EXCHANGE_KeysResponse kresp = {
    264     .hr.reply = j,
    265     .hr.http_status = (unsigned int) response_code,
    266     .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
    267   };
    268 
    269   gkh->job = NULL;
    270   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    271               "Received keys from URL `%s' with status %ld and expiration %s.\n",
    272               gkh->url,
    273               response_code,
    274               GNUNET_TIME_timestamp2s (gkh->expire));
    275   if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
    276   {
    277     if (MHD_HTTP_OK == response_code)
    278       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    279                   "Exchange failed to give expiration time, assuming in %s\n",
    280                   GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
    281                                           true));
    282     gkh->expire
    283       = GNUNET_TIME_absolute_to_timestamp (
    284           GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
    285   }
    286   switch (response_code)
    287   {
    288   case 0:
    289     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    290                 "Failed to receive /keys response from exchange %s\n",
    291                 gkh->exchange_url);
    292     break;
    293   case MHD_HTTP_OK:
    294     if (NULL == j)
    295     {
    296       GNUNET_break (0);
    297       kresp.hr.http_status = 0;
    298       break;
    299     }
    300     kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
    301     kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
    302     if (NULL != gkh->prev_keys)
    303     {
    304       const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
    305 
    306       /* We keep the denomination keys and auditor signatures from the
    307          previous iteration (/keys cherry picking) */
    308       kd->num_denom_keys
    309         = kd_old->num_denom_keys;
    310       kd->last_denom_issue_date
    311         = kd_old->last_denom_issue_date;
    312       GNUNET_array_grow (kd->denom_keys,
    313                          kd->denom_keys_size,
    314                          kd->num_denom_keys);
    315       /* First make a shallow copy, we then need another pass for the RSA key... */
    316       GNUNET_memcpy (kd->denom_keys,
    317                      kd_old->denom_keys,
    318                      kd_old->num_denom_keys
    319                      * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
    320       for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
    321         TALER_denom_pub_copy (&kd->denom_keys[i].key,
    322                               &kd_old->denom_keys[i].key);
    323       kd->num_auditors = kd_old->num_auditors;
    324       kd->auditors
    325         = GNUNET_new_array (kd->num_auditors,
    326                             struct TALER_EXCHANGE_AuditorInformation);
    327       /* Now the necessary deep copy... */
    328       for (unsigned int i = 0; i<kd_old->num_auditors; i++)
    329       {
    330         const struct TALER_EXCHANGE_AuditorInformation *aold =
    331           &kd_old->auditors[i];
    332         struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
    333 
    334         anew->auditor_pub = aold->auditor_pub;
    335         anew->auditor_url = GNUNET_strdup (aold->auditor_url);
    336         anew->auditor_name = GNUNET_strdup (aold->auditor_name);
    337         GNUNET_array_grow (anew->denom_keys,
    338                            anew->num_denom_keys,
    339                            aold->num_denom_keys);
    340         GNUNET_memcpy (
    341           anew->denom_keys,
    342           aold->denom_keys,
    343           aold->num_denom_keys
    344           * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
    345       }
    346     }
    347     /* Now decode fresh /keys response */
    348     if (GNUNET_OK !=
    349         TALER_EXCHANGE_decode_keys_json_ (j,
    350                                           true,
    351                                           kd,
    352                                           &kresp.details.ok.compat))
    353     {
    354       TALER_LOG_ERROR ("Could not decode /keys response\n");
    355       kd->rc = 1;
    356       TALER_EXCHANGE_keys_decref (kd);
    357       kd = NULL;
    358       kresp.hr.http_status = 0;
    359       kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    360       break;
    361     }
    362     if (NULL != gkh->prev_keys)
    363     {
    364       const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
    365 
    366       /* The denominations and auditor signatures we cherry-picked from
    367          the previous response were validated against the master public
    368          key of that response; refuse to re-attribute them to a
    369          different master public key (or currency). */
    370       if ( (0 !=
    371             GNUNET_memcmp (&kd->master_pub,
    372                            &kd_old->master_pub)) ||
    373            (0 !=
    374             strcmp (kd->currency,
    375                     kd_old->currency)) )
    376       {
    377         GNUNET_break_op (0);
    378         TALER_LOG_ERROR (
    379           "Exchange changed master public key or currency in /keys\n");
    380         kd->rc = 1;
    381         TALER_EXCHANGE_keys_decref (kd);
    382         kd = NULL;
    383         kresp.hr.http_status = 0;
    384         kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    385         break;
    386       }
    387     }
    388     kd->rc = 1;
    389     kd->key_data_expiration = gkh->expire;
    390     if (GNUNET_TIME_relative_cmp (
    391           GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
    392           <,
    393           MINIMUM_EXPIRATION))
    394     {
    395       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    396                   "Exchange returned keys with expiration time below %s. Compensating.\n",
    397                   GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
    398                                           true));
    399       kd->key_data_expiration
    400         = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
    401     }
    402 
    403     kresp.details.ok.keys = kd;
    404     break;
    405   case MHD_HTTP_BAD_REQUEST:
    406   case MHD_HTTP_UNAUTHORIZED:
    407   case MHD_HTTP_FORBIDDEN:
    408   case MHD_HTTP_NOT_FOUND:
    409     if (NULL == j)
    410     {
    411       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    412       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    413     }
    414     else
    415     {
    416       kresp.hr.ec = TALER_JSON_get_error_code (j);
    417       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    418     }
    419     break;
    420   default:
    421     if (NULL == j)
    422     {
    423       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    424       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    425     }
    426     else
    427     {
    428       kresp.hr.ec = TALER_JSON_get_error_code (j);
    429       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    430     }
    431     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    432                 "Unexpected response code %u/%d\n",
    433                 (unsigned int) response_code,
    434                 (int) kresp.hr.ec);
    435     break;
    436   }
    437   gkh->cert_cb (gkh->cert_cb_cls,
    438                 &kresp,
    439                 kd);
    440   TALER_EXCHANGE_get_keys_cancel (gkh);
    441 }
    442 
    443 
    444 struct TALER_EXCHANGE_GetKeysHandle *
    445 TALER_EXCHANGE_get_keys_create (
    446   struct GNUNET_CURL_Context *ctx,
    447   const char *url)
    448 {
    449   struct TALER_EXCHANGE_GetKeysHandle *gkh;
    450 
    451   gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
    452   gkh->ctx = ctx;
    453   gkh->exchange_url = GNUNET_strdup (url);
    454   return gkh;
    455 }
    456 
    457 
    458 enum GNUNET_GenericReturnValue
    459 TALER_EXCHANGE_get_keys_set_options_ (
    460   struct TALER_EXCHANGE_GetKeysHandle *gkh,
    461   unsigned int num_options,
    462   const struct TALER_EXCHANGE_GetKeysOptionValue *options)
    463 {
    464   for (unsigned int i = 0; i < num_options; i++)
    465   {
    466     const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];
    467 
    468     switch (opt->option)
    469     {
    470     case TALER_EXCHANGE_GET_KEYS_OPTION_END:
    471       return GNUNET_OK;
    472     case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
    473       TALER_EXCHANGE_keys_decref (gkh->prev_keys);
    474       gkh->prev_keys = NULL;
    475       if (NULL != opt->details.last_keys)
    476         gkh->prev_keys
    477           = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
    478       break;
    479     }
    480   }
    481   return GNUNET_OK;
    482 }
    483 
    484 
    485 enum TALER_ErrorCode
    486 TALER_EXCHANGE_get_keys_start (
    487   struct TALER_EXCHANGE_GetKeysHandle *gkh,
    488   TALER_EXCHANGE_GetKeysCallback cert_cb,
    489   TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls)
    490 {
    491   CURL *eh;
    492   char last_date[80] = { 0 };
    493 
    494   gkh->cert_cb = cert_cb;
    495   gkh->cert_cb_cls = cert_cb_cls;
    496   if (NULL != gkh->prev_keys)
    497   {
    498     TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
    499                      GNUNET_TIME_timestamp2s (
    500                        gkh->prev_keys->last_denom_issue_date));
    501     GNUNET_snprintf (last_date,
    502                      sizeof (last_date),
    503                      "%llu",
    504                      (unsigned long long)
    505                      gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
    506                      / 1000000LLU);
    507   }
    508   gkh->url = TALER_url_join (gkh->exchange_url,
    509                              "keys",
    510                              (NULL != gkh->prev_keys)
    511                              ? "last_issue_date"
    512                              : NULL,
    513                              (NULL != gkh->prev_keys)
    514                              ? last_date
    515                              : NULL,
    516                              NULL);
    517   if (NULL == gkh->url)
    518   {
    519     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    520                 "Could not construct request URL.\n");
    521     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    522   }
    523   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    524               "Requesting keys with URL `%s'.\n",
    525               gkh->url);
    526   eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
    527   if (NULL == eh)
    528   {
    529     GNUNET_break (0);
    530     GNUNET_free (gkh->url);
    531     gkh->url = NULL;
    532     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    533   }
    534   GNUNET_break (CURLE_OK ==
    535                 curl_easy_setopt (eh,
    536                                   CURLOPT_VERBOSE,
    537                                   0));
    538   GNUNET_break (CURLE_OK ==
    539                 curl_easy_setopt (eh,
    540                                   CURLOPT_TIMEOUT,
    541                                   120 /* seconds */));
    542   GNUNET_assert (CURLE_OK ==
    543                  curl_easy_setopt (eh,
    544                                    CURLOPT_HEADERFUNCTION,
    545                                    &header_cb));
    546   GNUNET_assert (CURLE_OK ==
    547                  curl_easy_setopt (eh,
    548                                    CURLOPT_HEADERDATA,
    549                                    gkh));
    550   gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
    551                                                eh,
    552                                                &keys_completed_cb,
    553                                                gkh);
    554   if (NULL == gkh->job)
    555   {
    556     GNUNET_free (gkh->url);
    557     gkh->url = NULL;
    558     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    559   }
    560   return TALER_EC_NONE;
    561 }
    562 
    563 
    564 void
    565 TALER_EXCHANGE_get_keys_cancel (
    566   struct TALER_EXCHANGE_GetKeysHandle *gkh)
    567 {
    568   if (NULL != gkh->job)
    569   {
    570     GNUNET_CURL_job_cancel (gkh->job);
    571     gkh->job = NULL;
    572   }
    573   TALER_EXCHANGE_keys_decref (gkh->prev_keys);
    574   GNUNET_free (gkh->exchange_url);
    575   GNUNET_free (gkh->url);
    576   GNUNET_free (gkh);
    577 }
    578 
    579 
    580 /* end of exchange_api_get-keys.c */