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 (16625B)


      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 "taler/platform.h"
     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"
     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       response_code = 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     kd->rc = 1;
    363     kd->key_data_expiration = gkh->expire;
    364     if (GNUNET_TIME_relative_cmp (
    365           GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
    366           <,
    367           MINIMUM_EXPIRATION))
    368     {
    369       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    370                   "Exchange returned keys with expiration time below %s. Compensating.\n",
    371                   GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
    372                                           true));
    373       kd->key_data_expiration
    374         = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
    375     }
    376 
    377     kresp.details.ok.keys = kd;
    378     break;
    379   case MHD_HTTP_BAD_REQUEST:
    380   case MHD_HTTP_UNAUTHORIZED:
    381   case MHD_HTTP_FORBIDDEN:
    382   case MHD_HTTP_NOT_FOUND:
    383     if (NULL == j)
    384     {
    385       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    386       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    387     }
    388     else
    389     {
    390       kresp.hr.ec = TALER_JSON_get_error_code (j);
    391       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    392     }
    393     break;
    394   default:
    395     if (NULL == j)
    396     {
    397       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    398       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    399     }
    400     else
    401     {
    402       kresp.hr.ec = TALER_JSON_get_error_code (j);
    403       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    404     }
    405     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    406                 "Unexpected response code %u/%d\n",
    407                 (unsigned int) response_code,
    408                 (int) kresp.hr.ec);
    409     break;
    410   }
    411   gkh->cert_cb (gkh->cert_cb_cls,
    412                 &kresp,
    413                 kd);
    414   TALER_EXCHANGE_get_keys_cancel (gkh);
    415 }
    416 
    417 
    418 struct TALER_EXCHANGE_GetKeysHandle *
    419 TALER_EXCHANGE_get_keys_create (
    420   struct GNUNET_CURL_Context *ctx,
    421   const char *url)
    422 {
    423   struct TALER_EXCHANGE_GetKeysHandle *gkh;
    424 
    425   gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
    426   gkh->ctx = ctx;
    427   gkh->exchange_url = GNUNET_strdup (url);
    428   return gkh;
    429 }
    430 
    431 
    432 enum GNUNET_GenericReturnValue
    433 TALER_EXCHANGE_get_keys_set_options_ (
    434   struct TALER_EXCHANGE_GetKeysHandle *gkh,
    435   unsigned int num_options,
    436   const struct TALER_EXCHANGE_GetKeysOptionValue *options)
    437 {
    438   for (unsigned int i = 0; i < num_options; i++)
    439   {
    440     const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];
    441 
    442     switch (opt->option)
    443     {
    444     case TALER_EXCHANGE_GET_KEYS_OPTION_END:
    445       return GNUNET_OK;
    446     case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
    447       TALER_EXCHANGE_keys_decref (gkh->prev_keys);
    448       gkh->prev_keys = NULL;
    449       if (NULL != opt->details.last_keys)
    450         gkh->prev_keys
    451           = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
    452       break;
    453     }
    454   }
    455   return GNUNET_OK;
    456 }
    457 
    458 
    459 enum TALER_ErrorCode
    460 TALER_EXCHANGE_get_keys_start (
    461   struct TALER_EXCHANGE_GetKeysHandle *gkh,
    462   TALER_EXCHANGE_GetKeysCallback cert_cb,
    463   TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls)
    464 {
    465   CURL *eh;
    466   char last_date[80] = { 0 };
    467 
    468   gkh->cert_cb = cert_cb;
    469   gkh->cert_cb_cls = cert_cb_cls;
    470   if (NULL != gkh->prev_keys)
    471   {
    472     TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
    473                      GNUNET_TIME_timestamp2s (
    474                        gkh->prev_keys->last_denom_issue_date));
    475     GNUNET_snprintf (last_date,
    476                      sizeof (last_date),
    477                      "%llu",
    478                      (unsigned long long)
    479                      gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
    480                      / 1000000LLU);
    481   }
    482   gkh->url = TALER_url_join (gkh->exchange_url,
    483                              "keys",
    484                              (NULL != gkh->prev_keys)
    485                              ? "last_issue_date"
    486                              : NULL,
    487                              (NULL != gkh->prev_keys)
    488                              ? last_date
    489                              : NULL,
    490                              NULL);
    491   if (NULL == gkh->url)
    492   {
    493     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    494                 "Could not construct request URL.\n");
    495     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    496   }
    497   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    498               "Requesting keys with URL `%s'.\n",
    499               gkh->url);
    500   eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
    501   if (NULL == eh)
    502   {
    503     GNUNET_break (0);
    504     GNUNET_free (gkh->url);
    505     gkh->url = NULL;
    506     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    507   }
    508   GNUNET_break (CURLE_OK ==
    509                 curl_easy_setopt (eh,
    510                                   CURLOPT_VERBOSE,
    511                                   0));
    512   GNUNET_break (CURLE_OK ==
    513                 curl_easy_setopt (eh,
    514                                   CURLOPT_TIMEOUT,
    515                                   120 /* seconds */));
    516   GNUNET_assert (CURLE_OK ==
    517                  curl_easy_setopt (eh,
    518                                    CURLOPT_HEADERFUNCTION,
    519                                    &header_cb));
    520   GNUNET_assert (CURLE_OK ==
    521                  curl_easy_setopt (eh,
    522                                    CURLOPT_HEADERDATA,
    523                                    gkh));
    524   gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
    525                                                eh,
    526                                                &keys_completed_cb,
    527                                                gkh);
    528   if (NULL == gkh->job)
    529   {
    530     GNUNET_free (gkh->url);
    531     gkh->url = NULL;
    532     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    533   }
    534   return TALER_EC_NONE;
    535 }
    536 
    537 
    538 void
    539 TALER_EXCHANGE_get_keys_cancel (
    540   struct TALER_EXCHANGE_GetKeysHandle *gkh)
    541 {
    542   if (NULL != gkh->job)
    543   {
    544     GNUNET_CURL_job_cancel (gkh->job);
    545     gkh->job = NULL;
    546   }
    547   TALER_EXCHANGE_keys_decref (gkh->prev_keys);
    548   GNUNET_free (gkh->exchange_url);
    549   GNUNET_free (gkh->url);
    550   GNUNET_free (gkh);
    551 }
    552 
    553 
    554 /* end of exchange_api_get-keys.c */