taler-exchange-httpd_post-withdraw.c (55465B)
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 for (unsigned int i = 0; i<wc->request.num_planchets; i++) 1201 TALER_blinded_planchet_free (&wc->request.planchets[i]); 1202 GNUNET_free (wc->request.planchets); 1203 free_db_withdraw_data (&wc->request.withdraw); 1204 GNUNET_free (wc->request.cs_indices); 1205 if (wc->request.is_idempotent) 1206 free_db_withdraw_data (&wc->request.withdraw_idem); 1207 if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) && 1208 (NULL != wc->error.details.legitimization_result.response) ) 1209 { 1210 MHD_destroy_response (wc->error.details.legitimization_result.response); 1211 wc->error.details.legitimization_result.response = NULL; 1212 } 1213 GNUNET_free (wc); 1214 } 1215 1216 1217 /** 1218 * Generates response for the withdraw request. 1219 * 1220 * @param wc withdraw operation context 1221 */ 1222 static void 1223 phase_generate_reply_success (struct WithdrawContext *wc) 1224 { 1225 struct TALER_EXCHANGEDB_Withdraw *db_obj; 1226 1227 db_obj = wc->request.is_idempotent 1228 ? &wc->request.withdraw_idem 1229 : &wc->request.withdraw; 1230 1231 if (wc->request.withdraw.age_proof_required) 1232 { 1233 struct TALER_ExchangePublicKeyP pub; 1234 struct TALER_ExchangeSignatureP sig; 1235 enum TALER_ErrorCode ec_confirmation_sign; 1236 1237 ec_confirmation_sign = 1238 TALER_exchange_online_withdraw_age_confirmation_sign ( 1239 &TEH_keys_exchange_sign_, 1240 &db_obj->planchets_h, 1241 db_obj->noreveal_index, 1242 &pub, 1243 &sig); 1244 if (TALER_EC_NONE != ec_confirmation_sign) 1245 { 1246 SET_ERROR_WITH_FIELD (wc, 1247 WITHDRAW_ERROR_CONFIRMATION_SIGN, 1248 ec_confirmation_sign); 1249 return; 1250 } 1251 1252 finish_loop (wc, 1253 TALER_MHD_REPLY_JSON_PACK ( 1254 wc->rc->connection, 1255 MHD_HTTP_CREATED, 1256 GNUNET_JSON_pack_uint64 ("noreveal_index", 1257 db_obj->noreveal_index), 1258 GNUNET_JSON_pack_data_auto ("exchange_sig", 1259 &sig), 1260 GNUNET_JSON_pack_data_auto ("exchange_pub", 1261 &pub))); 1262 } 1263 else /* not age restricted */ 1264 { 1265 json_t *sigs; 1266 1267 sigs = json_array (); 1268 GNUNET_assert (NULL != sigs); 1269 for (unsigned int i = 0; i<db_obj->num_coins; i++) 1270 { 1271 GNUNET_assert ( 1272 0 == 1273 json_array_append_new ( 1274 sigs, 1275 GNUNET_JSON_PACK ( 1276 TALER_JSON_pack_blinded_denom_sig ( 1277 NULL, 1278 &db_obj->denom_sigs[i])))); 1279 } 1280 finish_loop (wc, 1281 TALER_MHD_REPLY_JSON_PACK ( 1282 wc->rc->connection, 1283 MHD_HTTP_OK, 1284 GNUNET_JSON_pack_array_steal ("ev_sigs", 1285 sigs))); 1286 } 1287 1288 TEH_METRICS_withdraw_num_coins += db_obj->num_coins; 1289 } 1290 1291 1292 /** 1293 * Reports an error, potentially with details. 1294 * That is, it puts a error-type specific response into the MHD queue. 1295 * It will do a idempotency check first, if needed for the error type. 1296 * 1297 * @param wc withdraw context 1298 */ 1299 static void 1300 phase_generate_reply_error ( 1301 struct WithdrawContext *wc) 1302 { 1303 GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase); 1304 if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) && 1305 withdraw_is_idempotent (wc)) 1306 { 1307 return; 1308 } 1309 1310 switch (wc->error.code) 1311 { 1312 case WITHDRAW_ERROR_NONE: 1313 break; 1314 case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED: 1315 finish_loop (wc, 1316 TALER_MHD_reply_with_error ( 1317 wc->rc->connection, 1318 MHD_HTTP_BAD_REQUEST, 1319 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1320 wc->error.details.request_parameter_malformed)); 1321 return; 1322 case WITHDRAW_ERROR_KEYS_MISSING: 1323 finish_loop (wc, 1324 TALER_MHD_reply_with_error ( 1325 wc->rc->connection, 1326 MHD_HTTP_SERVICE_UNAVAILABLE, 1327 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 1328 NULL)); 1329 return; 1330 case WITHDRAW_ERROR_DB_FETCH_FAILED: 1331 finish_loop (wc, 1332 TALER_MHD_reply_with_error ( 1333 wc->rc->connection, 1334 MHD_HTTP_INTERNAL_SERVER_ERROR, 1335 TALER_EC_GENERIC_DB_FETCH_FAILED, 1336 wc->error.details.db_fetch_context)); 1337 return; 1338 case WITHDRAW_ERROR_DB_INVARIANT_FAILURE: 1339 finish_loop (wc, 1340 TALER_MHD_reply_with_error ( 1341 wc->rc->connection, 1342 MHD_HTTP_INTERNAL_SERVER_ERROR, 1343 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 1344 NULL)); 1345 return; 1346 case WITHDRAW_ERROR_RESERVE_UNKNOWN: 1347 finish_loop (wc, 1348 TALER_MHD_reply_with_error ( 1349 wc->rc->connection, 1350 MHD_HTTP_NOT_FOUND, 1351 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 1352 NULL)); 1353 return; 1354 case WITHDRAW_ERROR_DENOMINATION_SIGN: 1355 finish_loop (wc, 1356 TALER_MHD_reply_with_ec ( 1357 wc->rc->connection, 1358 wc->error.details.ec_denomination_sign, 1359 NULL)); 1360 return; 1361 case WITHDRAW_ERROR_KYC_REQUIRED: 1362 finish_loop (wc, 1363 TEH_RESPONSE_reply_kyc_required ( 1364 wc->rc->connection, 1365 &wc->h_normalized_payto, 1366 &wc->kyc, 1367 false)); 1368 return; 1369 case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN: 1370 GNUNET_break_op (0); 1371 finish_loop (wc, 1372 TEH_RESPONSE_reply_unknown_denom_pub_hash ( 1373 wc->rc->connection, 1374 wc->error.details.denom_h)); 1375 return; 1376 case WITHDRAW_ERROR_DENOMINATION_EXPIRED: 1377 GNUNET_break_op (0); 1378 finish_loop (wc, 1379 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1380 wc->rc->connection, 1381 wc->error.details.denom_h, 1382 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, 1383 "WITHDRAW")); 1384 return; 1385 case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE: 1386 finish_loop (wc, 1387 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1388 wc->rc->connection, 1389 wc->error.details.denom_h, 1390 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, 1391 "WITHDRAW")); 1392 return; 1393 case WITHDRAW_ERROR_DENOMINATION_REVOKED: 1394 GNUNET_break_op (0); 1395 finish_loop (wc, 1396 TALER_MHD_reply_with_error ( 1397 wc->rc->connection, 1398 MHD_HTTP_GONE, 1399 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, 1400 NULL)); 1401 return; 1402 case WITHDRAW_ERROR_CIPHER_MISMATCH: 1403 finish_loop (wc, 1404 TALER_MHD_reply_with_error ( 1405 wc->rc->connection, 1406 MHD_HTTP_BAD_REQUEST, 1407 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1408 NULL)); 1409 return; 1410 case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED: 1411 finish_loop (wc, 1412 TALER_MHD_reply_with_error ( 1413 wc->rc->connection, 1414 MHD_HTTP_BAD_REQUEST, 1415 TALER_EC_GENERIC_PARAMETER_MISSING, 1416 "blinding_seed")); 1417 return; 1418 case WITHDRAW_ERROR_CRYPTO_HELPER: 1419 finish_loop (wc, 1420 TALER_MHD_reply_with_error ( 1421 wc->rc->connection, 1422 MHD_HTTP_INTERNAL_SERVER_ERROR, 1423 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1424 NULL)); 1425 return; 1426 case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN: 1427 finish_loop (wc, 1428 TALER_MHD_reply_with_error ( 1429 wc->rc->connection, 1430 MHD_HTTP_BAD_REQUEST, 1431 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1432 "cipher")); 1433 return; 1434 case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION: 1435 { 1436 char msg[256]; 1437 1438 GNUNET_snprintf (msg, 1439 sizeof(msg), 1440 "denomination %s does not support age restriction", 1441 GNUNET_h2s (&wc->error.details.denom_h->hash)); 1442 finish_loop (wc, 1443 TALER_MHD_reply_with_error ( 1444 wc->rc->connection, 1445 MHD_HTTP_NOT_FOUND, 1446 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, 1447 msg)); 1448 return; 1449 } 1450 case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE: 1451 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1452 "Generating JSON response with code %d\n", 1453 (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE); 1454 finish_loop (wc, 1455 TALER_MHD_REPLY_JSON_PACK ( 1456 wc->rc->connection, 1457 MHD_HTTP_CONFLICT, 1458 TALER_MHD_PACK_EC ( 1459 TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE), 1460 GNUNET_JSON_pack_uint64 ( 1461 "allowed_maximum_age", 1462 wc->error.details.maximum_age_too_large.max_allowed), 1463 GNUNET_JSON_pack_uint64 ( 1464 "reserve_birthday", 1465 wc->error.details.maximum_age_too_large.birthday))); 1466 return; 1467 case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED: 1468 finish_loop (wc, 1469 TEH_RESPONSE_reply_reserve_age_restriction_required ( 1470 wc->rc->connection, 1471 wc->error.details.age_restriction_required)); 1472 return; 1473 case WITHDRAW_ERROR_AMOUNT_OVERFLOW: 1474 finish_loop (wc, 1475 TALER_MHD_reply_with_error ( 1476 wc->rc->connection, 1477 MHD_HTTP_BAD_REQUEST, 1478 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1479 "amount")); 1480 return; 1481 case WITHDRAW_ERROR_FEE_OVERFLOW: 1482 finish_loop (wc, 1483 TALER_MHD_reply_with_error ( 1484 wc->rc->connection, 1485 MHD_HTTP_BAD_REQUEST, 1486 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1487 "fee")); 1488 return; 1489 case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW: 1490 finish_loop (wc, 1491 TALER_MHD_reply_with_error ( 1492 wc->rc->connection, 1493 MHD_HTTP_INTERNAL_SERVER_ERROR, 1494 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW, 1495 "amount+fee")); 1496 return; 1497 case WITHDRAW_ERROR_CONFIRMATION_SIGN: 1498 finish_loop (wc, 1499 TALER_MHD_reply_with_ec ( 1500 wc->rc->connection, 1501 wc->error.details.ec_confirmation_sign, 1502 NULL)); 1503 return; 1504 case WITHDRAW_ERROR_INSUFFICIENT_FUNDS: 1505 finish_loop (wc, 1506 TEH_RESPONSE_reply_reserve_insufficient_balance ( 1507 wc->rc->connection, 1508 TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, 1509 &wc->error.details.insufficient_funds, 1510 &wc->request.withdraw.amount_with_fee, 1511 &wc->request.withdraw.reserve_pub)); 1512 return; 1513 case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET: 1514 finish_loop (wc, 1515 TALER_MHD_reply_with_error ( 1516 wc->rc->connection, 1517 MHD_HTTP_BAD_REQUEST, 1518 TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET, 1519 NULL)); 1520 return; 1521 case WITHDRAW_ERROR_NONCE_REUSE: 1522 finish_loop (wc, 1523 TALER_MHD_reply_with_error ( 1524 wc->rc->connection, 1525 MHD_HTTP_CONFLICT, 1526 TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE, 1527 NULL)); 1528 return; 1529 case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID: 1530 finish_loop (wc, 1531 TALER_MHD_reply_with_error ( 1532 wc->rc->connection, 1533 MHD_HTTP_FORBIDDEN, 1534 TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, 1535 NULL)); 1536 return; 1537 case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: { 1538 finish_loop ( 1539 wc, 1540 MHD_queue_response (wc->rc->connection, 1541 wc->error.details.legitimization_result.http_status, 1542 wc->error.details.legitimization_result.response)); 1543 return; 1544 } 1545 } 1546 GNUNET_break (0); 1547 finish_loop (wc, 1548 TALER_MHD_reply_with_error ( 1549 wc->rc->connection, 1550 MHD_HTTP_INTERNAL_SERVER_ERROR, 1551 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1552 "error phase without error")); 1553 } 1554 1555 1556 /** 1557 * Initializes the new context for the incoming withdraw request 1558 * 1559 * @param[in,out] wc withdraw request context 1560 * @param root json body of the request 1561 */ 1562 static void 1563 withdraw_phase_parse ( 1564 struct WithdrawContext *wc, 1565 const json_t *root) 1566 { 1567 const json_t *j_denoms_h; 1568 const json_t *j_coin_evs; 1569 const char *cipher; 1570 bool no_max_age; 1571 struct GNUNET_JSON_Specification spec[] = { 1572 GNUNET_JSON_spec_string ("cipher", 1573 &cipher), 1574 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 1575 &wc->request.withdraw.reserve_pub), 1576 GNUNET_JSON_spec_array_const ("denoms_h", 1577 &j_denoms_h), 1578 GNUNET_JSON_spec_array_const ("coin_evs", 1579 &j_coin_evs), 1580 GNUNET_JSON_spec_mark_optional ( 1581 GNUNET_JSON_spec_uint16 ("max_age", 1582 &wc->request.withdraw.max_age), 1583 &no_max_age), 1584 GNUNET_JSON_spec_mark_optional ( 1585 GNUNET_JSON_spec_fixed_auto ("blinding_seed", 1586 &wc->request.withdraw.blinding_seed), 1587 &wc->request.withdraw.no_blinding_seed), 1588 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 1589 &wc->request.withdraw.reserve_sig), 1590 GNUNET_JSON_spec_end () 1591 }; 1592 enum GNUNET_GenericReturnValue res; 1593 1594 res = TALER_MHD_parse_json_data (wc->rc->connection, 1595 root, 1596 spec); 1597 if (GNUNET_YES != res) 1598 { 1599 GNUNET_break_op (0); 1600 wc->phase = (GNUNET_SYSERR == res) 1601 ? WITHDRAW_PHASE_RETURN_NO 1602 : WITHDRAW_PHASE_RETURN_YES; 1603 return; 1604 } 1605 1606 /* For now, we only support cipher "ED25519" for signatures by the reserve */ 1607 if (0 != strcmp ("ED25519", 1608 cipher)) 1609 { 1610 GNUNET_break_op (0); 1611 SET_ERROR_WITH_DETAIL (wc, 1612 WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN, 1613 reserve_cipher_unknown, 1614 cipher); 1615 return; 1616 } 1617 1618 wc->request.withdraw.age_proof_required = ! no_max_age; 1619 1620 if (wc->request.withdraw.age_proof_required) 1621 { 1622 /* The age value MUST be on the beginning of an age group */ 1623 if (wc->request.withdraw.max_age != 1624 TALER_get_lowest_age (&TEH_age_restriction_mask, 1625 wc->request.withdraw.max_age)) 1626 { 1627 GNUNET_break_op (0); 1628 SET_ERROR_WITH_DETAIL ( 1629 wc, 1630 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1631 request_parameter_malformed, 1632 "max_age must be the lower edge of an age group"); 1633 return; 1634 } 1635 } 1636 1637 /* validate array size */ 1638 { 1639 size_t num_coins = json_array_size (j_denoms_h); 1640 size_t array_size = json_array_size (j_coin_evs); 1641 const char *error; 1642 1643 GNUNET_static_assert ( 1644 TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA); 1645 1646 #define BAIL_IF(cond, msg) \ 1647 if ((cond)) { \ 1648 GNUNET_break_op (0); \ 1649 error = (msg); break; \ 1650 } 1651 1652 do { 1653 BAIL_IF (0 == num_coins, 1654 "denoms_h must not be empty") 1655 1656 /** 1657 * The wallet had committed to more than the maximum coins allowed, the 1658 * reserve has been charged, but now the user can not withdraw any money 1659 * from it. Note that the user can't get their money back in this case! 1660 */ 1661 BAIL_IF (num_coins > TALER_MAX_COINS, 1662 "maximum number of coins that can be withdrawn has been exceeded") 1663 1664 BAIL_IF ((! wc->request.withdraw.age_proof_required) && 1665 (num_coins != array_size), 1666 "denoms_h and coin_evs must be arrays of the same size") 1667 1668 BAIL_IF (wc->request.withdraw.age_proof_required && 1669 ((TALER_CNC_KAPPA * num_coins) != array_size), 1670 "coin_evs must be an array of length " 1671 TALER_CNC_KAPPA_STR 1672 "*len(denoms_h)") 1673 1674 wc->request.withdraw.num_coins = num_coins; 1675 wc->request.num_planchets = array_size; 1676 error = NULL; 1677 1678 } while (0); 1679 #undef BAIL_IF 1680 1681 if (NULL != error) 1682 { 1683 SET_ERROR_WITH_DETAIL (wc, 1684 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1685 request_parameter_malformed, 1686 error); 1687 return; 1688 } 1689 } 1690 /* extract the denomination hashes */ 1691 { 1692 size_t idx; 1693 json_t *value; 1694 1695 wc->request.denoms_h 1696 = GNUNET_new_array (wc->request.withdraw.num_coins, 1697 struct TALER_DenominationHashP); 1698 1699 json_array_foreach (j_denoms_h, idx, value) { 1700 struct GNUNET_JSON_Specification ispec[] = { 1701 GNUNET_JSON_spec_fixed_auto (NULL, 1702 &wc->request.denoms_h[idx]), 1703 GNUNET_JSON_spec_end () 1704 }; 1705 1706 res = TALER_MHD_parse_json_data (wc->rc->connection, 1707 value, 1708 ispec); 1709 if (GNUNET_YES != res) 1710 { 1711 GNUNET_break_op (0); 1712 wc->phase = (GNUNET_SYSERR == res) 1713 ? WITHDRAW_PHASE_RETURN_NO 1714 : WITHDRAW_PHASE_RETURN_YES; 1715 return; 1716 } 1717 } 1718 } 1719 /* Parse the blinded coin envelopes */ 1720 { 1721 json_t *j_cev; 1722 size_t idx; 1723 1724 wc->request.planchets = 1725 GNUNET_new_array (wc->request.num_planchets, 1726 struct TALER_BlindedPlanchet); 1727 json_array_foreach (j_coin_evs, idx, j_cev) 1728 { 1729 /* Now parse the individual envelopes and calculate the hash of 1730 * the commitment along the way. */ 1731 struct GNUNET_JSON_Specification kspec[] = { 1732 TALER_JSON_spec_blinded_planchet (NULL, 1733 &wc->request.planchets[idx]), 1734 GNUNET_JSON_spec_end () 1735 }; 1736 1737 res = TALER_MHD_parse_json_data (wc->rc->connection, 1738 j_cev, 1739 kspec); 1740 if (GNUNET_OK != res) 1741 { 1742 GNUNET_break_op (0); 1743 wc->phase = (GNUNET_SYSERR == res) 1744 ? WITHDRAW_PHASE_RETURN_NO 1745 : WITHDRAW_PHASE_RETURN_YES; 1746 return; 1747 } 1748 1749 /* Check for duplicate planchets. Technically a bug on 1750 * the client side that is harmless for us, but still 1751 * not allowed per protocol */ 1752 for (size_t i = 0; i < idx; i++) 1753 { 1754 if (0 == 1755 TALER_blinded_planchet_cmp ( 1756 &wc->request.planchets[idx], 1757 &wc->request.planchets[i])) 1758 { 1759 GNUNET_break_op (0); 1760 SET_ERROR (wc, 1761 WITHDRAW_ERROR_IDEMPOTENT_PLANCHET); 1762 return; 1763 } 1764 } /* end duplicate check */ 1765 } /* json_array_foreach over j_coin_evs */ 1766 } /* scope of j_kappa_planchets, idx */ 1767 wc->phase = WITHDRAW_PHASE_CHECK_KEYS; 1768 } 1769 1770 1771 enum MHD_Result 1772 TEH_handler_withdraw ( 1773 struct TEH_RequestContext *rc, 1774 const json_t *root, 1775 const char *const args[0]) 1776 { 1777 struct WithdrawContext *wc = rc->rh_ctx; 1778 1779 (void) args; 1780 if (NULL == wc) 1781 { 1782 wc = GNUNET_new (struct WithdrawContext); 1783 rc->rh_ctx = wc; 1784 rc->rh_cleaner = &clean_withdraw_rc; 1785 wc->rc = rc; 1786 wc->now = GNUNET_TIME_timestamp_get (); 1787 } 1788 while (true) 1789 { 1790 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1791 "withdrawal%s processing in phase %d\n", 1792 wc->request.withdraw.age_proof_required 1793 ? " (with required age proof)" 1794 : "", 1795 wc->phase); 1796 switch (wc->phase) 1797 { 1798 case WITHDRAW_PHASE_PARSE: 1799 withdraw_phase_parse (wc, 1800 root); 1801 break; 1802 case WITHDRAW_PHASE_CHECK_KEYS: 1803 phase_check_keys (wc); 1804 break; 1805 case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE: 1806 phase_check_reserve_signature (wc); 1807 break; 1808 case WITHDRAW_PHASE_RUN_LEGI_CHECK: 1809 phase_run_legi_check (wc); 1810 break; 1811 case WITHDRAW_PHASE_SUSPENDED: 1812 return MHD_YES; 1813 case WITHDRAW_PHASE_CHECK_KYC_RESULT: 1814 phase_check_kyc_result (wc); 1815 break; 1816 case WITHDRAW_PHASE_PREPARE_TRANSACTION: 1817 phase_prepare_transaction (wc); 1818 break; 1819 case WITHDRAW_PHASE_RUN_TRANSACTION: 1820 phase_run_transaction (wc); 1821 break; 1822 case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS: 1823 phase_generate_reply_success (wc); 1824 break; 1825 case WITHDRAW_PHASE_GENERATE_REPLY_ERROR: 1826 phase_generate_reply_error (wc); 1827 break; 1828 case WITHDRAW_PHASE_RETURN_YES: 1829 return MHD_YES; 1830 case WITHDRAW_PHASE_RETURN_NO: 1831 return MHD_NO; 1832 } 1833 } 1834 }