Line data Source code
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 34 : 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 34 : if (strlen (dateline) > MAX_DATE_LINE_LEN)
136 : {
137 0 : GNUNET_break_op (0);
138 0 : return GNUNET_SYSERR;
139 : }
140 34 : while (*dateline == ' ')
141 0 : ++dateline;
142 170 : while (*dateline && *dateline != ' ')
143 136 : ++dateline;
144 68 : while (*dateline == ' ')
145 34 : ++dateline;
146 : /* We just skipped over the day of the week. Now we have:*/
147 34 : if ( (sscanf (dateline,
148 : "%d %3s %d %d:%d:%d",
149 0 : &day, month, &year, &hour, &min, &sec) != 6) &&
150 0 : (sscanf (dateline,
151 : "%d-%3s-%d %d:%d:%d",
152 0 : &day, month, &year, &hour, &min, &sec) != 6) &&
153 0 : (sscanf (dateline,
154 : "%3s %d %d:%d:%d %d",
155 : month, &day, &hour, &min, &sec, &year) != 6) )
156 : {
157 0 : GNUNET_break (0);
158 0 : 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 34 : if (year < 100)
163 0 : year += 1900;
164 :
165 102 : for (mon = 0; ; mon++)
166 : {
167 102 : if (! MONTHS[mon])
168 : {
169 0 : GNUNET_break_op (0);
170 0 : return GNUNET_SYSERR;
171 : }
172 102 : if (0 == strcasecmp (month,
173 : MONTHS[mon]))
174 34 : break;
175 : }
176 :
177 34 : memset (&tm, 0, sizeof(tm));
178 34 : tm.tm_year = year - 1900;
179 34 : tm.tm_mon = mon;
180 34 : tm.tm_mday = day;
181 34 : tm.tm_hour = hour;
182 34 : tm.tm_min = min;
183 34 : tm.tm_sec = sec;
184 :
185 34 : t = mktime (&tm);
186 34 : if (((time_t) -1) == t)
187 : {
188 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
189 : "mktime");
190 0 : return GNUNET_SYSERR;
191 : }
192 34 : if (t < 0)
193 0 : t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
194 34 : *at = GNUNET_TIME_timestamp_from_s (t);
195 34 : 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 442 : header_cb (char *buffer,
212 : size_t size,
213 : size_t nitems,
214 : void *userdata)
215 : {
216 442 : struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
217 442 : size_t total = size * nitems;
218 : char *val;
219 :
220 442 : if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
221 34 : return total;
222 408 : if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
223 : buffer,
224 : strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
225 374 : return total;
226 34 : val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
227 : total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
228 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
229 : "Found %s header `%s'\n",
230 : MHD_HTTP_HEADER_EXPIRES,
231 : val);
232 34 : if (GNUNET_OK !=
233 34 : parse_date_string (val,
234 : &kr->expire))
235 : {
236 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
237 : "Failed to parse %s-header `%s'\n",
238 : MHD_HTTP_HEADER_EXPIRES,
239 : val);
240 0 : kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
241 : }
242 34 : GNUNET_free (val);
243 34 : 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 34 : keys_completed_cb (void *cls,
257 : long response_code,
258 : const void *resp_obj)
259 : {
260 34 : struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
261 34 : const json_t *j = resp_obj;
262 34 : struct TALER_EXCHANGE_Keys *kd = NULL;
263 34 : struct TALER_EXCHANGE_KeysResponse kresp = {
264 : .hr.reply = j,
265 34 : .hr.http_status = (unsigned int) response_code,
266 : .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
267 : };
268 :
269 34 : gkh->job = NULL;
270 34 : 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 34 : if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
276 : {
277 0 : if (MHD_HTTP_OK == response_code)
278 0 : 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 0 : = GNUNET_TIME_absolute_to_timestamp (
284 : GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
285 : }
286 34 : switch (response_code)
287 : {
288 0 : case 0:
289 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
290 : "Failed to receive /keys response from exchange %s\n",
291 : gkh->exchange_url);
292 0 : break;
293 34 : case MHD_HTTP_OK:
294 34 : if (NULL == j)
295 : {
296 0 : GNUNET_break (0);
297 0 : response_code = 0;
298 0 : break;
299 : }
300 34 : kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
301 34 : kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
302 34 : if (NULL != gkh->prev_keys)
303 : {
304 8 : 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 8 : = kd_old->num_denom_keys;
310 : kd->last_denom_issue_date
311 8 : = kd_old->last_denom_issue_date;
312 8 : 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 8 : GNUNET_memcpy (kd->denom_keys,
317 : kd_old->denom_keys,
318 : kd_old->num_denom_keys
319 : * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
320 113 : for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
321 105 : TALER_denom_pub_copy (&kd->denom_keys[i].key,
322 105 : &kd_old->denom_keys[i].key);
323 8 : kd->num_auditors = kd_old->num_auditors;
324 : kd->auditors
325 8 : = GNUNET_new_array (kd->num_auditors,
326 : struct TALER_EXCHANGE_AuditorInformation);
327 : /* Now the necessary deep copy... */
328 10 : for (unsigned int i = 0; i<kd_old->num_auditors; i++)
329 : {
330 2 : const struct TALER_EXCHANGE_AuditorInformation *aold =
331 2 : &kd_old->auditors[i];
332 2 : struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
333 :
334 2 : anew->auditor_pub = aold->auditor_pub;
335 2 : anew->auditor_url = GNUNET_strdup (aold->auditor_url);
336 2 : anew->auditor_name = GNUNET_strdup (aold->auditor_name);
337 2 : GNUNET_array_grow (anew->denom_keys,
338 : anew->num_denom_keys,
339 : aold->num_denom_keys);
340 2 : 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 34 : if (GNUNET_OK !=
349 34 : TALER_EXCHANGE_decode_keys_json_ (j,
350 : true,
351 : kd,
352 : &kresp.details.ok.compat))
353 : {
354 0 : TALER_LOG_ERROR ("Could not decode /keys response\n");
355 0 : kd->rc = 1;
356 0 : TALER_EXCHANGE_keys_decref (kd);
357 0 : kd = NULL;
358 0 : kresp.hr.http_status = 0;
359 0 : kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
360 0 : break;
361 : }
362 34 : kd->rc = 1;
363 34 : kd->key_data_expiration = gkh->expire;
364 34 : if (GNUNET_TIME_relative_cmp (
365 : GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
366 : <,
367 : MINIMUM_EXPIRATION))
368 : {
369 0 : 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 0 : = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
375 : }
376 :
377 34 : kresp.details.ok.keys = kd;
378 34 : break;
379 0 : case MHD_HTTP_BAD_REQUEST:
380 : case MHD_HTTP_UNAUTHORIZED:
381 : case MHD_HTTP_FORBIDDEN:
382 : case MHD_HTTP_NOT_FOUND:
383 0 : if (NULL == j)
384 : {
385 0 : kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
386 0 : kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
387 : }
388 : else
389 : {
390 0 : kresp.hr.ec = TALER_JSON_get_error_code (j);
391 0 : kresp.hr.hint = TALER_JSON_get_error_hint (j);
392 : }
393 0 : break;
394 0 : default:
395 0 : if (NULL == j)
396 : {
397 0 : kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
398 0 : kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
399 : }
400 : else
401 : {
402 0 : kresp.hr.ec = TALER_JSON_get_error_code (j);
403 0 : kresp.hr.hint = TALER_JSON_get_error_hint (j);
404 : }
405 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
406 : "Unexpected response code %u/%d\n",
407 : (unsigned int) response_code,
408 : (int) kresp.hr.ec);
409 0 : break;
410 : }
411 34 : gkh->cert_cb (gkh->cert_cb_cls,
412 : &kresp,
413 : kd);
414 34 : TALER_EXCHANGE_get_keys_cancel (gkh);
415 34 : }
416 :
417 :
418 : struct TALER_EXCHANGE_GetKeysHandle *
419 34 : TALER_EXCHANGE_get_keys_create (
420 : struct GNUNET_CURL_Context *ctx,
421 : const char *url)
422 : {
423 : struct TALER_EXCHANGE_GetKeysHandle *gkh;
424 :
425 34 : gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
426 34 : gkh->ctx = ctx;
427 34 : gkh->exchange_url = GNUNET_strdup (url);
428 34 : return gkh;
429 : }
430 :
431 :
432 : enum GNUNET_GenericReturnValue
433 8 : 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 16 : for (unsigned int i = 0; i < num_options; i++)
439 : {
440 16 : const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];
441 :
442 16 : switch (opt->option)
443 : {
444 8 : case TALER_EXCHANGE_GET_KEYS_OPTION_END:
445 8 : return GNUNET_OK;
446 8 : case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
447 8 : TALER_EXCHANGE_keys_decref (gkh->prev_keys);
448 8 : gkh->prev_keys = NULL;
449 8 : if (NULL != opt->details.last_keys)
450 : gkh->prev_keys
451 8 : = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
452 8 : break;
453 : }
454 : }
455 0 : return GNUNET_OK;
456 : }
457 :
458 :
459 : enum TALER_ErrorCode
460 34 : 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 34 : char last_date[80] = { 0 };
467 :
468 34 : gkh->cert_cb = cert_cb;
469 34 : gkh->cert_cb_cls = cert_cb_cls;
470 34 : if (NULL != gkh->prev_keys)
471 : {
472 8 : 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 8 : GNUNET_snprintf (last_date,
476 : sizeof (last_date),
477 : "%llu",
478 : (unsigned long long)
479 8 : gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
480 : / 1000000LLU);
481 : }
482 68 : gkh->url = TALER_url_join (gkh->exchange_url,
483 : "keys",
484 34 : (NULL != gkh->prev_keys)
485 : ? "last_issue_date"
486 : : NULL,
487 34 : (NULL != gkh->prev_keys)
488 : ? last_date
489 : : NULL,
490 : NULL);
491 34 : if (NULL == gkh->url)
492 : {
493 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
494 : "Could not construct request URL.\n");
495 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
496 : }
497 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
498 : "Requesting keys with URL `%s'.\n",
499 : gkh->url);
500 34 : eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
501 34 : if (NULL == eh)
502 : {
503 0 : GNUNET_break (0);
504 0 : GNUNET_free (gkh->url);
505 0 : gkh->url = NULL;
506 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
507 : }
508 34 : GNUNET_break (CURLE_OK ==
509 : curl_easy_setopt (eh,
510 : CURLOPT_VERBOSE,
511 : 0));
512 34 : GNUNET_break (CURLE_OK ==
513 : curl_easy_setopt (eh,
514 : CURLOPT_TIMEOUT,
515 : 120 /* seconds */));
516 34 : GNUNET_assert (CURLE_OK ==
517 : curl_easy_setopt (eh,
518 : CURLOPT_HEADERFUNCTION,
519 : &header_cb));
520 34 : GNUNET_assert (CURLE_OK ==
521 : curl_easy_setopt (eh,
522 : CURLOPT_HEADERDATA,
523 : gkh));
524 34 : gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
525 : eh,
526 : &keys_completed_cb,
527 : gkh);
528 34 : if (NULL == gkh->job)
529 : {
530 0 : GNUNET_free (gkh->url);
531 0 : gkh->url = NULL;
532 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
533 : }
534 34 : return TALER_EC_NONE;
535 : }
536 :
537 :
538 : void
539 34 : TALER_EXCHANGE_get_keys_cancel (
540 : struct TALER_EXCHANGE_GetKeysHandle *gkh)
541 : {
542 34 : if (NULL != gkh->job)
543 : {
544 0 : GNUNET_CURL_job_cancel (gkh->job);
545 0 : gkh->job = NULL;
546 : }
547 34 : TALER_EXCHANGE_keys_decref (gkh->prev_keys);
548 34 : GNUNET_free (gkh->exchange_url);
549 34 : GNUNET_free (gkh->url);
550 34 : GNUNET_free (gkh);
551 34 : }
552 :
553 :
554 : /* end of exchange_api_get-keys.c */
|