taler-exchange-httpd_post-withdraw.c (55620B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 See the GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General 16 Public License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file taler-exchange-httpd_post-withdraw.c 21 * @brief Code to handle /withdraw requests 22 * @note This endpoint is active since v26 of the protocol API 23 * @author Özgür Kesim 24 */ 25 26 #include <gnunet/gnunet_util_lib.h> 27 #include <jansson.h> 28 #include "taler-exchange-httpd.h" 29 #include "exchange-database/select_withdraw_amounts_for_kyc_check.h" 30 #include "taler/taler_json_lib.h" 31 #include "taler/taler_kyclogic_lib.h" 32 #include "taler/taler_mhd_lib.h" 33 #include "taler-exchange-httpd_post-withdraw.h" 34 #include "taler-exchange-httpd_common_kyc.h" 35 #include "taler-exchange-httpd_responses.h" 36 #include "taler-exchange-httpd_get-keys.h" 37 #include "taler-exchange-httpd_secmod-helpers.h" 38 #include "taler/taler_util.h" 39 #include "exchange-database/do_withdraw.h" 40 #include "exchange-database/get_withdraw.h" 41 #include "exchange-database/reserves_get_origin.h" 42 #include "exchange-database/rollback.h" 43 44 /** 45 * The different type of errors that might occur, sorted by name. 46 * Some of them require idempotency checks, which are marked 47 * in @e idempotency_check_required below. 48 */ 49 enum WithdrawError 50 { 51 WITHDRAW_ERROR_NONE, 52 WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, 53 WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED, 54 WITHDRAW_ERROR_AMOUNT_OVERFLOW, 55 WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW, 56 WITHDRAW_ERROR_BLINDING_SEED_REQUIRED, 57 WITHDRAW_ERROR_CIPHER_MISMATCH, 58 WITHDRAW_ERROR_CONFIRMATION_SIGN, 59 WITHDRAW_ERROR_DB_FETCH_FAILED, 60 WITHDRAW_ERROR_DB_INVARIANT_FAILURE, 61 WITHDRAW_ERROR_DENOMINATION_EXPIRED, 62 WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN, 63 WITHDRAW_ERROR_DENOMINATION_REVOKED, 64 WITHDRAW_ERROR_DENOMINATION_SIGN, 65 WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE, 66 WITHDRAW_ERROR_FEE_OVERFLOW, 67 WITHDRAW_ERROR_IDEMPOTENT_PLANCHET, 68 WITHDRAW_ERROR_INSUFFICIENT_FUNDS, 69 WITHDRAW_ERROR_CRYPTO_HELPER, 70 WITHDRAW_ERROR_KEYS_MISSING, 71 WITHDRAW_ERROR_KYC_REQUIRED, 72 WITHDRAW_ERROR_LEGITIMIZATION_RESULT, 73 WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE, 74 WITHDRAW_ERROR_NONCE_REUSE, 75 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 76 WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN, 77 WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID, 78 WITHDRAW_ERROR_RESERVE_UNKNOWN, 79 }; 80 81 /** 82 * With the bits set in this value will be mark the errors 83 * that require a check for idempotency before actually 84 * returning an error. 85 */ 86 static const uint64_t idempotency_check_required = 87 0 88 | (1LLU << WITHDRAW_ERROR_DENOMINATION_EXPIRED) 89 | (1LLU << WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN) 90 | (1LLU << WITHDRAW_ERROR_DENOMINATION_REVOKED) 91 | (1LLU << WITHDRAW_ERROR_INSUFFICIENT_FUNDS) 92 | (1LLU << WITHDRAW_ERROR_KEYS_MISSING) 93 | (1LLU << WITHDRAW_ERROR_KYC_REQUIRED); 94 95 #define IDEMPOTENCY_CHECK_REQUIRED(ec) \ 96 (0LLU != (idempotency_check_required & (1LLU << (ec)))) 97 98 99 /** 100 * Context for a /withdraw requests 101 */ 102 struct WithdrawContext 103 { 104 105 /** 106 * This struct is kept in a DLL. 107 */ 108 struct WithdrawContext *prev; 109 struct WithdrawContext *next; 110 111 /** 112 * Processing phase we are in. 113 * The ordering here partially matters, as we progress through 114 * them by incrementing the phase in the happy path. 115 */ 116 enum 117 { 118 WITHDRAW_PHASE_PARSE = 0, 119 WITHDRAW_PHASE_CHECK_KEYS, 120 WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE, 121 WITHDRAW_PHASE_RUN_LEGI_CHECK, 122 WITHDRAW_PHASE_SUSPENDED, 123 WITHDRAW_PHASE_CHECK_KYC_RESULT, 124 WITHDRAW_PHASE_PREPARE_TRANSACTION, 125 WITHDRAW_PHASE_RUN_TRANSACTION, 126 WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS, 127 WITHDRAW_PHASE_GENERATE_REPLY_ERROR, 128 WITHDRAW_PHASE_RETURN_NO, 129 WITHDRAW_PHASE_RETURN_YES, 130 } phase; 131 132 133 /** 134 * Handle for the legitimization check. 135 */ 136 struct TEH_LegitimizationCheckHandle *lch; 137 138 /** 139 * Request context 140 */ 141 const struct TEH_RequestContext *rc; 142 143 /** 144 * KYC status for the operation. 145 */ 146 struct TALER_EXCHANGEDB_KycStatus kyc; 147 148 /** 149 * Current time for the DB transaction. 150 */ 151 struct GNUNET_TIME_Timestamp now; 152 153 /** 154 * Set to the hash of the normalized payto URI that established 155 * the reserve. 156 */ 157 struct TALER_NormalizedPaytoHashP h_normalized_payto; 158 159 /** 160 * Captures all parameters provided in the JSON request 161 */ 162 struct 163 { 164 /** 165 * All fields (from the request or computed) 166 * that we persist in the database. 167 */ 168 struct TALER_EXCHANGEDB_Withdraw withdraw; 169 170 /** 171 * In some error cases we check for idempotency. 172 * If we find an entry in the database, we mark this here. 173 */ 174 bool is_idempotent; 175 176 /** 177 * In some error conditions the request is checked 178 * for idempotency and the result from the database 179 * is stored here. 180 */ 181 struct TALER_EXCHANGEDB_Withdraw withdraw_idem; 182 183 /** 184 * Array of ``withdraw.num_coins`` hashes of the public keys 185 * of the denominations to withdraw. 186 */ 187 struct TALER_DenominationHashP *denoms_h; 188 189 /** 190 * Number of planchets. If ``withdraw.max_age`` was _not_ set, this is equal to ``num_coins``. 191 * Otherwise (``withdraw.max_age`` was set) it is ``withdraw.num_coins * kappa``. 192 */ 193 size_t num_planchets; 194 195 /** 196 * Array of ``withdraw.num_planchets`` coin planchets. 197 * Note that the size depends on the age restriction: 198 * If ``withdraw.age_proof_required`` is false, 199 * this is an array of length ``withdraw.num_coins``. 200 * Otherwise it is an array of length ``kappa*withdraw.num_coins``, 201 * arranged in runs of ``num_coins`` coins, 202 * [0..num_coins)..[0..num_coins), 203 * one for each #TALER_CNC_KAPPA value. 204 */ 205 struct TALER_BlindedPlanchet *planchets; 206 207 /** 208 * If proof of age-restriction is required, the #TALER_CNC_KAPPA hashes 209 * of the batches of ``withdraw.num_coins`` coins. 210 */ 211 struct TALER_HashBlindedPlanchetsP kappa_planchets_h[TALER_CNC_KAPPA]; 212 213 /** 214 * Total (over all coins) amount (excluding fee) committed to withdraw 215 */ 216 struct TALER_Amount amount; 217 218 /** 219 * Total fees for the withdraw 220 */ 221 struct TALER_Amount fee; 222 223 /** 224 * Array of length ``withdraw.num_cs_r_values`` of indices into 225 * @e denoms_h of CS denominations. 226 */ 227 uint32_t *cs_indices; 228 229 } request; 230 231 232 /** 233 * Errors occurring during evaluation of the request are captured in this 234 * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error 235 * message is prepared and sent to the client. 236 */ 237 struct 238 { 239 /* The (internal) error code */ 240 enum WithdrawError code; 241 242 /** 243 * Some errors require details to be sent to the client. 244 * These are captured in this union. 245 * Each field is named according to the error that is using it, except 246 * commented otherwise. 247 */ 248 union 249 { 250 const char *request_parameter_malformed; 251 252 const char *reserve_cipher_unknown; 253 254 /** 255 * For all errors related to a particular denomination, i.e. 256 * WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN, 257 * WITHDRAW_ERROR_DENOMINATION_EXPIRED, 258 * WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE, 259 * WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, 260 * we use this one field. 261 */ 262 const struct TALER_DenominationHashP *denom_h; 263 264 const char *db_fetch_context; 265 266 struct 267 { 268 uint16_t max_allowed; 269 uint32_t birthday; 270 } maximum_age_too_large; 271 272 /** 273 * The lowest age required 274 */ 275 uint16_t age_restriction_required; 276 277 /** 278 * Balance of the reserve 279 */ 280 struct TALER_Amount insufficient_funds; 281 282 enum TALER_ErrorCode ec_confirmation_sign; 283 284 enum TALER_ErrorCode ec_denomination_sign; 285 286 struct 287 { 288 struct MHD_Response *response; 289 unsigned int http_status; 290 } legitimization_result; 291 292 } details; 293 } error; 294 }; 295 296 /** 297 * The following macros set the given error code, 298 * set the phase to WITHDRAW_PHASE_GENERATE_REPLY_ERROR, 299 * and optionally set the given field (with an optionally given value). 300 */ 301 #define SET_ERROR(wc, ec) \ 302 do \ 303 { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \ 304 (wc)->error.code = (ec); \ 305 (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0) 306 307 #define SET_ERROR_WITH_FIELD(wc, ec, field) \ 308 do \ 309 { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \ 310 (wc)->error.code = (ec); \ 311 (wc)->error.details.field = (field); \ 312 (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0) 313 314 #define SET_ERROR_WITH_DETAIL(wc, ec, field, value) \ 315 do \ 316 { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \ 317 (wc)->error.code = (ec); \ 318 (wc)->error.details.field = (value); \ 319 (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0) 320 321 322 /** 323 * All withdraw context is kept in a DLL. 324 */ 325 static struct WithdrawContext *wc_head; 326 static struct WithdrawContext *wc_tail; 327 328 329 void 330 TEH_withdraw_cleanup () 331 { 332 struct WithdrawContext *wc; 333 334 while (NULL != (wc = wc_head)) 335 { 336 GNUNET_CONTAINER_DLL_remove (wc_head, 337 wc_tail, 338 wc); 339 wc->phase = WITHDRAW_PHASE_RETURN_NO; 340 MHD_resume_connection (wc->rc->connection); 341 } 342 } 343 344 345 /** 346 * Terminate the main loop by returning the final 347 * result. 348 * 349 * @param[in,out] wc context to update phase for 350 * @param mres MHD status to return 351 */ 352 static void 353 finish_loop (struct WithdrawContext *wc, 354 enum MHD_Result mres) 355 { 356 wc->phase = (MHD_YES == mres) 357 ? WITHDRAW_PHASE_RETURN_YES 358 : WITHDRAW_PHASE_RETURN_NO; 359 } 360 361 362 /** 363 * Check if the withdraw request is replayed 364 * and we already have an answer. 365 * If so, replay the existing answer and return the HTTP response. 366 * 367 * @param[in,out] wc parsed request data 368 * @return true if the request is idempotent with an existing request 369 * false if we did not find the request in the DB and did not set @a mret 370 */ 371 static bool 372 withdraw_is_idempotent ( 373 struct WithdrawContext *wc) 374 { 375 enum GNUNET_DB_QueryStatus qs; 376 uint8_t max_retries = 3; 377 378 /* We should at most be called once */ 379 GNUNET_assert (! wc->request.is_idempotent); 380 while (0 < max_retries--) 381 { 382 qs = TALER_EXCHANGEDB_get_withdraw ( 383 TEH_pg, 384 &wc->request.withdraw.planchets_h, 385 &wc->request.withdraw_idem); 386 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 387 break; 388 } 389 390 if (0 > qs) 391 { 392 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 393 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 394 SET_ERROR_WITH_DETAIL (wc, 395 WITHDRAW_ERROR_DB_FETCH_FAILED, 396 db_fetch_context, 397 "get_withdraw"); 398 return true; /* Well, kind-of. */ 399 } 400 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 401 return false; 402 403 wc->request.is_idempotent = true; 404 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 405 "request is idempotent\n"); 406 407 /* Generate idempotent reply */ 408 TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++; 409 wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS; 410 return true; 411 } 412 413 414 /** 415 * Function implementing withdraw transaction. Runs the 416 * transaction logic; IF it returns a non-error code, the transaction 417 * logic MUST NOT queue a MHD response. IF it returns an hard error, 418 * the transaction logic MUST queue a MHD response and set @a mhd_ret. 419 * IF it returns the soft error code, the function MAY be called again 420 * to retry and MUST not queue a MHD response. 421 * 422 * @param cls a `struct WithdrawContext *` 423 * @param connection MHD request which triggered the transaction 424 * @param[out] mhd_ret set to MHD response status for @a connection, 425 * if transaction failed (!) 426 * @return transaction status 427 */ 428 static enum GNUNET_DB_QueryStatus 429 withdraw_transaction ( 430 void *cls, 431 struct MHD_Connection *connection, 432 enum MHD_Result *mhd_ret) 433 { 434 struct WithdrawContext *wc = cls; 435 enum GNUNET_DB_QueryStatus qs; 436 bool balance_ok; 437 bool age_ok; 438 bool found; 439 uint16_t noreveal_index; 440 bool nonce_reuse; 441 uint16_t allowed_maximum_age; 442 uint32_t reserve_birthday; 443 struct TALER_Amount insufficient_funds; 444 445 qs = TALER_EXCHANGEDB_do_withdraw (TEH_pg, 446 &wc->request.withdraw, 447 &wc->now, 448 &balance_ok, 449 &insufficient_funds, 450 &age_ok, 451 &allowed_maximum_age, 452 &reserve_birthday, 453 &found, 454 &noreveal_index, 455 &nonce_reuse); 456 if (0 > qs) 457 { 458 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 459 SET_ERROR_WITH_DETAIL (wc, 460 WITHDRAW_ERROR_DB_FETCH_FAILED, 461 db_fetch_context, 462 "do_withdraw"); 463 return qs; 464 } 465 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 466 { 467 SET_ERROR (wc, 468 WITHDRAW_ERROR_RESERVE_UNKNOWN); 469 return GNUNET_DB_STATUS_HARD_ERROR; 470 } 471 472 if (found) 473 { 474 /** 475 * The request was idempotent and we got the previous noreveal_index. 476 * We simply overwrite that value in our current withdraw object and 477 * move on to reply success. 478 */ 479 wc->request.withdraw.noreveal_index = noreveal_index; 480 wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS; 481 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 482 } 483 484 if (! age_ok) 485 { 486 if (wc->request.withdraw.age_proof_required) 487 { 488 wc->error.details.maximum_age_too_large.max_allowed = allowed_maximum_age; 489 wc->error.details.maximum_age_too_large.birthday = reserve_birthday; 490 SET_ERROR (wc, 491 WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE); 492 } 493 else 494 { 495 wc->error.details.age_restriction_required = allowed_maximum_age; 496 SET_ERROR (wc, 497 WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED); 498 } 499 return GNUNET_DB_STATUS_HARD_ERROR; 500 } 501 502 if (! balance_ok) 503 { 504 TALER_EXCHANGEDB_rollback (TEH_pg); 505 SET_ERROR_WITH_FIELD (wc, 506 WITHDRAW_ERROR_INSUFFICIENT_FUNDS, 507 insufficient_funds); 508 return GNUNET_DB_STATUS_HARD_ERROR; 509 } 510 511 if (nonce_reuse) 512 { 513 GNUNET_break (0); 514 SET_ERROR (wc, 515 WITHDRAW_ERROR_NONCE_REUSE); 516 return GNUNET_DB_STATUS_HARD_ERROR; 517 } 518 519 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 520 TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++; 521 return qs; 522 } 523 524 525 /** 526 * The request was prepared successfully. 527 * Run the main DB transaction. 528 * 529 * @param wc The context for the current withdraw request 530 */ 531 static void 532 phase_run_transaction ( 533 struct WithdrawContext *wc) 534 { 535 enum MHD_Result mhd_ret; 536 enum GNUNET_GenericReturnValue qs; 537 538 GNUNET_assert (WITHDRAW_PHASE_RUN_TRANSACTION == 539 wc->phase); 540 qs = TEH_DB_run_transaction (wc->rc->connection, 541 "run withdraw", 542 TEH_MT_REQUEST_WITHDRAW, 543 &mhd_ret, 544 &withdraw_transaction, 545 wc); 546 if (WITHDRAW_PHASE_RUN_TRANSACTION != wc->phase) 547 return; 548 GNUNET_break (GNUNET_OK == qs); 549 /* If the transaction has changed the phase, we don't alter it and return.*/ 550 wc->phase++; 551 } 552 553 554 /** 555 * The request for withdraw was parsed successfully. 556 * Sign and persist the chosen blinded coins for the reveal step. 557 * 558 * @param wc The context for the current withdraw request 559 */ 560 static void 561 phase_prepare_transaction ( 562 struct WithdrawContext *wc) 563 { 564 size_t offset = 0; 565 566 wc->request.withdraw.denom_sigs 567 = GNUNET_new_array ( 568 wc->request.withdraw.num_coins, 569 struct TALER_BlindedDenominationSignature); 570 /* Pick the challenge in case of age restriction */ 571 if (wc->request.withdraw.age_proof_required) 572 { 573 wc->request.withdraw.noreveal_index = 574 GNUNET_CRYPTO_random_u32 (TALER_CNC_KAPPA); 575 /** 576 * In case of age restriction, we use the corresponding offset in the planchet 577 * array to the beginning of the coins corresponding to the noreveal_index. 578 */ 579 offset = wc->request.withdraw.noreveal_index 580 * wc->request.withdraw.num_coins; 581 GNUNET_assert (offset + wc->request.withdraw.num_coins <= 582 wc->request.num_planchets); 583 } 584 585 /* Choose and sign the coins */ 586 { 587 struct TEH_SECMOD_CoinSignData csds[wc->request.withdraw.num_coins]; 588 enum TALER_ErrorCode ec_denomination_sign; 589 590 memset (csds, 591 0, 592 sizeof(csds)); 593 594 /* Pick the chosen blinded coins */ 595 for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++) 596 { 597 csds[i].bp = &wc->request.planchets[i + offset]; 598 csds[i].h_denom_pub = &wc->request.denoms_h[i]; 599 } 600 601 ec_denomination_sign = TEH_SECMOD_denom_batch_sign ( 602 wc->request.withdraw.num_coins, 603 csds, 604 false, 605 wc->request.withdraw.denom_sigs); 606 if (TALER_EC_NONE != ec_denomination_sign) 607 { 608 GNUNET_break (0); 609 SET_ERROR_WITH_FIELD (wc, 610 WITHDRAW_ERROR_DENOMINATION_SIGN, 611 ec_denomination_sign); 612 return; 613 } 614 615 /* Save the hash value of the selected batch of coins */ 616 wc->request.withdraw.selected_h = 617 wc->request.kappa_planchets_h[wc->request.withdraw.noreveal_index]; 618 } 619 620 /** 621 * For the denominations with cipher CS, calculate the R-values 622 * and save the choices we made now, as at a later point, the 623 * private keys for the denominations might now be available anymore 624 * to make the same choice again. 625 */ 626 if (0 < wc->request.withdraw.num_cs_r_values) 627 { 628 size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values; 629 struct TEH_SECMOD_CsDeriveData cdds[num_cs_r_values]; 630 struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values]; 631 632 memset (nonces, 633 0, 634 sizeof(nonces)); 635 wc->request.withdraw.cs_r_values 636 = GNUNET_new_array ( 637 num_cs_r_values, 638 struct GNUNET_CRYPTO_CSPublicRPairP); 639 wc->request.withdraw.cs_r_choices = 0; 640 641 GNUNET_assert (! wc->request.withdraw.no_blinding_seed); 642 TALER_cs_derive_nonces_from_seed ( 643 &wc->request.withdraw.blinding_seed, 644 false, /* not for melt */ 645 num_cs_r_values, 646 wc->request.cs_indices, 647 nonces); 648 649 for (size_t i = 0; i < num_cs_r_values; i++) 650 { 651 size_t idx = wc->request.cs_indices[i]; 652 653 GNUNET_assert (idx < wc->request.withdraw.num_coins); 654 cdds[i].h_denom_pub = &wc->request.denoms_h[idx]; 655 cdds[i].nonce = &nonces[i]; 656 } 657 658 /** 659 * Let the crypto helper generate the R-values and make the choices. 660 */ 661 if (TALER_EC_NONE != 662 TEH_SECMOD_denom_cs_batch_r_pub_simple ( 663 wc->request.withdraw.num_cs_r_values, 664 cdds, 665 false, 666 wc->request.withdraw.cs_r_values)) 667 { 668 GNUNET_break (0); 669 SET_ERROR (wc, 670 WITHDRAW_ERROR_CRYPTO_HELPER); 671 return; 672 } 673 674 /* This invariant should hold because 675 num_coins <= TALER_MAX_COINS. Still good 676 to check explicitly. */ 677 GNUNET_assert (num_cs_r_values <= 64); 678 /* Now save the choices for the selected bits */ 679 for (size_t i = 0; i < num_cs_r_values; i++) 680 { 681 size_t idx = wc->request.cs_indices[i]; 682 683 struct TALER_BlindedDenominationSignature *sig = 684 &wc->request.withdraw.denom_sigs[idx]; 685 uint64_t bit = sig->blinded_sig->details.blinded_cs_answer.b; 686 687 GNUNET_assert (bit <= 1); /* well, should actually be 0 or 1 */ 688 wc->request.withdraw.cs_r_choices |= bit << i; 689 GNUNET_static_assert ( 690 TALER_MAX_COINS <= 691 sizeof(wc->request.withdraw.cs_r_choices) * 8); 692 } 693 } 694 wc->phase++; 695 } 696 697 698 /** 699 * Check the KYC result. 700 * 701 * @param wc context for request processing 702 */ 703 static void 704 phase_check_kyc_result (struct WithdrawContext *wc) 705 { 706 /* return final positive response */ 707 if (! wc->kyc.ok) 708 { 709 SET_ERROR (wc, 710 WITHDRAW_ERROR_KYC_REQUIRED); 711 return; 712 } 713 wc->phase++; 714 } 715 716 717 /** 718 * Function called with the result of a legitimization 719 * check. 720 * 721 * @param cls closure 722 * @param lcr legitimization check result 723 */ 724 static void 725 withdraw_legi_cb ( 726 void *cls, 727 const struct TEH_LegitimizationCheckResult *lcr) 728 { 729 struct WithdrawContext *wc = cls; 730 731 wc->lch = NULL; 732 GNUNET_assert (WITHDRAW_PHASE_SUSPENDED == 733 wc->phase); 734 MHD_resume_connection (wc->rc->connection); 735 GNUNET_CONTAINER_DLL_remove (wc_head, 736 wc_tail, 737 wc); 738 TALER_MHD_daemon_trigger (); 739 if (NULL != lcr->response) 740 { 741 wc->error.details.legitimization_result.response = lcr->response; 742 wc->error.details.legitimization_result.http_status = lcr->http_status; 743 SET_ERROR (wc, 744 WITHDRAW_ERROR_LEGITIMIZATION_RESULT); 745 return; 746 } 747 wc->kyc = lcr->kyc; 748 wc->phase = WITHDRAW_PHASE_CHECK_KYC_RESULT; 749 } 750 751 752 /** 753 * Function called to iterate over KYC-relevant transaction amounts for a 754 * particular time range. Called within a database transaction, so must 755 * not start a new one. 756 * 757 * @param cls closure, identifies the event type and account to iterate 758 * over events for 759 * @param limit maximum time-range for which events should be fetched 760 * (timestamp in the past) 761 * @param cb function to call on each event found, events must be returned 762 * in reverse chronological order 763 * @param cb_cls closure for @a cb, of type struct WithdrawContext 764 * @return transaction status 765 */ 766 static enum GNUNET_DB_QueryStatus 767 withdraw_amount_cb ( 768 void *cls, 769 struct GNUNET_TIME_Absolute limit, 770 TALER_KYCLOGIC_KycAmountCallback cb, 771 void *cb_cls) 772 { 773 struct WithdrawContext *wc = cls; 774 enum GNUNET_GenericReturnValue ret; 775 enum GNUNET_DB_QueryStatus qs; 776 777 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 778 "Signaling amount %s for KYC check during witdrawal\n", 779 TALER_amount2s (&wc->request.withdraw.amount_with_fee)); 780 781 ret = cb (cb_cls, 782 &wc->request.withdraw.amount_with_fee, 783 wc->now.abs_time); 784 GNUNET_break (GNUNET_SYSERR != ret); 785 if (GNUNET_OK != ret) 786 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 787 788 qs = TALER_EXCHANGEDB_select_withdraw_amounts_for_kyc_check ( 789 TEH_pg, 790 &wc->h_normalized_payto, 791 limit, 792 cb, 793 cb_cls); 794 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 795 "Got %d additional transactions for this withdrawal and limit %llu\n", 796 qs, 797 (unsigned long long) limit.abs_value_us); 798 GNUNET_break (qs >= 0); 799 return qs; 800 } 801 802 803 /** 804 * Do legitimization check. 805 * 806 * @param wc operation context 807 */ 808 static void 809 phase_run_legi_check (struct WithdrawContext *wc) 810 { 811 enum GNUNET_DB_QueryStatus qs; 812 struct TALER_FullPayto payto_uri; 813 struct TALER_FullPaytoHashP h_full_payto; 814 815 /* Check if the money came from a wire transfer */ 816 qs = TALER_TALER_EXCHANGEDB_reserves_get_origin ( 817 TEH_pg, 818 &wc->request.withdraw.reserve_pub, 819 &h_full_payto, 820 &payto_uri); 821 if (qs < 0) 822 { 823 SET_ERROR_WITH_DETAIL (wc, 824 WITHDRAW_ERROR_DB_FETCH_FAILED, 825 db_fetch_context, 826 "reserves_get_origin"); 827 return; 828 } 829 /* If _no_ results, reserve was created by merge, 830 in which case no KYC check is required as the 831 merge already did that. */ 832 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 833 { 834 wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION; 835 return; 836 } 837 TALER_full_payto_normalize_and_hash (payto_uri, 838 &wc->h_normalized_payto); 839 wc->lch = TEH_legitimization_check ( 840 &wc->rc->async_scope_id, 841 TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, 842 payto_uri, 843 &wc->h_normalized_payto, 844 NULL, /* no account pub: this is about the origin account */ 845 &withdraw_amount_cb, 846 wc, 847 &withdraw_legi_cb, 848 wc); 849 GNUNET_assert (NULL != wc->lch); 850 GNUNET_free (payto_uri.full_payto); 851 GNUNET_CONTAINER_DLL_insert (wc_head, 852 wc_tail, 853 wc); 854 MHD_suspend_connection (wc->rc->connection); 855 wc->phase = WITHDRAW_PHASE_SUSPENDED; 856 } 857 858 859 /** 860 * Check if the given denomination is still or already valid, has not been 861 * revoked and potentically supports age restriction. 862 * 863 * @param[in,out] wc context for the withdraw operation 864 * @param ksh The handle to the current state of (denomination) keys in the exchange 865 * @param denom_h Hash of the denomination key to check 866 * @param[out] pdk denomination key found, might be NULL 867 * @return true when denomation was found and valid, 868 * false when denomination was not valid and the state machine was advanced 869 */ 870 static enum GNUNET_GenericReturnValue 871 find_denomination ( 872 struct WithdrawContext *wc, 873 struct TEH_KeyStateHandle *ksh, 874 const struct TALER_DenominationHashP *denom_h, 875 struct TEH_DenominationKey **pdk) 876 { 877 struct TEH_DenominationKey *dk; 878 879 *pdk = NULL; 880 dk = TEH_keys_denomination_by_hash_from_state ( 881 ksh, 882 denom_h, 883 NULL, 884 NULL); 885 if (NULL == dk) 886 { 887 SET_ERROR_WITH_FIELD (wc, 888 WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN, 889 denom_h); 890 return false; 891 } 892 if (GNUNET_TIME_absolute_is_past ( 893 dk->meta.expire_withdraw.abs_time)) 894 { 895 SET_ERROR_WITH_FIELD (wc, 896 WITHDRAW_ERROR_DENOMINATION_EXPIRED, 897 denom_h); 898 return false; 899 } 900 if (GNUNET_TIME_absolute_is_future ( 901 dk->meta.start.abs_time)) 902 { 903 GNUNET_break_op (0); 904 SET_ERROR_WITH_FIELD (wc, 905 WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE, 906 denom_h); 907 return false; 908 } 909 if (dk->recoup_possible) 910 { 911 SET_ERROR (wc, 912 WITHDRAW_ERROR_DENOMINATION_REVOKED); 913 return false; 914 } 915 /* In case of age withdraw, make sure that the denomination supports age restriction */ 916 if (wc->request.withdraw.age_proof_required) 917 { 918 if (0 == dk->denom_pub.age_mask.bits) 919 { 920 GNUNET_break_op (0); 921 SET_ERROR_WITH_FIELD (wc, 922 WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, 923 denom_h); 924 return false; 925 } 926 } 927 *pdk = dk; 928 return true; 929 } 930 931 932 /** 933 * Check if the given array of hashes of denomination_keys 934 * a) belong to valid denominations 935 * b) those are marked as age restricted, if the request is age restricted 936 * c) calculate the total amount of the denominations including fees 937 * for withdraw. 938 * 939 * @param wc context of the age withdrawal to check keys for 940 */ 941 static void 942 phase_check_keys ( 943 struct WithdrawContext *wc) 944 { 945 struct TEH_KeyStateHandle *ksh; 946 bool is_cs_denom[wc->request.withdraw.num_coins]; 947 948 memset (is_cs_denom, 949 0, 950 sizeof(is_cs_denom)); 951 ksh = TEH_keys_get_state (); 952 if (NULL == ksh) 953 { 954 GNUNET_break (0); 955 SET_ERROR (wc, 956 WITHDRAW_ERROR_KEYS_MISSING); 957 return; 958 } 959 wc->request.withdraw.denom_serials = 960 GNUNET_new_array (wc->request.withdraw.num_coins, 961 uint64_t); 962 GNUNET_assert (GNUNET_OK == 963 TALER_amount_set_zero (TEH_currency, 964 &wc->request.amount)); 965 GNUNET_assert (GNUNET_OK == 966 TALER_amount_set_zero (TEH_currency, 967 &wc->request.fee)); 968 GNUNET_assert (GNUNET_OK == 969 TALER_amount_set_zero (TEH_currency, 970 &wc->request.withdraw.amount_with_fee)); 971 972 for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++) 973 { 974 struct TEH_DenominationKey *dk; 975 976 if (! find_denomination (wc, 977 ksh, 978 &wc->request.denoms_h[i], 979 &dk)) 980 return; 981 switch (dk->denom_pub.bsign_pub_key->cipher) 982 { 983 case GNUNET_CRYPTO_BSA_INVALID: 984 /* This should never happen (memory corruption?) */ 985 GNUNET_assert (0); 986 case GNUNET_CRYPTO_BSA_RSA: 987 /* nothing to do here */ 988 break; 989 case GNUNET_CRYPTO_BSA_CS: 990 if (wc->request.withdraw.no_blinding_seed) 991 { 992 GNUNET_break_op (0); 993 SET_ERROR (wc, 994 WITHDRAW_ERROR_BLINDING_SEED_REQUIRED); 995 return; 996 } 997 wc->request.withdraw.num_cs_r_values++; 998 is_cs_denom[i] = true; 999 break; 1000 } 1001 1002 /* Ensure the ciphers from the planchets match the denominations'. */ 1003 if (wc->request.withdraw.age_proof_required) 1004 { 1005 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 1006 { 1007 size_t off = k * wc->request.withdraw.num_coins; 1008 1009 if (dk->denom_pub.bsign_pub_key->cipher != 1010 wc->request.planchets[i + off].blinded_message->cipher) 1011 { 1012 GNUNET_break_op (0); 1013 SET_ERROR (wc, 1014 WITHDRAW_ERROR_CIPHER_MISMATCH); 1015 return; 1016 } 1017 } 1018 } 1019 else 1020 { 1021 if (dk->denom_pub.bsign_pub_key->cipher != 1022 wc->request.planchets[i].blinded_message->cipher) 1023 { 1024 GNUNET_break_op (0); 1025 SET_ERROR (wc, 1026 WITHDRAW_ERROR_CIPHER_MISMATCH); 1027 return; 1028 } 1029 } 1030 1031 /* Accumulate the values */ 1032 if (0 > TALER_amount_add (&wc->request.amount, 1033 &wc->request.amount, 1034 &dk->meta.value)) 1035 { 1036 GNUNET_break_op (0); 1037 SET_ERROR (wc, 1038 WITHDRAW_ERROR_AMOUNT_OVERFLOW); 1039 return; 1040 } 1041 1042 /* Accumulate the withdraw fees */ 1043 if (0 > TALER_amount_add (&wc->request.fee, 1044 &wc->request.fee, 1045 &dk->meta.fees.withdraw)) 1046 { 1047 GNUNET_break_op (0); 1048 SET_ERROR (wc, 1049 WITHDRAW_ERROR_FEE_OVERFLOW); 1050 return; 1051 } 1052 wc->request.withdraw.denom_serials[i] = dk->meta.serial; 1053 } 1054 1055 /* Save the hash of the batch of planchets */ 1056 if (! wc->request.withdraw.age_proof_required) 1057 { 1058 TALER_wallet_blinded_planchets_hash ( 1059 wc->request.withdraw.num_coins, 1060 wc->request.planchets, 1061 wc->request.denoms_h, 1062 &wc->request.withdraw.planchets_h); 1063 } 1064 else 1065 { 1066 struct GNUNET_HashContext *ctx; 1067 1068 /** 1069 * The age-proof-required case is a bit more involved, 1070 * because we need to calculate and remember kappa hashes 1071 * for each batch of coins. 1072 */ 1073 ctx = GNUNET_CRYPTO_hash_context_start (); 1074 GNUNET_assert (NULL != ctx); 1075 1076 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 1077 { 1078 size_t off = k * wc->request.withdraw.num_coins; 1079 1080 TALER_wallet_blinded_planchets_hash ( 1081 wc->request.withdraw.num_coins, 1082 &wc->request.planchets[off], 1083 wc->request.denoms_h, 1084 &wc->request.kappa_planchets_h[k]); 1085 GNUNET_CRYPTO_hash_context_read ( 1086 ctx, 1087 &wc->request.kappa_planchets_h[k], 1088 sizeof(wc->request.kappa_planchets_h[k])); 1089 } 1090 GNUNET_CRYPTO_hash_context_finish ( 1091 ctx, 1092 &wc->request.withdraw.planchets_h.hash); 1093 } 1094 1095 /* Save the total amount including fees */ 1096 if (0 > TALER_amount_add ( 1097 &wc->request.withdraw.amount_with_fee, 1098 &wc->request.amount, 1099 &wc->request.fee)) 1100 { 1101 GNUNET_break_op (0); 1102 SET_ERROR (wc, 1103 WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW); 1104 return; 1105 } 1106 1107 /* Save the indices of CS denominations */ 1108 if (0 < wc->request.withdraw.num_cs_r_values) 1109 { 1110 size_t j = 0; 1111 1112 wc->request.cs_indices = GNUNET_new_array ( 1113 wc->request.withdraw.num_cs_r_values, 1114 uint32_t); 1115 1116 for (size_t i = 0; i < wc->request.withdraw.num_coins; i++) 1117 { 1118 if (is_cs_denom[i]) 1119 wc->request.cs_indices[j++] = i; 1120 } 1121 } 1122 1123 wc->phase++; 1124 } 1125 1126 1127 /** 1128 * Check that the client signature authorizing the withdrawal is valid. 1129 * 1130 * @param[in,out] wc request context to check 1131 */ 1132 static void 1133 phase_check_reserve_signature ( 1134 struct WithdrawContext *wc) 1135 { 1136 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 1137 if (GNUNET_OK != 1138 TALER_wallet_withdraw_verify ( 1139 &wc->request.amount, 1140 &wc->request.fee, 1141 &wc->request.withdraw.planchets_h, 1142 wc->request.withdraw.no_blinding_seed 1143 ? NULL 1144 : &wc->request.withdraw.blinding_seed, 1145 (wc->request.withdraw.age_proof_required) 1146 ? &TEH_age_restriction_mask 1147 : NULL, 1148 (wc->request.withdraw.age_proof_required) 1149 ? wc->request.withdraw.max_age 1150 : 0, 1151 &wc->request.withdraw.reserve_pub, 1152 &wc->request.withdraw.reserve_sig)) 1153 { 1154 GNUNET_break_op (0); 1155 SET_ERROR (wc, 1156 WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID); 1157 return; 1158 } 1159 wc->phase++; 1160 } 1161 1162 1163 /** 1164 * Free data inside of @a wd, but not @a wd itself. 1165 * 1166 * @param[in] wd withdraw data to free 1167 */ 1168 static void 1169 free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd) 1170 { 1171 if (NULL != wd->denom_sigs) 1172 { 1173 for (unsigned int i = 0; i<wd->num_coins; i++) 1174 TALER_blinded_denom_sig_free (&wd->denom_sigs[i]); 1175 GNUNET_free (wd->denom_sigs); 1176 } 1177 GNUNET_free (wd->denom_serials); 1178 GNUNET_free (wd->cs_r_values); 1179 } 1180 1181 1182 /** 1183 * Cleanup routine for withdraw request. 1184 * The function is called upon completion of the request 1185 * that should clean up @a rh_ctx. Can be NULL. 1186 * 1187 * @param rc request context to clean up 1188 */ 1189 static void 1190 clean_withdraw_rc (struct TEH_RequestContext *rc) 1191 { 1192 struct WithdrawContext *wc = rc->rh_ctx; 1193 1194 if (NULL != wc->lch) 1195 { 1196 TEH_legitimization_check_cancel (wc->lch); 1197 wc->lch = NULL; 1198 } 1199 GNUNET_free (wc->request.denoms_h); 1200 if (NULL != wc->request.planchets) 1201 { 1202 /* num_planchets is set long before planchets is allocated, 1203 so this needs the above guard */ 1204 for (unsigned int i = 0; i<wc->request.num_planchets; i++) 1205 TALER_blinded_planchet_free (&wc->request.planchets[i]); 1206 GNUNET_free (wc->request.planchets); 1207 } 1208 free_db_withdraw_data (&wc->request.withdraw); 1209 GNUNET_free (wc->request.cs_indices); 1210 if (wc->request.is_idempotent) 1211 free_db_withdraw_data (&wc->request.withdraw_idem); 1212 if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) && 1213 (NULL != wc->error.details.legitimization_result.response) ) 1214 { 1215 MHD_destroy_response (wc->error.details.legitimization_result.response); 1216 wc->error.details.legitimization_result.response = NULL; 1217 } 1218 GNUNET_free (wc); 1219 } 1220 1221 1222 /** 1223 * Generates response for the withdraw request. 1224 * 1225 * @param wc withdraw operation context 1226 */ 1227 static void 1228 phase_generate_reply_success (struct WithdrawContext *wc) 1229 { 1230 struct TALER_EXCHANGEDB_Withdraw *db_obj; 1231 1232 db_obj = wc->request.is_idempotent 1233 ? &wc->request.withdraw_idem 1234 : &wc->request.withdraw; 1235 1236 if (wc->request.withdraw.age_proof_required) 1237 { 1238 struct TALER_ExchangePublicKeyP pub; 1239 struct TALER_ExchangeSignatureP sig; 1240 enum TALER_ErrorCode ec_confirmation_sign; 1241 1242 ec_confirmation_sign = 1243 TALER_exchange_online_withdraw_age_confirmation_sign ( 1244 &TEH_keys_exchange_sign_, 1245 &db_obj->planchets_h, 1246 db_obj->noreveal_index, 1247 &pub, 1248 &sig); 1249 if (TALER_EC_NONE != ec_confirmation_sign) 1250 { 1251 SET_ERROR_WITH_FIELD (wc, 1252 WITHDRAW_ERROR_CONFIRMATION_SIGN, 1253 ec_confirmation_sign); 1254 return; 1255 } 1256 1257 finish_loop (wc, 1258 TALER_MHD_REPLY_JSON_PACK ( 1259 wc->rc->connection, 1260 MHD_HTTP_CREATED, 1261 GNUNET_JSON_pack_uint64 ("noreveal_index", 1262 db_obj->noreveal_index), 1263 GNUNET_JSON_pack_data_auto ("exchange_sig", 1264 &sig), 1265 GNUNET_JSON_pack_data_auto ("exchange_pub", 1266 &pub))); 1267 } 1268 else /* not age restricted */ 1269 { 1270 json_t *sigs; 1271 1272 sigs = json_array (); 1273 GNUNET_assert (NULL != sigs); 1274 for (unsigned int i = 0; i<db_obj->num_coins; i++) 1275 { 1276 GNUNET_assert ( 1277 0 == 1278 json_array_append_new ( 1279 sigs, 1280 GNUNET_JSON_PACK ( 1281 TALER_JSON_pack_blinded_denom_sig ( 1282 NULL, 1283 &db_obj->denom_sigs[i])))); 1284 } 1285 finish_loop (wc, 1286 TALER_MHD_REPLY_JSON_PACK ( 1287 wc->rc->connection, 1288 MHD_HTTP_OK, 1289 GNUNET_JSON_pack_array_steal ("ev_sigs", 1290 sigs))); 1291 } 1292 1293 TEH_METRICS_withdraw_num_coins += db_obj->num_coins; 1294 } 1295 1296 1297 /** 1298 * Reports an error, potentially with details. 1299 * That is, it puts a error-type specific response into the MHD queue. 1300 * It will do a idempotency check first, if needed for the error type. 1301 * 1302 * @param wc withdraw context 1303 */ 1304 static void 1305 phase_generate_reply_error ( 1306 struct WithdrawContext *wc) 1307 { 1308 GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase); 1309 if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) && 1310 withdraw_is_idempotent (wc)) 1311 { 1312 return; 1313 } 1314 1315 switch (wc->error.code) 1316 { 1317 case WITHDRAW_ERROR_NONE: 1318 break; 1319 case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED: 1320 finish_loop (wc, 1321 TALER_MHD_reply_with_error ( 1322 wc->rc->connection, 1323 MHD_HTTP_BAD_REQUEST, 1324 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1325 wc->error.details.request_parameter_malformed)); 1326 return; 1327 case WITHDRAW_ERROR_KEYS_MISSING: 1328 finish_loop (wc, 1329 TALER_MHD_reply_with_error ( 1330 wc->rc->connection, 1331 MHD_HTTP_SERVICE_UNAVAILABLE, 1332 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 1333 NULL)); 1334 return; 1335 case WITHDRAW_ERROR_DB_FETCH_FAILED: 1336 finish_loop (wc, 1337 TALER_MHD_reply_with_error ( 1338 wc->rc->connection, 1339 MHD_HTTP_INTERNAL_SERVER_ERROR, 1340 TALER_EC_GENERIC_DB_FETCH_FAILED, 1341 wc->error.details.db_fetch_context)); 1342 return; 1343 case WITHDRAW_ERROR_DB_INVARIANT_FAILURE: 1344 finish_loop (wc, 1345 TALER_MHD_reply_with_error ( 1346 wc->rc->connection, 1347 MHD_HTTP_INTERNAL_SERVER_ERROR, 1348 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 1349 NULL)); 1350 return; 1351 case WITHDRAW_ERROR_RESERVE_UNKNOWN: 1352 finish_loop (wc, 1353 TALER_MHD_reply_with_error ( 1354 wc->rc->connection, 1355 MHD_HTTP_NOT_FOUND, 1356 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 1357 NULL)); 1358 return; 1359 case WITHDRAW_ERROR_DENOMINATION_SIGN: 1360 finish_loop (wc, 1361 TALER_MHD_reply_with_ec ( 1362 wc->rc->connection, 1363 wc->error.details.ec_denomination_sign, 1364 NULL)); 1365 return; 1366 case WITHDRAW_ERROR_KYC_REQUIRED: 1367 finish_loop (wc, 1368 TEH_RESPONSE_reply_kyc_required ( 1369 wc->rc->connection, 1370 &wc->h_normalized_payto, 1371 &wc->kyc, 1372 false)); 1373 return; 1374 case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN: 1375 GNUNET_break_op (0); 1376 finish_loop (wc, 1377 TEH_RESPONSE_reply_unknown_denom_pub_hash ( 1378 wc->rc->connection, 1379 wc->error.details.denom_h)); 1380 return; 1381 case WITHDRAW_ERROR_DENOMINATION_EXPIRED: 1382 GNUNET_break_op (0); 1383 finish_loop (wc, 1384 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1385 wc->rc->connection, 1386 wc->error.details.denom_h, 1387 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, 1388 "WITHDRAW")); 1389 return; 1390 case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE: 1391 finish_loop (wc, 1392 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1393 wc->rc->connection, 1394 wc->error.details.denom_h, 1395 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, 1396 "WITHDRAW")); 1397 return; 1398 case WITHDRAW_ERROR_DENOMINATION_REVOKED: 1399 GNUNET_break_op (0); 1400 finish_loop (wc, 1401 TALER_MHD_reply_with_error ( 1402 wc->rc->connection, 1403 MHD_HTTP_GONE, 1404 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, 1405 NULL)); 1406 return; 1407 case WITHDRAW_ERROR_CIPHER_MISMATCH: 1408 finish_loop (wc, 1409 TALER_MHD_reply_with_error ( 1410 wc->rc->connection, 1411 MHD_HTTP_BAD_REQUEST, 1412 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1413 NULL)); 1414 return; 1415 case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED: 1416 finish_loop (wc, 1417 TALER_MHD_reply_with_error ( 1418 wc->rc->connection, 1419 MHD_HTTP_BAD_REQUEST, 1420 TALER_EC_GENERIC_PARAMETER_MISSING, 1421 "blinding_seed")); 1422 return; 1423 case WITHDRAW_ERROR_CRYPTO_HELPER: 1424 finish_loop (wc, 1425 TALER_MHD_reply_with_error ( 1426 wc->rc->connection, 1427 MHD_HTTP_INTERNAL_SERVER_ERROR, 1428 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1429 NULL)); 1430 return; 1431 case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN: 1432 finish_loop (wc, 1433 TALER_MHD_reply_with_error ( 1434 wc->rc->connection, 1435 MHD_HTTP_BAD_REQUEST, 1436 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1437 "cipher")); 1438 return; 1439 case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION: 1440 { 1441 char msg[256]; 1442 1443 GNUNET_snprintf (msg, 1444 sizeof(msg), 1445 "denomination %s does not support age restriction", 1446 GNUNET_h2s (&wc->error.details.denom_h->hash)); 1447 finish_loop (wc, 1448 TALER_MHD_reply_with_error ( 1449 wc->rc->connection, 1450 MHD_HTTP_NOT_FOUND, 1451 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, 1452 msg)); 1453 return; 1454 } 1455 case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE: 1456 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1457 "Generating JSON response with code %d\n", 1458 (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE); 1459 finish_loop (wc, 1460 TALER_MHD_REPLY_JSON_PACK ( 1461 wc->rc->connection, 1462 MHD_HTTP_CONFLICT, 1463 TALER_MHD_PACK_EC ( 1464 TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE), 1465 GNUNET_JSON_pack_uint64 ( 1466 "allowed_maximum_age", 1467 wc->error.details.maximum_age_too_large.max_allowed), 1468 GNUNET_JSON_pack_uint64 ( 1469 "reserve_birthday", 1470 wc->error.details.maximum_age_too_large.birthday))); 1471 return; 1472 case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED: 1473 finish_loop (wc, 1474 TEH_RESPONSE_reply_reserve_age_restriction_required ( 1475 wc->rc->connection, 1476 wc->error.details.age_restriction_required)); 1477 return; 1478 case WITHDRAW_ERROR_AMOUNT_OVERFLOW: 1479 finish_loop (wc, 1480 TALER_MHD_reply_with_error ( 1481 wc->rc->connection, 1482 MHD_HTTP_BAD_REQUEST, 1483 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1484 "amount")); 1485 return; 1486 case WITHDRAW_ERROR_FEE_OVERFLOW: 1487 finish_loop (wc, 1488 TALER_MHD_reply_with_error ( 1489 wc->rc->connection, 1490 MHD_HTTP_BAD_REQUEST, 1491 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1492 "fee")); 1493 return; 1494 case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW: 1495 finish_loop (wc, 1496 TALER_MHD_reply_with_error ( 1497 wc->rc->connection, 1498 MHD_HTTP_INTERNAL_SERVER_ERROR, 1499 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW, 1500 "amount+fee")); 1501 return; 1502 case WITHDRAW_ERROR_CONFIRMATION_SIGN: 1503 finish_loop (wc, 1504 TALER_MHD_reply_with_ec ( 1505 wc->rc->connection, 1506 wc->error.details.ec_confirmation_sign, 1507 NULL)); 1508 return; 1509 case WITHDRAW_ERROR_INSUFFICIENT_FUNDS: 1510 finish_loop (wc, 1511 TEH_RESPONSE_reply_reserve_insufficient_balance ( 1512 wc->rc->connection, 1513 TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, 1514 &wc->error.details.insufficient_funds, 1515 &wc->request.withdraw.amount_with_fee, 1516 &wc->request.withdraw.reserve_pub)); 1517 return; 1518 case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET: 1519 finish_loop (wc, 1520 TALER_MHD_reply_with_error ( 1521 wc->rc->connection, 1522 MHD_HTTP_BAD_REQUEST, 1523 TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET, 1524 NULL)); 1525 return; 1526 case WITHDRAW_ERROR_NONCE_REUSE: 1527 finish_loop (wc, 1528 TALER_MHD_reply_with_error ( 1529 wc->rc->connection, 1530 MHD_HTTP_CONFLICT, 1531 TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE, 1532 NULL)); 1533 return; 1534 case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID: 1535 finish_loop (wc, 1536 TALER_MHD_reply_with_error ( 1537 wc->rc->connection, 1538 MHD_HTTP_FORBIDDEN, 1539 TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, 1540 NULL)); 1541 return; 1542 case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: { 1543 finish_loop ( 1544 wc, 1545 MHD_queue_response (wc->rc->connection, 1546 wc->error.details.legitimization_result.http_status, 1547 wc->error.details.legitimization_result.response)); 1548 return; 1549 } 1550 } 1551 GNUNET_break (0); 1552 finish_loop (wc, 1553 TALER_MHD_reply_with_error ( 1554 wc->rc->connection, 1555 MHD_HTTP_INTERNAL_SERVER_ERROR, 1556 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1557 "error phase without error")); 1558 } 1559 1560 1561 /** 1562 * Initializes the new context for the incoming withdraw request 1563 * 1564 * @param[in,out] wc withdraw request context 1565 * @param root json body of the request 1566 */ 1567 static void 1568 withdraw_phase_parse ( 1569 struct WithdrawContext *wc, 1570 const json_t *root) 1571 { 1572 const json_t *j_denoms_h; 1573 const json_t *j_coin_evs; 1574 const char *cipher; 1575 bool no_max_age; 1576 struct GNUNET_JSON_Specification spec[] = { 1577 GNUNET_JSON_spec_string ("cipher", 1578 &cipher), 1579 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 1580 &wc->request.withdraw.reserve_pub), 1581 GNUNET_JSON_spec_array_const ("denoms_h", 1582 &j_denoms_h), 1583 GNUNET_JSON_spec_array_const ("coin_evs", 1584 &j_coin_evs), 1585 GNUNET_JSON_spec_mark_optional ( 1586 GNUNET_JSON_spec_uint16 ("max_age", 1587 &wc->request.withdraw.max_age), 1588 &no_max_age), 1589 GNUNET_JSON_spec_mark_optional ( 1590 GNUNET_JSON_spec_fixed_auto ("blinding_seed", 1591 &wc->request.withdraw.blinding_seed), 1592 &wc->request.withdraw.no_blinding_seed), 1593 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 1594 &wc->request.withdraw.reserve_sig), 1595 GNUNET_JSON_spec_end () 1596 }; 1597 enum GNUNET_GenericReturnValue res; 1598 1599 res = TALER_MHD_parse_json_data (wc->rc->connection, 1600 root, 1601 spec); 1602 if (GNUNET_YES != res) 1603 { 1604 GNUNET_break_op (0); 1605 wc->phase = (GNUNET_SYSERR == res) 1606 ? WITHDRAW_PHASE_RETURN_NO 1607 : WITHDRAW_PHASE_RETURN_YES; 1608 return; 1609 } 1610 1611 /* For now, we only support cipher "ED25519" for signatures by the reserve */ 1612 if (0 != strcmp ("ED25519", 1613 cipher)) 1614 { 1615 GNUNET_break_op (0); 1616 SET_ERROR_WITH_DETAIL (wc, 1617 WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN, 1618 reserve_cipher_unknown, 1619 cipher); 1620 return; 1621 } 1622 1623 wc->request.withdraw.age_proof_required = ! no_max_age; 1624 1625 if (wc->request.withdraw.age_proof_required) 1626 { 1627 /* The age value MUST be on the beginning of an age group */ 1628 if (wc->request.withdraw.max_age != 1629 TALER_get_lowest_age (&TEH_age_restriction_mask, 1630 wc->request.withdraw.max_age)) 1631 { 1632 GNUNET_break_op (0); 1633 SET_ERROR_WITH_DETAIL ( 1634 wc, 1635 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1636 request_parameter_malformed, 1637 "max_age must be the lower edge of an age group"); 1638 return; 1639 } 1640 } 1641 1642 /* validate array size */ 1643 { 1644 size_t num_coins = json_array_size (j_denoms_h); 1645 size_t array_size = json_array_size (j_coin_evs); 1646 const char *error; 1647 1648 GNUNET_static_assert ( 1649 TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA); 1650 1651 #define BAIL_IF(cond, msg) \ 1652 if ((cond)) { \ 1653 GNUNET_break_op (0); \ 1654 error = (msg); break; \ 1655 } 1656 1657 do { 1658 BAIL_IF (0 == num_coins, 1659 "denoms_h must not be empty") 1660 1661 /** 1662 * The wallet had committed to more than the maximum coins allowed, the 1663 * reserve has been charged, but now the user can not withdraw any money 1664 * from it. Note that the user can't get their money back in this case! 1665 */ 1666 BAIL_IF (num_coins > TALER_MAX_COINS, 1667 "maximum number of coins that can be withdrawn has been exceeded") 1668 1669 BAIL_IF ((! wc->request.withdraw.age_proof_required) && 1670 (num_coins != array_size), 1671 "denoms_h and coin_evs must be arrays of the same size") 1672 1673 BAIL_IF (wc->request.withdraw.age_proof_required && 1674 ((TALER_CNC_KAPPA * num_coins) != array_size), 1675 "coin_evs must be an array of length " 1676 TALER_CNC_KAPPA_STR 1677 "*len(denoms_h)") 1678 1679 wc->request.withdraw.num_coins = num_coins; 1680 wc->request.num_planchets = array_size; 1681 error = NULL; 1682 1683 } while (0); 1684 #undef BAIL_IF 1685 1686 if (NULL != error) 1687 { 1688 SET_ERROR_WITH_DETAIL (wc, 1689 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1690 request_parameter_malformed, 1691 error); 1692 return; 1693 } 1694 } 1695 /* extract the denomination hashes */ 1696 { 1697 size_t idx; 1698 json_t *value; 1699 1700 wc->request.denoms_h 1701 = GNUNET_new_array (wc->request.withdraw.num_coins, 1702 struct TALER_DenominationHashP); 1703 1704 json_array_foreach (j_denoms_h, idx, value) { 1705 struct GNUNET_JSON_Specification ispec[] = { 1706 GNUNET_JSON_spec_fixed_auto (NULL, 1707 &wc->request.denoms_h[idx]), 1708 GNUNET_JSON_spec_end () 1709 }; 1710 1711 res = TALER_MHD_parse_json_data (wc->rc->connection, 1712 value, 1713 ispec); 1714 if (GNUNET_YES != res) 1715 { 1716 GNUNET_break_op (0); 1717 wc->phase = (GNUNET_SYSERR == res) 1718 ? WITHDRAW_PHASE_RETURN_NO 1719 : WITHDRAW_PHASE_RETURN_YES; 1720 return; 1721 } 1722 } 1723 } 1724 /* Parse the blinded coin envelopes */ 1725 { 1726 json_t *j_cev; 1727 size_t idx; 1728 1729 wc->request.planchets = 1730 GNUNET_new_array (wc->request.num_planchets, 1731 struct TALER_BlindedPlanchet); 1732 json_array_foreach (j_coin_evs, idx, j_cev) 1733 { 1734 /* Now parse the individual envelopes and calculate the hash of 1735 * the commitment along the way. */ 1736 struct GNUNET_JSON_Specification kspec[] = { 1737 TALER_JSON_spec_blinded_planchet (NULL, 1738 &wc->request.planchets[idx]), 1739 GNUNET_JSON_spec_end () 1740 }; 1741 1742 res = TALER_MHD_parse_json_data (wc->rc->connection, 1743 j_cev, 1744 kspec); 1745 if (GNUNET_OK != res) 1746 { 1747 GNUNET_break_op (0); 1748 wc->phase = (GNUNET_SYSERR == res) 1749 ? WITHDRAW_PHASE_RETURN_NO 1750 : WITHDRAW_PHASE_RETURN_YES; 1751 return; 1752 } 1753 1754 /* Check for duplicate planchets. Technically a bug on 1755 * the client side that is harmless for us, but still 1756 * not allowed per protocol */ 1757 for (size_t i = 0; i < idx; i++) 1758 { 1759 if (0 == 1760 TALER_blinded_planchet_cmp ( 1761 &wc->request.planchets[idx], 1762 &wc->request.planchets[i])) 1763 { 1764 GNUNET_break_op (0); 1765 SET_ERROR (wc, 1766 WITHDRAW_ERROR_IDEMPOTENT_PLANCHET); 1767 return; 1768 } 1769 } /* end duplicate check */ 1770 } /* json_array_foreach over j_coin_evs */ 1771 } /* scope of j_kappa_planchets, idx */ 1772 wc->phase = WITHDRAW_PHASE_CHECK_KEYS; 1773 } 1774 1775 1776 enum MHD_Result 1777 TEH_handler_withdraw ( 1778 struct TEH_RequestContext *rc, 1779 const json_t *root, 1780 const char *const args[0]) 1781 { 1782 struct WithdrawContext *wc = rc->rh_ctx; 1783 1784 (void) args; 1785 if (NULL == wc) 1786 { 1787 wc = GNUNET_new (struct WithdrawContext); 1788 rc->rh_ctx = wc; 1789 rc->rh_cleaner = &clean_withdraw_rc; 1790 wc->rc = rc; 1791 wc->now = GNUNET_TIME_timestamp_get (); 1792 } 1793 while (true) 1794 { 1795 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1796 "withdrawal%s processing in phase %d\n", 1797 wc->request.withdraw.age_proof_required 1798 ? " (with required age proof)" 1799 : "", 1800 wc->phase); 1801 switch (wc->phase) 1802 { 1803 case WITHDRAW_PHASE_PARSE: 1804 withdraw_phase_parse (wc, 1805 root); 1806 break; 1807 case WITHDRAW_PHASE_CHECK_KEYS: 1808 phase_check_keys (wc); 1809 break; 1810 case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE: 1811 phase_check_reserve_signature (wc); 1812 break; 1813 case WITHDRAW_PHASE_RUN_LEGI_CHECK: 1814 phase_run_legi_check (wc); 1815 break; 1816 case WITHDRAW_PHASE_SUSPENDED: 1817 return MHD_YES; 1818 case WITHDRAW_PHASE_CHECK_KYC_RESULT: 1819 phase_check_kyc_result (wc); 1820 break; 1821 case WITHDRAW_PHASE_PREPARE_TRANSACTION: 1822 phase_prepare_transaction (wc); 1823 break; 1824 case WITHDRAW_PHASE_RUN_TRANSACTION: 1825 phase_run_transaction (wc); 1826 break; 1827 case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS: 1828 phase_generate_reply_success (wc); 1829 break; 1830 case WITHDRAW_PHASE_GENERATE_REPLY_ERROR: 1831 phase_generate_reply_error (wc); 1832 break; 1833 case WITHDRAW_PHASE_RETURN_YES: 1834 return MHD_YES; 1835 case WITHDRAW_PHASE_RETURN_NO: 1836 return MHD_NO; 1837 } 1838 } 1839 }