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 */