exchange_api_post-reserves-RESERVE_PUB-open.c (15482B)
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_post-reserves-RESERVE_PUB-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 POST /reserves/$RID/open Handle 65 */ 66 struct TALER_EXCHANGE_PostReservesOpenHandle 67 { 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Base URL of the exchange. 76 */ 77 char *base_url; 78 79 /** 80 * The url for this request, set during _start. 81 */ 82 char *url; 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 * Handle for the request. 92 */ 93 struct GNUNET_CURL_Job *job; 94 95 /** 96 * Function to call with the result. 97 */ 98 TALER_EXCHANGE_PostReservesOpenCallback cb; 99 100 /** 101 * Closure for @a cb. 102 */ 103 TALER_EXCHANGE_POST_RESERVES_OPEN_RESULT_CLOSURE *cb_cls; 104 105 /** 106 * The keys of the exchange this request handle will use 107 */ 108 struct TALER_EXCHANGE_Keys *keys; 109 110 /** 111 * Public key of the reserve we are querying. 112 */ 113 struct TALER_ReservePublicKeyP reserve_pub; 114 115 /** 116 * Information we keep per coin to validate the reply. 117 */ 118 struct CoinData *coins; 119 120 /** 121 * Length of the @e coins array. 122 */ 123 unsigned int num_coins; 124 125 /** 126 * Pre-built JSON body for the request. 127 */ 128 json_t *body; 129 130 }; 131 132 133 /** 134 * We received an #MHD_HTTP_OK open code. Handle the JSON 135 * response. 136 * 137 * @param proh handle of the request 138 * @param j JSON response 139 * @return #GNUNET_OK on success 140 */ 141 static enum GNUNET_GenericReturnValue 142 handle_reserves_open_ok (struct TALER_EXCHANGE_PostReservesOpenHandle *proh, 143 const json_t *j) 144 { 145 struct TALER_EXCHANGE_PostReservesOpenResponse rs = { 146 .hr.reply = j, 147 .hr.http_status = MHD_HTTP_OK, 148 }; 149 struct GNUNET_JSON_Specification spec[] = { 150 TALER_JSON_spec_amount_any ("open_cost", 151 &rs.details.ok.open_cost), 152 GNUNET_JSON_spec_timestamp ("reserve_expiration", 153 &rs.details.ok.expiration_time), 154 GNUNET_JSON_spec_end () 155 }; 156 157 if (GNUNET_OK != 158 GNUNET_JSON_parse (j, 159 spec, 160 NULL, 161 NULL)) 162 { 163 GNUNET_break_op (0); 164 return GNUNET_SYSERR; 165 } 166 proh->cb (proh->cb_cls, 167 &rs); 168 proh->cb = NULL; 169 GNUNET_JSON_parse_free (spec); 170 return GNUNET_OK; 171 } 172 173 174 /** 175 * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON 176 * response. 177 * 178 * @param proh handle of the request 179 * @param j JSON response 180 * @return #GNUNET_OK on success 181 */ 182 static enum GNUNET_GenericReturnValue 183 handle_reserves_open_pr (struct TALER_EXCHANGE_PostReservesOpenHandle *proh, 184 const json_t *j) 185 { 186 struct TALER_EXCHANGE_PostReservesOpenResponse rs = { 187 .hr.reply = j, 188 .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED, 189 }; 190 struct GNUNET_JSON_Specification spec[] = { 191 TALER_JSON_spec_amount_any ("open_cost", 192 &rs.details.payment_required.open_cost), 193 GNUNET_JSON_spec_timestamp ("reserve_expiration", 194 &rs.details.payment_required.expiration_time), 195 GNUNET_JSON_spec_end () 196 }; 197 198 if (GNUNET_OK != 199 GNUNET_JSON_parse (j, 200 spec, 201 NULL, 202 NULL)) 203 { 204 GNUNET_break_op (0); 205 return GNUNET_SYSERR; 206 } 207 proh->cb (proh->cb_cls, 208 &rs); 209 proh->cb = NULL; 210 GNUNET_JSON_parse_free (spec); 211 return GNUNET_OK; 212 } 213 214 215 /** 216 * Function called when we're done processing the 217 * HTTP /reserves/$RID/open request. 218 * 219 * @param cls the `struct TALER_EXCHANGE_PostReservesOpenHandle` 220 * @param response_code HTTP response code, 0 on error 221 * @param response parsed JSON result, NULL on error 222 */ 223 static void 224 handle_reserves_open_finished (void *cls, 225 long response_code, 226 const void *response) 227 { 228 struct TALER_EXCHANGE_PostReservesOpenHandle *proh = cls; 229 const json_t *j = response; 230 struct TALER_EXCHANGE_PostReservesOpenResponse rs = { 231 .hr.reply = j, 232 .hr.http_status = (unsigned int) response_code 233 }; 234 235 proh->job = NULL; 236 switch (response_code) 237 { 238 case 0: 239 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 240 break; 241 case MHD_HTTP_OK: 242 if (GNUNET_OK != 243 handle_reserves_open_ok (proh, 244 j)) 245 { 246 GNUNET_break_op (0); 247 rs.hr.http_status = 0; 248 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 249 } 250 break; 251 case MHD_HTTP_BAD_REQUEST: 252 /* This should never happen, either us or the exchange is buggy 253 (or API version conflict); just pass JSON reply to the application */ 254 GNUNET_break (0); 255 json_dumpf (j, 256 stderr, 257 JSON_INDENT (2)); 258 rs.hr.ec = TALER_JSON_get_error_code (j); 259 rs.hr.hint = TALER_JSON_get_error_hint (j); 260 break; 261 case MHD_HTTP_PAYMENT_REQUIRED: 262 if (GNUNET_OK != 263 handle_reserves_open_pr (proh, 264 j)) 265 { 266 GNUNET_break_op (0); 267 rs.hr.http_status = 0; 268 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 269 } 270 break; 271 case MHD_HTTP_FORBIDDEN: 272 /* This should never happen, either us or the exchange is buggy 273 (or API version conflict); just pass JSON reply to the application */ 274 GNUNET_break (0); 275 rs.hr.ec = TALER_JSON_get_error_code (j); 276 rs.hr.hint = TALER_JSON_get_error_hint (j); 277 break; 278 case MHD_HTTP_NOT_FOUND: 279 /* Nothing really to verify, this should never 280 happen, we should pass the JSON reply to the application */ 281 rs.hr.ec = TALER_JSON_get_error_code (j); 282 rs.hr.hint = TALER_JSON_get_error_hint (j); 283 break; 284 case MHD_HTTP_CONFLICT: 285 { 286 const struct CoinData *cd = NULL; 287 struct GNUNET_JSON_Specification spec[] = { 288 GNUNET_JSON_spec_fixed_auto ("coin_pub", 289 &rs.details.conflict.coin_pub), 290 GNUNET_JSON_spec_end () 291 }; 292 293 if (GNUNET_OK != 294 GNUNET_JSON_parse (j, 295 spec, 296 NULL, 297 NULL)) 298 { 299 GNUNET_break_op (0); 300 rs.hr.http_status = 0; 301 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 302 break; 303 } 304 for (unsigned int i = 0; i < proh->num_coins; i++) 305 { 306 const struct CoinData *cdi = &proh->coins[i]; 307 308 if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub, 309 &cdi->coin_pub)) 310 { 311 cd = cdi; 312 break; 313 } 314 } 315 if (NULL == cd) 316 { 317 GNUNET_break_op (0); 318 rs.hr.http_status = 0; 319 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 320 break; 321 } 322 rs.hr.ec = TALER_JSON_get_error_code (j); 323 rs.hr.hint = TALER_JSON_get_error_hint (j); 324 break; 325 } 326 case MHD_HTTP_INTERNAL_SERVER_ERROR: 327 /* Server had an internal issue; we should retry, but this API 328 leaves this to the application */ 329 rs.hr.ec = TALER_JSON_get_error_code (j); 330 rs.hr.hint = TALER_JSON_get_error_hint (j); 331 break; 332 default: 333 /* unexpected response code */ 334 GNUNET_break_op (0); 335 rs.hr.ec = TALER_JSON_get_error_code (j); 336 rs.hr.hint = TALER_JSON_get_error_hint (j); 337 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 338 "Unexpected response code %u/%d for reserves open\n", 339 (unsigned int) response_code, 340 (int) rs.hr.ec); 341 break; 342 } 343 if (NULL != proh->cb) 344 { 345 proh->cb (proh->cb_cls, 346 &rs); 347 proh->cb = NULL; 348 } 349 TALER_EXCHANGE_post_reserves_open_cancel (proh); 350 } 351 352 353 struct TALER_EXCHANGE_PostReservesOpenHandle * 354 TALER_EXCHANGE_post_reserves_open_create ( 355 struct GNUNET_CURL_Context *ctx, 356 const char *url, 357 struct TALER_EXCHANGE_Keys *keys, 358 const struct TALER_ReservePrivateKeyP *reserve_priv, 359 const struct TALER_Amount *reserve_contribution, 360 unsigned int coin_payments_length, 361 const struct TALER_EXCHANGE_PurseDeposit coin_payments[ 362 static coin_payments_length], 363 struct GNUNET_TIME_Timestamp expiration_time, 364 uint32_t min_purses) 365 { 366 struct TALER_EXCHANGE_PostReservesOpenHandle *proh; 367 struct GNUNET_TIME_Timestamp ts; 368 struct TALER_ReserveSignatureP reserve_sig; 369 json_t *cpa; 370 371 proh = GNUNET_new (struct TALER_EXCHANGE_PostReservesOpenHandle); 372 proh->ctx = ctx; 373 proh->base_url = GNUNET_strdup (url); 374 proh->keys = TALER_EXCHANGE_keys_incref (keys); 375 ts = GNUNET_TIME_timestamp_get (); 376 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 377 &proh->reserve_pub.eddsa_pub); 378 TALER_wallet_reserve_open_sign (reserve_contribution, 379 ts, 380 expiration_time, 381 min_purses, 382 reserve_priv, 383 &reserve_sig); 384 proh->coins = GNUNET_new_array (coin_payments_length, 385 struct CoinData); 386 proh->num_coins = coin_payments_length; 387 cpa = json_array (); 388 GNUNET_assert (NULL != cpa); 389 for (unsigned int i = 0; i < coin_payments_length; i++) 390 { 391 const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i]; 392 const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof; 393 struct TALER_AgeCommitmentHashP ahac; 394 struct TALER_AgeCommitmentHashP *achp = NULL; 395 struct CoinData *cd = &proh->coins[i]; 396 json_t *cp; 397 398 cd->contribution = pd->amount; 399 cd->h_denom_pub = pd->h_denom_pub; 400 if (NULL != acp) 401 { 402 TALER_age_commitment_hash (&acp->commitment, 403 &ahac); 404 achp = &ahac; 405 } 406 TALER_wallet_reserve_open_deposit_sign (&pd->amount, 407 &reserve_sig, 408 &pd->coin_priv, 409 &cd->coin_sig); 410 GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, 411 &cd->coin_pub.eddsa_pub); 412 413 cp = GNUNET_JSON_PACK ( 414 GNUNET_JSON_pack_allow_null ( 415 GNUNET_JSON_pack_data_auto ("h_age_commitment", 416 achp)), 417 TALER_JSON_pack_amount ("amount", 418 &pd->amount), 419 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 420 &pd->h_denom_pub), 421 TALER_JSON_pack_denom_sig ("ub_sig", 422 &pd->denom_sig), 423 GNUNET_JSON_pack_data_auto ("coin_pub", 424 &cd->coin_pub), 425 GNUNET_JSON_pack_data_auto ("coin_sig", 426 &cd->coin_sig)); 427 GNUNET_assert (0 == 428 json_array_append_new (cpa, 429 cp)); 430 } 431 proh->body = GNUNET_JSON_PACK ( 432 GNUNET_JSON_pack_timestamp ("request_timestamp", 433 ts), 434 GNUNET_JSON_pack_timestamp ("reserve_expiration", 435 expiration_time), 436 GNUNET_JSON_pack_array_steal ("payments", 437 cpa), 438 TALER_JSON_pack_amount ("reserve_payment", 439 reserve_contribution), 440 GNUNET_JSON_pack_uint64 ("purse_limit", 441 min_purses), 442 GNUNET_JSON_pack_data_auto ("reserve_sig", 443 &reserve_sig)); 444 if (NULL == proh->body) 445 { 446 GNUNET_break (0); 447 GNUNET_free (proh->coins); 448 GNUNET_free (proh->base_url); 449 TALER_EXCHANGE_keys_decref (proh->keys); 450 GNUNET_free (proh); 451 return NULL; 452 } 453 return proh; 454 } 455 456 457 enum TALER_ErrorCode 458 TALER_EXCHANGE_post_reserves_open_start ( 459 struct TALER_EXCHANGE_PostReservesOpenHandle *proh, 460 TALER_EXCHANGE_PostReservesOpenCallback cb, 461 TALER_EXCHANGE_POST_RESERVES_OPEN_RESULT_CLOSURE *cb_cls) 462 { 463 CURL *eh; 464 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 465 466 proh->cb = cb; 467 proh->cb_cls = cb_cls; 468 { 469 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 470 char *end; 471 472 end = GNUNET_STRINGS_data_to_string ( 473 &proh->reserve_pub, 474 sizeof (proh->reserve_pub), 475 pub_str, 476 sizeof (pub_str)); 477 *end = '\0'; 478 GNUNET_snprintf (arg_str, 479 sizeof (arg_str), 480 "reserves/%s/open", 481 pub_str); 482 } 483 proh->url = TALER_url_join (proh->base_url, 484 arg_str, 485 NULL); 486 if (NULL == proh->url) 487 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 488 eh = TALER_EXCHANGE_curl_easy_get_ (proh->url); 489 if ( (NULL == eh) || 490 (GNUNET_OK != 491 TALER_curl_easy_post (&proh->post_ctx, 492 eh, 493 proh->body)) ) 494 { 495 GNUNET_break (0); 496 if (NULL != eh) 497 curl_easy_cleanup (eh); 498 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 499 } 500 proh->job = GNUNET_CURL_job_add2 (proh->ctx, 501 eh, 502 proh->post_ctx.headers, 503 &handle_reserves_open_finished, 504 proh); 505 if (NULL == proh->job) 506 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 507 return TALER_EC_NONE; 508 } 509 510 511 void 512 TALER_EXCHANGE_post_reserves_open_cancel ( 513 struct TALER_EXCHANGE_PostReservesOpenHandle *proh) 514 { 515 if (NULL != proh->job) 516 { 517 GNUNET_CURL_job_cancel (proh->job); 518 proh->job = NULL; 519 } 520 TALER_curl_easy_post_finished (&proh->post_ctx); 521 json_decref (proh->body); 522 GNUNET_free (proh->coins); 523 GNUNET_free (proh->url); 524 GNUNET_free (proh->base_url); 525 TALER_EXCHANGE_keys_decref (proh->keys); 526 GNUNET_free (proh); 527 } 528 529 530 /* end of exchange_api_post-reserves-RESERVE_PUB-open.c */