anastasis_api_keyshare_lookup.c (17726B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file restclient/anastasis_api_keyshare_lookup.c 18 * @brief Implementation of the GET /truth client 19 * @author Christian Grothoff 20 * @author Dennis Neufeld 21 * @author Dominik Meister 22 */ 23 #include "platform.h" 24 #include <curl/curl.h> 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP status codes */ 27 #include "anastasis_service.h" 28 #include "anastasis_api_curl_defaults.h" 29 #include <taler/taler_json_lib.h> 30 #include <taler/taler_merchant_service.h> 31 32 33 /** 34 * @brief A Contract Operation Handle 35 */ 36 struct ANASTASIS_KeyShareLookupOperation 37 { 38 /** 39 * The url for this request, including parameters. 40 */ 41 char *url; 42 43 /** 44 * The url for this request, without response parameter. 45 */ 46 char *display_url; 47 48 /** 49 * Handle for the request. 50 */ 51 struct GNUNET_CURL_Job *job; 52 53 /** 54 * Function to call with the result. 55 */ 56 ANASTASIS_KeyShareLookupCallback cb; 57 58 /** 59 * Closure for @a cb. 60 */ 61 void *cb_cls; 62 63 /** 64 * Reference to the execution context. 65 */ 66 struct GNUNET_CURL_Context *ctx; 67 68 /** 69 * Identification of the Truth Object 70 */ 71 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_public_key; 72 73 /** 74 * Key to decrypt the truth on the server 75 */ 76 const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; 77 78 /** 79 * Hash of the response (security question) 80 */ 81 const struct GNUNET_HashCode *hashed_answer; 82 83 /** 84 * Payment URI we received from the service, or NULL. 85 */ 86 char *pay_uri; 87 88 /** 89 * Location URI we received from the service, or NULL. 90 */ 91 char *location; 92 93 /** 94 * Content type of the body. 95 */ 96 char *content_type; 97 }; 98 99 100 void 101 ANASTASIS_keyshare_lookup_cancel ( 102 struct ANASTASIS_KeyShareLookupOperation *kslo) 103 { 104 if (NULL != kslo->job) 105 { 106 GNUNET_CURL_job_cancel (kslo->job); 107 kslo->job = NULL; 108 } 109 GNUNET_free (kslo->location); 110 GNUNET_free (kslo->pay_uri); 111 GNUNET_free (kslo->display_url); 112 GNUNET_free (kslo->url); 113 GNUNET_free (kslo->content_type); 114 GNUNET_free (kslo); 115 } 116 117 118 /** 119 * Process GET /truth response 120 * 121 * @param cls our `struct ANASTASIS_KeyShareLookupOperation *` 122 * @param response_code the HTTP status 123 * @param data the body of the response 124 * @param data_size number of bytes in @a data 125 */ 126 static void 127 handle_keyshare_lookup_finished (void *cls, 128 long response_code, 129 const void *data, 130 size_t data_size) 131 { 132 struct ANASTASIS_KeyShareLookupOperation *kslo = cls; 133 struct ANASTASIS_KeyShareDownloadDetails kdd; 134 135 kslo->job = NULL; 136 memset (&kdd, 137 0, 138 sizeof (kdd)); 139 kdd.server_url = kslo->display_url; 140 switch (response_code) 141 { 142 case 0: 143 /* Hard error */ 144 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 145 "Backend didn't even return from GET /truth\n"); 146 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 147 kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 148 break; 149 case MHD_HTTP_OK: 150 if (sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP) != data_size) 151 { 152 GNUNET_break_op (0); 153 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 154 kdd.details.server_failure.http_status = MHD_HTTP_OK; 155 kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 156 break; 157 } 158 /* Success, call callback with all details! */ 159 memcpy (&kdd.details.eks, 160 data, 161 data_size); 162 break; 163 case MHD_HTTP_ACCEPTED: 164 kdd.details.external_challenge = json_loadb (data, 165 data_size, 166 JSON_REJECT_DUPLICATES, 167 NULL); 168 if (NULL == kdd.details.external_challenge) 169 { 170 GNUNET_break_op (0); 171 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 172 kdd.details.server_failure.http_status = MHD_HTTP_ACCEPTED; 173 kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 174 break; 175 } 176 kdd.status = ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS; 177 break; 178 case MHD_HTTP_BAD_REQUEST: 179 /* This should never happen, either us or the anastasis server is buggy 180 (or API version conflict); just pass JSON reply to the application */ 181 GNUNET_break (0); 182 kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; 183 kdd.details.server_failure.http_status = MHD_HTTP_BAD_REQUEST; 184 kdd.details.server_failure.ec = TALER_EC_GENERIC_JSON_INVALID; 185 break; 186 case MHD_HTTP_PAYMENT_REQUIRED: 187 { 188 struct TALER_MERCHANT_PayUriData pd; 189 190 if ( (NULL == kslo->pay_uri) || 191 (GNUNET_OK != 192 TALER_MERCHANT_parse_pay_uri (kslo->pay_uri, 193 &pd)) ) 194 { 195 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 196 "Failed to parse `%s'\n", 197 kslo->pay_uri); 198 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 199 kdd.details.server_failure.http_status = MHD_HTTP_PAYMENT_REQUIRED; 200 kdd.details.server_failure.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 201 break; 202 } 203 if (GNUNET_OK != 204 GNUNET_STRINGS_string_to_data ( 205 pd.order_id, 206 strlen (pd.order_id), 207 &kdd.details.payment_required.payment_secret, 208 sizeof (kdd.details.payment_required.payment_secret))) 209 { 210 GNUNET_break (0); 211 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 212 kdd.details.server_failure.http_status = MHD_HTTP_PAYMENT_REQUIRED; 213 kdd.details.server_failure.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 214 TALER_MERCHANT_parse_pay_uri_free (&pd); 215 break; 216 } 217 kdd.status = ANASTASIS_KSD_PAYMENT_REQUIRED; 218 kdd.details.payment_required.taler_pay_uri = kslo->pay_uri; 219 kslo->cb (kslo->cb_cls, 220 &kdd); 221 ANASTASIS_keyshare_lookup_cancel (kslo); 222 TALER_MERCHANT_parse_pay_uri_free (&pd); 223 return; 224 } 225 break; 226 case MHD_HTTP_SEE_OTHER: 227 /* Nothing really to verify, authentication required/failed */ 228 kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION; 229 kdd.details.redirect_url = kslo->location; 230 break; 231 case MHD_HTTP_FORBIDDEN: 232 /* Nothing really to verify, authentication required/failed */ 233 kdd.status = ANASTASIS_KSD_INVALID_ANSWER; 234 kdd.details.open_challenge.body = data; 235 kdd.details.open_challenge.body_size = data_size; 236 kdd.details.open_challenge.content_type = kslo->content_type; 237 kdd.details.open_challenge.http_status = response_code; 238 break; 239 case MHD_HTTP_NOT_FOUND: 240 /* Nothing really to verify */ 241 kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; 242 break; 243 case MHD_HTTP_REQUEST_TIMEOUT: 244 /* Nothing really to verify */ 245 kdd.status = ANASTASIS_KSD_AUTHENTICATION_TIMEOUT; 246 break; 247 case MHD_HTTP_CONFLICT: 248 /* Nothing really to verify */ 249 kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; 250 kdd.details.server_failure.http_status = MHD_HTTP_CONFLICT; 251 kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, 252 data_size); 253 break; 254 case MHD_HTTP_GONE: 255 /* Nothing really to verify */ 256 kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; 257 break; 258 case MHD_HTTP_TOO_MANY_REQUESTS: 259 kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; 260 { 261 struct GNUNET_JSON_Specification spec[] = { 262 GNUNET_JSON_spec_uint32 ( 263 "request_limit", 264 &kdd.details.rate_limit_exceeded.request_limit), 265 GNUNET_JSON_spec_relative_time ( 266 "request_frequency", 267 &kdd.details.rate_limit_exceeded.request_frequency), 268 GNUNET_JSON_spec_end () 269 }; 270 json_t *reply; 271 272 reply = json_loadb (data, 273 data_size, 274 JSON_REJECT_DUPLICATES, 275 NULL); 276 if (NULL == reply) 277 { 278 GNUNET_break_op (0); 279 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 280 kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 281 kdd.details.server_failure.http_status = response_code; 282 break; 283 } 284 if (GNUNET_OK != 285 GNUNET_JSON_parse (reply, 286 spec, 287 NULL, NULL)) 288 { 289 GNUNET_break_op (0); 290 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 291 kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply); 292 kdd.details.server_failure.http_status = response_code; 293 json_decref (reply); 294 break; 295 } 296 json_decref (reply); 297 } 298 break; 299 case MHD_HTTP_INTERNAL_SERVER_ERROR: 300 /* Server had an internal issue; we should retry, but this API 301 leaves this to the application */ 302 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 303 kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, 304 data_size); 305 kdd.details.server_failure.http_status = response_code; 306 break; 307 case MHD_HTTP_BAD_GATEWAY: 308 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 309 kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, 310 data_size); 311 kdd.details.server_failure.http_status = response_code; 312 break; 313 default: 314 /* unexpected response code */ 315 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 316 "Unexpected response code %u to GET /truth\n", 317 (unsigned int) response_code); 318 GNUNET_break (0); 319 kdd.status = ANASTASIS_KSD_SERVER_ERROR; 320 kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, 321 data_size); 322 kdd.details.server_failure.http_status = response_code; 323 break; 324 } 325 kslo->cb (kslo->cb_cls, 326 &kdd); 327 ANASTASIS_keyshare_lookup_cancel (kslo); 328 } 329 330 331 /** 332 * Patch value in @a val, replacing new line with '\0'. 333 * 334 * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. 335 */ 336 static void 337 patch_value (char *val) 338 { 339 size_t len; 340 341 /* found location URI we care about! */ 342 len = strlen (val); 343 while ( (len > 0) && 344 ( ('\n' == val[len - 1]) || 345 ('\r' == val[len - 1]) ) ) 346 { 347 len--; 348 val[len] = '\0'; 349 } 350 } 351 352 353 /** 354 * Handle HTTP header received by curl. 355 * 356 * @param buffer one line of HTTP header data 357 * @param size size of an item 358 * @param nitems number of items passed 359 * @param userdata our `struct ANASTASIS_StorePolicyOperation *` 360 * @return `size * nitems` 361 */ 362 static size_t 363 handle_header (char *buffer, 364 size_t size, 365 size_t nitems, 366 void *userdata) 367 { 368 struct ANASTASIS_KeyShareLookupOperation *kslo = userdata; 369 size_t total = size * nitems; 370 char *ndup; 371 const char *hdr_type; 372 char *hdr_val; 373 char *sp; 374 375 ndup = GNUNET_strndup (buffer, 376 total); 377 hdr_type = strtok_r (ndup, 378 ":", 379 &sp); 380 if (NULL == hdr_type) 381 { 382 GNUNET_free (ndup); 383 return total; 384 } 385 hdr_val = strtok_r (NULL, 386 "", 387 &sp); 388 if (NULL == hdr_val) 389 { 390 GNUNET_free (ndup); 391 return total; 392 } 393 if (' ' == *hdr_val) 394 hdr_val++; 395 if (0 == strcasecmp (hdr_type, 396 ANASTASIS_HTTP_HEADER_TALER)) 397 { 398 /* found payment URI we care about! */ 399 GNUNET_free (kslo->pay_uri); 400 kslo->pay_uri = GNUNET_strdup (hdr_val); 401 patch_value (kslo->pay_uri); 402 } 403 if (0 == strcasecmp (hdr_type, 404 MHD_HTTP_HEADER_LOCATION)) 405 { 406 /* found location URI we care about! */ 407 GNUNET_free (kslo->location); 408 kslo->location = GNUNET_strdup (hdr_val); 409 patch_value (kslo->location); 410 } 411 if (0 == strcasecmp (hdr_type, 412 MHD_HTTP_HEADER_CONTENT_TYPE)) 413 { 414 /* found location URI we care about! */ 415 GNUNET_free (kslo->content_type); 416 kslo->content_type = GNUNET_strdup (hdr_val); 417 patch_value (kslo->content_type); 418 } 419 GNUNET_free (ndup); 420 return total; 421 } 422 423 424 struct ANASTASIS_KeyShareLookupOperation * 425 ANASTASIS_keyshare_lookup ( 426 struct GNUNET_CURL_Context *ctx, 427 const char *backend_url, 428 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 429 const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, 430 const struct ANASTASIS_PaymentSecretP *payment_secret, 431 struct GNUNET_TIME_Relative timeout, 432 const struct GNUNET_HashCode *hashed_answer, 433 ANASTASIS_KeyShareLookupCallback cb, 434 void *cb_cls) 435 { 436 struct ANASTASIS_KeyShareLookupOperation *kslo; 437 CURL *eh; 438 struct curl_slist *job_headers; 439 char *path; 440 char *answer_s; 441 unsigned long long tms; 442 443 tms = (unsigned long long) (timeout.rel_value_us 444 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 445 job_headers = NULL; 446 { 447 struct curl_slist *ext; 448 char *val; 449 char *hdr; 450 451 /* Set Anastasis-Truth-Decryption-Key header */ 452 val = GNUNET_STRINGS_data_to_string_alloc (truth_key, 453 sizeof (*truth_key)); 454 GNUNET_asprintf (&hdr, 455 "%s: %s", 456 ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY, 457 val); 458 GNUNET_free (val); 459 ext = curl_slist_append (job_headers, 460 hdr); 461 GNUNET_free (hdr); 462 if (NULL == ext) 463 { 464 GNUNET_break (0); 465 curl_slist_free_all (job_headers); 466 return NULL; 467 } 468 job_headers = ext; 469 470 /* Setup Payment-Identifier header */ 471 if (NULL != payment_secret) 472 { 473 char *paid_order_id; 474 475 paid_order_id = GNUNET_STRINGS_data_to_string_alloc ( 476 payment_secret, 477 sizeof (*payment_secret)); 478 GNUNET_asprintf (&hdr, 479 "%s: %s", 480 ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER, 481 paid_order_id); 482 GNUNET_free (paid_order_id); 483 ext = curl_slist_append (job_headers, 484 hdr); 485 GNUNET_free (hdr); 486 if (NULL == ext) 487 { 488 GNUNET_break (0); 489 curl_slist_free_all (job_headers); 490 return NULL; 491 } 492 job_headers = ext; 493 } 494 } 495 kslo = GNUNET_new (struct ANASTASIS_KeyShareLookupOperation); 496 kslo->ctx = ctx; 497 kslo->truth_key = truth_key; 498 { 499 char *uuid_str; 500 501 uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, 502 sizeof (*truth_uuid)); 503 GNUNET_asprintf (&path, 504 "truth/%s", 505 uuid_str); 506 GNUNET_free (uuid_str); 507 } 508 { 509 char timeout_ms[32]; 510 511 GNUNET_snprintf (timeout_ms, 512 sizeof (timeout_ms), 513 "%llu", 514 tms); 515 if (NULL != hashed_answer) 516 { 517 answer_s = GNUNET_STRINGS_data_to_string_alloc (hashed_answer, 518 sizeof (*hashed_answer)); 519 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 520 "Querying challenge with existing response code\n"); 521 kslo->url = TALER_url_join (backend_url, 522 path, 523 "response", 524 answer_s, 525 "timeout_ms", 526 (0 != timeout.rel_value_us) 527 ? timeout_ms 528 : NULL, 529 NULL); 530 GNUNET_free (answer_s); 531 } 532 else 533 { 534 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 535 "Querying challenge without response code\n"); 536 kslo->url = TALER_url_join (backend_url, 537 path, 538 "timeout_ms", 539 (0 != timeout.rel_value_us) 540 ? timeout_ms 541 : NULL, 542 NULL); 543 } 544 } 545 kslo->display_url = TALER_url_join (backend_url, 546 path, 547 NULL); 548 GNUNET_free (path); 549 eh = ANASTASIS_curl_easy_get_ (kslo->url); 550 if (0 != tms) 551 GNUNET_assert (CURLE_OK == 552 curl_easy_setopt (eh, 553 CURLOPT_TIMEOUT_MS, 554 (long) (tms + 5000))); 555 GNUNET_assert (CURLE_OK == 556 curl_easy_setopt (eh, 557 CURLOPT_HEADERFUNCTION, 558 &handle_header)); 559 GNUNET_assert (CURLE_OK == 560 curl_easy_setopt (eh, 561 CURLOPT_HEADERDATA, 562 kslo)); 563 kslo->cb = cb; 564 kslo->cb_cls = cb_cls; 565 kslo->job = GNUNET_CURL_job_add_raw (ctx, 566 eh, 567 job_headers, 568 &handle_keyshare_lookup_finished, 569 kslo); 570 curl_slist_free_all (job_headers); 571 return kslo; 572 } 573 574 575 /* end of anastasis_api_keyshare_lookup.c */