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