exchange_api_reserves_open.c (16079B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 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_reserves_open.c 19 * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP open codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_exchange_service.h" 29 #include "taler/taler_json_lib.h" 30 #include "exchange_api_common.h" 31 #include "exchange_api_handle.h" 32 #include "taler/taler_signatures.h" 33 #include "exchange_api_curl_defaults.h" 34 35 36 /** 37 * Information we keep per coin to validate the reply. 38 */ 39 struct CoinData 40 { 41 /** 42 * Public key of the coin. 43 */ 44 struct TALER_CoinSpendPublicKeyP coin_pub; 45 46 /** 47 * Signature by the coin. 48 */ 49 struct TALER_CoinSpendSignatureP coin_sig; 50 51 /** 52 * The hash of the denomination's public key 53 */ 54 struct TALER_DenominationHashP h_denom_pub; 55 56 /** 57 * How much did this coin contribute. 58 */ 59 struct TALER_Amount contribution; 60 }; 61 62 63 /** 64 * @brief A /reserves/$RID/open Handle 65 */ 66 struct TALER_EXCHANGE_ReservesOpenHandle 67 { 68 69 /** 70 * The keys of the exchange this request handle will use 71 */ 72 struct TALER_EXCHANGE_Keys *keys; 73 74 /** 75 * The url for this request. 76 */ 77 char *url; 78 79 /** 80 * Handle for the request. 81 */ 82 struct GNUNET_CURL_Job *job; 83 84 /** 85 * Context for #TEH_curl_easy_post(). Keeps the data that must 86 * persist for Curl to make the upload. 87 */ 88 struct TALER_CURL_PostContext post_ctx; 89 90 /** 91 * Function to call with the result. 92 */ 93 TALER_EXCHANGE_ReservesOpenCallback cb; 94 95 /** 96 * Closure for @a cb. 97 */ 98 void *cb_cls; 99 100 /** 101 * Information we keep per coin to validate the reply. 102 */ 103 struct CoinData *coins; 104 105 /** 106 * Length of the @e coins array. 107 */ 108 unsigned int num_coins; 109 110 /** 111 * Public key of the reserve we are querying. 112 */ 113 struct TALER_ReservePublicKeyP reserve_pub; 114 115 /** 116 * Our signature. 117 */ 118 struct TALER_ReserveSignatureP reserve_sig; 119 120 /** 121 * When did we make the request. 122 */ 123 struct GNUNET_TIME_Timestamp ts; 124 125 }; 126 127 128 /** 129 * We received an #MHD_HTTP_OK open code. Handle the JSON 130 * response. 131 * 132 * @param roh handle of the request 133 * @param j JSON response 134 * @return #GNUNET_OK on success 135 */ 136 static enum GNUNET_GenericReturnValue 137 handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh, 138 const json_t *j) 139 { 140 struct TALER_EXCHANGE_ReserveOpenResult rs = { 141 .hr.reply = j, 142 .hr.http_status = MHD_HTTP_OK, 143 }; 144 struct GNUNET_JSON_Specification spec[] = { 145 TALER_JSON_spec_amount_any ("open_cost", 146 &rs.details.ok.open_cost), 147 GNUNET_JSON_spec_timestamp ("reserve_expiration", 148 &rs.details.ok.expiration_time), 149 GNUNET_JSON_spec_end () 150 }; 151 152 if (GNUNET_OK != 153 GNUNET_JSON_parse (j, 154 spec, 155 NULL, 156 NULL)) 157 { 158 GNUNET_break_op (0); 159 return GNUNET_SYSERR; 160 } 161 roh->cb (roh->cb_cls, 162 &rs); 163 roh->cb = NULL; 164 GNUNET_JSON_parse_free (spec); 165 return GNUNET_OK; 166 } 167 168 169 /** 170 * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON 171 * response. 172 * 173 * @param roh handle of the request 174 * @param j JSON response 175 * @return #GNUNET_OK on success 176 */ 177 static enum GNUNET_GenericReturnValue 178 handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh, 179 const json_t *j) 180 { 181 struct TALER_EXCHANGE_ReserveOpenResult rs = { 182 .hr.reply = j, 183 .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED, 184 }; 185 struct GNUNET_JSON_Specification spec[] = { 186 TALER_JSON_spec_amount_any ("open_cost", 187 &rs.details.payment_required.open_cost), 188 GNUNET_JSON_spec_timestamp ("reserve_expiration", 189 &rs.details.payment_required.expiration_time), 190 GNUNET_JSON_spec_end () 191 }; 192 193 if (GNUNET_OK != 194 GNUNET_JSON_parse (j, 195 spec, 196 NULL, 197 NULL)) 198 { 199 GNUNET_break_op (0); 200 return GNUNET_SYSERR; 201 } 202 roh->cb (roh->cb_cls, 203 &rs); 204 roh->cb = NULL; 205 GNUNET_JSON_parse_free (spec); 206 return GNUNET_OK; 207 } 208 209 210 /** 211 * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON 212 * response. 213 * 214 * @param roh handle of the request 215 * @param j JSON response 216 * @return #GNUNET_OK on success 217 */ 218 static enum GNUNET_GenericReturnValue 219 handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh, 220 const json_t *j) 221 { 222 struct TALER_EXCHANGE_ReserveOpenResult rs = { 223 .hr.reply = j, 224 .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, 225 }; 226 struct GNUNET_JSON_Specification spec[] = { 227 GNUNET_JSON_spec_fixed_auto ( 228 "h_payto", 229 &rs.details.unavailable_for_legal_reasons.h_payto), 230 GNUNET_JSON_spec_uint64 ( 231 "requirement_row", 232 &rs.details.unavailable_for_legal_reasons.requirement_row), 233 GNUNET_JSON_spec_end () 234 }; 235 236 if (GNUNET_OK != 237 GNUNET_JSON_parse (j, 238 spec, 239 NULL, 240 NULL)) 241 { 242 GNUNET_break_op (0); 243 return GNUNET_SYSERR; 244 } 245 roh->cb (roh->cb_cls, 246 &rs); 247 roh->cb = NULL; 248 GNUNET_JSON_parse_free (spec); 249 return GNUNET_OK; 250 } 251 252 253 /** 254 * Function called when we're done processing the 255 * HTTP /reserves/$RID/open request. 256 * 257 * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle` 258 * @param response_code HTTP response code, 0 on error 259 * @param response parsed JSON result, NULL on error 260 */ 261 static void 262 handle_reserves_open_finished (void *cls, 263 long response_code, 264 const void *response) 265 { 266 struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls; 267 const json_t *j = response; 268 struct TALER_EXCHANGE_ReserveOpenResult rs = { 269 .hr.reply = j, 270 .hr.http_status = (unsigned int) response_code 271 }; 272 273 roh->job = NULL; 274 switch (response_code) 275 { 276 case 0: 277 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 278 break; 279 case MHD_HTTP_OK: 280 if (GNUNET_OK != 281 handle_reserves_open_ok (roh, 282 j)) 283 { 284 GNUNET_break_op (0); 285 rs.hr.http_status = 0; 286 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 287 } 288 break; 289 case MHD_HTTP_BAD_REQUEST: 290 /* This should never happen, either us or the exchange is buggy 291 (or API version conflict); just pass JSON reply to the application */ 292 GNUNET_break (0); 293 json_dumpf (j, 294 stderr, 295 JSON_INDENT (2)); 296 rs.hr.ec = TALER_JSON_get_error_code (j); 297 rs.hr.hint = TALER_JSON_get_error_hint (j); 298 break; 299 case MHD_HTTP_PAYMENT_REQUIRED: 300 if (GNUNET_OK != 301 handle_reserves_open_pr (roh, 302 j)) 303 { 304 GNUNET_break_op (0); 305 rs.hr.http_status = 0; 306 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 307 } 308 break; 309 case MHD_HTTP_FORBIDDEN: 310 /* This should never happen, either us or the exchange is buggy 311 (or API version conflict); just pass JSON reply to the application */ 312 GNUNET_break (0); 313 rs.hr.ec = TALER_JSON_get_error_code (j); 314 rs.hr.hint = TALER_JSON_get_error_hint (j); 315 break; 316 case MHD_HTTP_NOT_FOUND: 317 /* Nothing really to verify, this should never 318 happen, we should pass the JSON reply to the application */ 319 rs.hr.ec = TALER_JSON_get_error_code (j); 320 rs.hr.hint = TALER_JSON_get_error_hint (j); 321 break; 322 case MHD_HTTP_CONFLICT: 323 { 324 const struct CoinData *cd = NULL; 325 struct GNUNET_JSON_Specification spec[] = { 326 GNUNET_JSON_spec_fixed_auto ("coin_pub", 327 &rs.details.conflict.coin_pub), 328 GNUNET_JSON_spec_end () 329 }; 330 331 if (GNUNET_OK != 332 GNUNET_JSON_parse (j, 333 spec, 334 NULL, 335 NULL)) 336 { 337 GNUNET_break_op (0); 338 rs.hr.http_status = 0; 339 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 340 break; 341 } 342 for (unsigned int i = 0; i<roh->num_coins; i++) 343 { 344 const struct CoinData *cdi = &roh->coins[i]; 345 346 if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub, 347 &cdi->coin_pub)) 348 { 349 cd = cdi; 350 break; 351 } 352 } 353 if (NULL == cd) 354 { 355 GNUNET_break_op (0); 356 rs.hr.http_status = 0; 357 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 358 break; 359 } 360 rs.hr.ec = TALER_JSON_get_error_code (j); 361 rs.hr.hint = TALER_JSON_get_error_hint (j); 362 break; 363 } 364 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 365 if (GNUNET_OK != 366 handle_reserves_open_kyc (roh, 367 j)) 368 { 369 GNUNET_break_op (0); 370 rs.hr.http_status = 0; 371 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 372 } 373 break; 374 case MHD_HTTP_INTERNAL_SERVER_ERROR: 375 /* Server had an internal issue; we should retry, but this API 376 leaves this to the application */ 377 rs.hr.ec = TALER_JSON_get_error_code (j); 378 rs.hr.hint = TALER_JSON_get_error_hint (j); 379 break; 380 default: 381 /* unexpected response code */ 382 GNUNET_break_op (0); 383 rs.hr.ec = TALER_JSON_get_error_code (j); 384 rs.hr.hint = TALER_JSON_get_error_hint (j); 385 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 386 "Unexpected response code %u/%d for reserves open\n", 387 (unsigned int) response_code, 388 (int) rs.hr.ec); 389 break; 390 } 391 if (NULL != roh->cb) 392 { 393 roh->cb (roh->cb_cls, 394 &rs); 395 roh->cb = NULL; 396 } 397 TALER_EXCHANGE_reserves_open_cancel (roh); 398 } 399 400 401 struct TALER_EXCHANGE_ReservesOpenHandle * 402 TALER_EXCHANGE_reserves_open ( 403 struct GNUNET_CURL_Context *ctx, 404 const char *url, 405 struct TALER_EXCHANGE_Keys *keys, 406 const struct TALER_ReservePrivateKeyP *reserve_priv, 407 const struct TALER_Amount *reserve_contribution, 408 unsigned int coin_payments_length, 409 const struct TALER_EXCHANGE_PurseDeposit coin_payments[ 410 static coin_payments_length], 411 struct GNUNET_TIME_Timestamp expiration_time, 412 uint32_t min_purses, 413 TALER_EXCHANGE_ReservesOpenCallback cb, 414 void *cb_cls) 415 { 416 struct TALER_EXCHANGE_ReservesOpenHandle *roh; 417 CURL *eh; 418 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 419 json_t *cpa; 420 421 roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle); 422 roh->cb = cb; 423 roh->cb_cls = cb_cls; 424 roh->ts = GNUNET_TIME_timestamp_get (); 425 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 426 &roh->reserve_pub.eddsa_pub); 427 { 428 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 429 char *end; 430 431 end = GNUNET_STRINGS_data_to_string ( 432 &roh->reserve_pub, 433 sizeof (roh->reserve_pub), 434 pub_str, 435 sizeof (pub_str)); 436 *end = '\0'; 437 GNUNET_snprintf (arg_str, 438 sizeof (arg_str), 439 "reserves/%s/open", 440 pub_str); 441 } 442 roh->url = TALER_url_join (url, 443 arg_str, 444 NULL); 445 if (NULL == roh->url) 446 { 447 GNUNET_free (roh); 448 return NULL; 449 } 450 eh = TALER_EXCHANGE_curl_easy_get_ (roh->url); 451 if (NULL == eh) 452 { 453 GNUNET_break (0); 454 GNUNET_free (roh->url); 455 GNUNET_free (roh); 456 return NULL; 457 } 458 TALER_wallet_reserve_open_sign (reserve_contribution, 459 roh->ts, 460 expiration_time, 461 min_purses, 462 reserve_priv, 463 &roh->reserve_sig); 464 roh->coins = GNUNET_new_array (coin_payments_length, 465 struct CoinData); 466 cpa = json_array (); 467 GNUNET_assert (NULL != cpa); 468 for (unsigned int i = 0; i<coin_payments_length; i++) 469 { 470 const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i]; 471 const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof; 472 struct TALER_AgeCommitmentHashP ahac; 473 struct TALER_AgeCommitmentHashP *achp = NULL; 474 struct CoinData *cd = &roh->coins[i]; 475 json_t *cp; 476 477 cd->contribution = pd->amount; 478 cd->h_denom_pub = pd->h_denom_pub; 479 if (NULL != acp) 480 { 481 TALER_age_commitment_hash (&acp->commitment, 482 &ahac); 483 achp = &ahac; 484 } 485 TALER_wallet_reserve_open_deposit_sign (&pd->amount, 486 &roh->reserve_sig, 487 &pd->coin_priv, 488 &cd->coin_sig); 489 GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, 490 &cd->coin_pub.eddsa_pub); 491 492 cp = GNUNET_JSON_PACK ( 493 GNUNET_JSON_pack_allow_null ( 494 GNUNET_JSON_pack_data_auto ("h_age_commitment", 495 achp)), 496 TALER_JSON_pack_amount ("amount", 497 &pd->amount), 498 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 499 &pd->h_denom_pub), 500 TALER_JSON_pack_denom_sig ("ub_sig", 501 &pd->denom_sig), 502 GNUNET_JSON_pack_data_auto ("coin_pub", 503 &cd->coin_pub), 504 GNUNET_JSON_pack_data_auto ("coin_sig", 505 &cd->coin_sig)); 506 GNUNET_assert (0 == 507 json_array_append_new (cpa, 508 cp)); 509 } 510 { 511 json_t *open_obj = GNUNET_JSON_PACK ( 512 GNUNET_JSON_pack_timestamp ("request_timestamp", 513 roh->ts), 514 GNUNET_JSON_pack_timestamp ("reserve_expiration", 515 expiration_time), 516 GNUNET_JSON_pack_array_steal ("payments", 517 cpa), 518 TALER_JSON_pack_amount ("reserve_payment", 519 reserve_contribution), 520 GNUNET_JSON_pack_uint64 ("purse_limit", 521 min_purses), 522 GNUNET_JSON_pack_data_auto ("reserve_sig", 523 &roh->reserve_sig)); 524 525 if (GNUNET_OK != 526 TALER_curl_easy_post (&roh->post_ctx, 527 eh, 528 open_obj)) 529 { 530 GNUNET_break (0); 531 curl_easy_cleanup (eh); 532 json_decref (open_obj); 533 GNUNET_free (roh->coins); 534 GNUNET_free (roh->url); 535 GNUNET_free (roh); 536 return NULL; 537 } 538 json_decref (open_obj); 539 } 540 roh->keys = TALER_EXCHANGE_keys_incref (keys); 541 roh->job = GNUNET_CURL_job_add2 (ctx, 542 eh, 543 roh->post_ctx.headers, 544 &handle_reserves_open_finished, 545 roh); 546 return roh; 547 } 548 549 550 void 551 TALER_EXCHANGE_reserves_open_cancel ( 552 struct TALER_EXCHANGE_ReservesOpenHandle *roh) 553 { 554 if (NULL != roh->job) 555 { 556 GNUNET_CURL_job_cancel (roh->job); 557 roh->job = NULL; 558 } 559 TALER_curl_easy_post_finished (&roh->post_ctx); 560 GNUNET_free (roh->coins); 561 GNUNET_free (roh->url); 562 TALER_EXCHANGE_keys_decref (roh->keys); 563 GNUNET_free (roh); 564 } 565 566 567 /* end of exchange_api_reserves_open.c */