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