taler-exchange-httpd_post-withdraw.c (55526B)
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 ( 575 GNUNET_CRYPTO_QUALITY_STRONG, 576 TALER_CNC_KAPPA); 577 /** 578 * In case of age restriction, we use the corresponding offset in the planchet 579 * array to the beginning of the coins corresponding to the noreveal_index. 580 */ 581 offset = wc->request.withdraw.noreveal_index 582 * wc->request.withdraw.num_coins; 583 GNUNET_assert (offset + wc->request.withdraw.num_coins <= 584 wc->request.num_planchets); 585 } 586 587 /* Choose and sign the coins */ 588 { 589 struct TEH_SECMOD_CoinSignData csds[wc->request.withdraw.num_coins]; 590 enum TALER_ErrorCode ec_denomination_sign; 591 592 memset (csds, 593 0, 594 sizeof(csds)); 595 596 /* Pick the chosen blinded coins */ 597 for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++) 598 { 599 csds[i].bp = &wc->request.planchets[i + offset]; 600 csds[i].h_denom_pub = &wc->request.denoms_h[i]; 601 } 602 603 ec_denomination_sign = TEH_SECMOD_denom_batch_sign ( 604 wc->request.withdraw.num_coins, 605 csds, 606 false, 607 wc->request.withdraw.denom_sigs); 608 if (TALER_EC_NONE != ec_denomination_sign) 609 { 610 GNUNET_break (0); 611 SET_ERROR_WITH_FIELD (wc, 612 WITHDRAW_ERROR_DENOMINATION_SIGN, 613 ec_denomination_sign); 614 return; 615 } 616 617 /* Save the hash value of the selected batch of coins */ 618 wc->request.withdraw.selected_h = 619 wc->request.kappa_planchets_h[wc->request.withdraw.noreveal_index]; 620 } 621 622 /** 623 * For the denominations with cipher CS, calculate the R-values 624 * and save the choices we made now, as at a later point, the 625 * private keys for the denominations might now be available anymore 626 * to make the same choice again. 627 */ 628 if (0 < wc->request.withdraw.num_cs_r_values) 629 { 630 size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values; 631 struct TEH_SECMOD_CsDeriveData cdds[num_cs_r_values]; 632 struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values]; 633 634 memset (nonces, 635 0, 636 sizeof(nonces)); 637 wc->request.withdraw.cs_r_values 638 = GNUNET_new_array ( 639 num_cs_r_values, 640 struct GNUNET_CRYPTO_CSPublicRPairP); 641 wc->request.withdraw.cs_r_choices = 0; 642 643 GNUNET_assert (! wc->request.withdraw.no_blinding_seed); 644 TALER_cs_derive_nonces_from_seed ( 645 &wc->request.withdraw.blinding_seed, 646 false, /* not for melt */ 647 num_cs_r_values, 648 wc->request.cs_indices, 649 nonces); 650 651 for (size_t i = 0; i < num_cs_r_values; i++) 652 { 653 size_t idx = wc->request.cs_indices[i]; 654 655 GNUNET_assert (idx < wc->request.withdraw.num_coins); 656 cdds[i].h_denom_pub = &wc->request.denoms_h[idx]; 657 cdds[i].nonce = &nonces[i]; 658 } 659 660 /** 661 * Let the crypto helper generate the R-values and make the choices. 662 */ 663 if (TALER_EC_NONE != 664 TEH_SECMOD_denom_cs_batch_r_pub_simple ( 665 wc->request.withdraw.num_cs_r_values, 666 cdds, 667 false, 668 wc->request.withdraw.cs_r_values)) 669 { 670 GNUNET_break (0); 671 SET_ERROR (wc, 672 WITHDRAW_ERROR_CRYPTO_HELPER); 673 return; 674 } 675 676 /* This invariant should hold because 677 num_coins <= TALER_MAX_COINS. Still good 678 to check explicitly. */ 679 GNUNET_assert (num_cs_r_values <= 64); 680 /* Now save the choices for the selected bits */ 681 for (size_t i = 0; i < num_cs_r_values; i++) 682 { 683 size_t idx = wc->request.cs_indices[i]; 684 685 struct TALER_BlindedDenominationSignature *sig = 686 &wc->request.withdraw.denom_sigs[idx]; 687 uint64_t bit = sig->blinded_sig->details.blinded_cs_answer.b; 688 689 GNUNET_assert (bit <= 1); /* well, should actually be 0 or 1 */ 690 wc->request.withdraw.cs_r_choices |= bit << i; 691 GNUNET_static_assert ( 692 TALER_MAX_COINS <= 693 sizeof(wc->request.withdraw.cs_r_choices) * 8); 694 } 695 } 696 wc->phase++; 697 } 698 699 700 /** 701 * Check the KYC result. 702 * 703 * @param wc context for request processing 704 */ 705 static void 706 phase_check_kyc_result (struct WithdrawContext *wc) 707 { 708 /* return final positive response */ 709 if (! wc->kyc.ok) 710 { 711 SET_ERROR (wc, 712 WITHDRAW_ERROR_KYC_REQUIRED); 713 return; 714 } 715 wc->phase++; 716 } 717 718 719 /** 720 * Function called with the result of a legitimization 721 * check. 722 * 723 * @param cls closure 724 * @param lcr legitimization check result 725 */ 726 static void 727 withdraw_legi_cb ( 728 void *cls, 729 const struct TEH_LegitimizationCheckResult *lcr) 730 { 731 struct WithdrawContext *wc = cls; 732 733 wc->lch = NULL; 734 GNUNET_assert (WITHDRAW_PHASE_SUSPENDED == 735 wc->phase); 736 MHD_resume_connection (wc->rc->connection); 737 GNUNET_CONTAINER_DLL_remove (wc_head, 738 wc_tail, 739 wc); 740 TALER_MHD_daemon_trigger (); 741 if (NULL != lcr->response) 742 { 743 wc->error.details.legitimization_result.response = lcr->response; 744 wc->error.details.legitimization_result.http_status = lcr->http_status; 745 SET_ERROR (wc, 746 WITHDRAW_ERROR_LEGITIMIZATION_RESULT); 747 return; 748 } 749 wc->kyc = lcr->kyc; 750 wc->phase = WITHDRAW_PHASE_CHECK_KYC_RESULT; 751 } 752 753 754 /** 755 * Function called to iterate over KYC-relevant transaction amounts for a 756 * particular time range. Called within a database transaction, so must 757 * not start a new one. 758 * 759 * @param cls closure, identifies the event type and account to iterate 760 * over events for 761 * @param limit maximum time-range for which events should be fetched 762 * (timestamp in the past) 763 * @param cb function to call on each event found, events must be returned 764 * in reverse chronological order 765 * @param cb_cls closure for @a cb, of type struct WithdrawContext 766 * @return transaction status 767 */ 768 static enum GNUNET_DB_QueryStatus 769 withdraw_amount_cb ( 770 void *cls, 771 struct GNUNET_TIME_Absolute limit, 772 TALER_KYCLOGIC_KycAmountCallback cb, 773 void *cb_cls) 774 { 775 struct WithdrawContext *wc = cls; 776 enum GNUNET_GenericReturnValue ret; 777 enum GNUNET_DB_QueryStatus qs; 778 779 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 780 "Signaling amount %s for KYC check during witdrawal\n", 781 TALER_amount2s (&wc->request.withdraw.amount_with_fee)); 782 783 ret = cb (cb_cls, 784 &wc->request.withdraw.amount_with_fee, 785 wc->now.abs_time); 786 GNUNET_break (GNUNET_SYSERR != ret); 787 if (GNUNET_OK != ret) 788 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 789 790 qs = TALER_EXCHANGEDB_select_withdraw_amounts_for_kyc_check ( 791 TEH_pg, 792 &wc->h_normalized_payto, 793 limit, 794 cb, 795 cb_cls); 796 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 797 "Got %d additional transactions for this withdrawal and limit %llu\n", 798 qs, 799 (unsigned long long) limit.abs_value_us); 800 GNUNET_break (qs >= 0); 801 return qs; 802 } 803 804 805 /** 806 * Do legitimization check. 807 * 808 * @param wc operation context 809 */ 810 static void 811 phase_run_legi_check (struct WithdrawContext *wc) 812 { 813 enum GNUNET_DB_QueryStatus qs; 814 struct TALER_FullPayto payto_uri; 815 struct TALER_FullPaytoHashP h_full_payto; 816 817 /* Check if the money came from a wire transfer */ 818 qs = TALER_TALER_EXCHANGEDB_reserves_get_origin ( 819 TEH_pg, 820 &wc->request.withdraw.reserve_pub, 821 &h_full_payto, 822 &payto_uri); 823 if (qs < 0) 824 { 825 SET_ERROR_WITH_DETAIL (wc, 826 WITHDRAW_ERROR_DB_FETCH_FAILED, 827 db_fetch_context, 828 "reserves_get_origin"); 829 return; 830 } 831 /* If _no_ results, reserve was created by merge, 832 in which case no KYC check is required as the 833 merge already did that. */ 834 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 835 { 836 wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION; 837 return; 838 } 839 TALER_full_payto_normalize_and_hash (payto_uri, 840 &wc->h_normalized_payto); 841 wc->lch = TEH_legitimization_check ( 842 &wc->rc->async_scope_id, 843 TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, 844 payto_uri, 845 &wc->h_normalized_payto, 846 NULL, /* no account pub: this is about the origin account */ 847 &withdraw_amount_cb, 848 wc, 849 &withdraw_legi_cb, 850 wc); 851 GNUNET_assert (NULL != wc->lch); 852 GNUNET_free (payto_uri.full_payto); 853 GNUNET_CONTAINER_DLL_insert (wc_head, 854 wc_tail, 855 wc); 856 MHD_suspend_connection (wc->rc->connection); 857 wc->phase = WITHDRAW_PHASE_SUSPENDED; 858 } 859 860 861 /** 862 * Check if the given denomination is still or already valid, has not been 863 * revoked and potentically supports age restriction. 864 * 865 * @param[in,out] wc context for the withdraw operation 866 * @param ksh The handle to the current state of (denomination) keys in the exchange 867 * @param denom_h Hash of the denomination key to check 868 * @param[out] pdk denomination key found, might be NULL 869 * @return true when denomation was found and valid, 870 * false when denomination was not valid and the state machine was advanced 871 */ 872 static enum GNUNET_GenericReturnValue 873 find_denomination ( 874 struct WithdrawContext *wc, 875 struct TEH_KeyStateHandle *ksh, 876 const struct TALER_DenominationHashP *denom_h, 877 struct TEH_DenominationKey **pdk) 878 { 879 struct TEH_DenominationKey *dk; 880 881 *pdk = NULL; 882 dk = TEH_keys_denomination_by_hash_from_state ( 883 ksh, 884 denom_h, 885 NULL, 886 NULL); 887 if (NULL == dk) 888 { 889 SET_ERROR_WITH_FIELD (wc, 890 WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN, 891 denom_h); 892 return false; 893 } 894 if (GNUNET_TIME_absolute_is_past ( 895 dk->meta.expire_withdraw.abs_time)) 896 { 897 SET_ERROR_WITH_FIELD (wc, 898 WITHDRAW_ERROR_DENOMINATION_EXPIRED, 899 denom_h); 900 return false; 901 } 902 if (GNUNET_TIME_absolute_is_future ( 903 dk->meta.start.abs_time)) 904 { 905 GNUNET_break_op (0); 906 SET_ERROR_WITH_FIELD (wc, 907 WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE, 908 denom_h); 909 return false; 910 } 911 if (dk->recoup_possible) 912 { 913 SET_ERROR (wc, 914 WITHDRAW_ERROR_DENOMINATION_REVOKED); 915 return false; 916 } 917 /* In case of age withdraw, make sure that the denomination supports age restriction */ 918 if (wc->request.withdraw.age_proof_required) 919 { 920 if (0 == dk->denom_pub.age_mask.bits) 921 { 922 GNUNET_break_op (0); 923 SET_ERROR_WITH_FIELD (wc, 924 WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, 925 denom_h); 926 return false; 927 } 928 } 929 *pdk = dk; 930 return true; 931 } 932 933 934 /** 935 * Check if the given array of hashes of denomination_keys 936 * a) belong to valid denominations 937 * b) those are marked as age restricted, if the request is age restricted 938 * c) calculate the total amount of the denominations including fees 939 * for withdraw. 940 * 941 * @param wc context of the age withdrawal to check keys for 942 */ 943 static void 944 phase_check_keys ( 945 struct WithdrawContext *wc) 946 { 947 struct TEH_KeyStateHandle *ksh; 948 bool is_cs_denom[wc->request.withdraw.num_coins]; 949 950 memset (is_cs_denom, 951 0, 952 sizeof(is_cs_denom)); 953 ksh = TEH_keys_get_state (); 954 if (NULL == ksh) 955 { 956 GNUNET_break (0); 957 SET_ERROR (wc, 958 WITHDRAW_ERROR_KEYS_MISSING); 959 return; 960 } 961 wc->request.withdraw.denom_serials = 962 GNUNET_new_array (wc->request.withdraw.num_coins, 963 uint64_t); 964 GNUNET_assert (GNUNET_OK == 965 TALER_amount_set_zero (TEH_currency, 966 &wc->request.amount)); 967 GNUNET_assert (GNUNET_OK == 968 TALER_amount_set_zero (TEH_currency, 969 &wc->request.fee)); 970 GNUNET_assert (GNUNET_OK == 971 TALER_amount_set_zero (TEH_currency, 972 &wc->request.withdraw.amount_with_fee)); 973 974 for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++) 975 { 976 struct TEH_DenominationKey *dk; 977 978 if (! find_denomination (wc, 979 ksh, 980 &wc->request.denoms_h[i], 981 &dk)) 982 return; 983 switch (dk->denom_pub.bsign_pub_key->cipher) 984 { 985 case GNUNET_CRYPTO_BSA_INVALID: 986 /* This should never happen (memory corruption?) */ 987 GNUNET_assert (0); 988 case GNUNET_CRYPTO_BSA_RSA: 989 /* nothing to do here */ 990 break; 991 case GNUNET_CRYPTO_BSA_CS: 992 if (wc->request.withdraw.no_blinding_seed) 993 { 994 GNUNET_break_op (0); 995 SET_ERROR (wc, 996 WITHDRAW_ERROR_BLINDING_SEED_REQUIRED); 997 return; 998 } 999 wc->request.withdraw.num_cs_r_values++; 1000 is_cs_denom[i] = true; 1001 break; 1002 } 1003 1004 /* Ensure the ciphers from the planchets match the denominations'. */ 1005 if (wc->request.withdraw.age_proof_required) 1006 { 1007 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 1008 { 1009 size_t off = k * wc->request.withdraw.num_coins; 1010 1011 if (dk->denom_pub.bsign_pub_key->cipher != 1012 wc->request.planchets[i + off].blinded_message->cipher) 1013 { 1014 GNUNET_break_op (0); 1015 SET_ERROR (wc, 1016 WITHDRAW_ERROR_CIPHER_MISMATCH); 1017 return; 1018 } 1019 } 1020 } 1021 else 1022 { 1023 if (dk->denom_pub.bsign_pub_key->cipher != 1024 wc->request.planchets[i].blinded_message->cipher) 1025 { 1026 GNUNET_break_op (0); 1027 SET_ERROR (wc, 1028 WITHDRAW_ERROR_CIPHER_MISMATCH); 1029 return; 1030 } 1031 } 1032 1033 /* Accumulate the values */ 1034 if (0 > TALER_amount_add (&wc->request.amount, 1035 &wc->request.amount, 1036 &dk->meta.value)) 1037 { 1038 GNUNET_break_op (0); 1039 SET_ERROR (wc, 1040 WITHDRAW_ERROR_AMOUNT_OVERFLOW); 1041 return; 1042 } 1043 1044 /* Accumulate the withdraw fees */ 1045 if (0 > TALER_amount_add (&wc->request.fee, 1046 &wc->request.fee, 1047 &dk->meta.fees.withdraw)) 1048 { 1049 GNUNET_break_op (0); 1050 SET_ERROR (wc, 1051 WITHDRAW_ERROR_FEE_OVERFLOW); 1052 return; 1053 } 1054 wc->request.withdraw.denom_serials[i] = dk->meta.serial; 1055 } 1056 1057 /* Save the hash of the batch of planchets */ 1058 if (! wc->request.withdraw.age_proof_required) 1059 { 1060 TALER_wallet_blinded_planchets_hash ( 1061 wc->request.withdraw.num_coins, 1062 wc->request.planchets, 1063 wc->request.denoms_h, 1064 &wc->request.withdraw.planchets_h); 1065 } 1066 else 1067 { 1068 struct GNUNET_HashContext *ctx; 1069 1070 /** 1071 * The age-proof-required case is a bit more involved, 1072 * because we need to calculate and remember kappa hashes 1073 * for each batch of coins. 1074 */ 1075 ctx = GNUNET_CRYPTO_hash_context_start (); 1076 GNUNET_assert (NULL != ctx); 1077 1078 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 1079 { 1080 size_t off = k * wc->request.withdraw.num_coins; 1081 1082 TALER_wallet_blinded_planchets_hash ( 1083 wc->request.withdraw.num_coins, 1084 &wc->request.planchets[off], 1085 wc->request.denoms_h, 1086 &wc->request.kappa_planchets_h[k]); 1087 GNUNET_CRYPTO_hash_context_read ( 1088 ctx, 1089 &wc->request.kappa_planchets_h[k], 1090 sizeof(wc->request.kappa_planchets_h[k])); 1091 } 1092 GNUNET_CRYPTO_hash_context_finish ( 1093 ctx, 1094 &wc->request.withdraw.planchets_h.hash); 1095 } 1096 1097 /* Save the total amount including fees */ 1098 if (0 > TALER_amount_add ( 1099 &wc->request.withdraw.amount_with_fee, 1100 &wc->request.amount, 1101 &wc->request.fee)) 1102 { 1103 GNUNET_break_op (0); 1104 SET_ERROR (wc, 1105 WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW); 1106 return; 1107 } 1108 1109 /* Save the indices of CS denominations */ 1110 if (0 < wc->request.withdraw.num_cs_r_values) 1111 { 1112 size_t j = 0; 1113 1114 wc->request.cs_indices = GNUNET_new_array ( 1115 wc->request.withdraw.num_cs_r_values, 1116 uint32_t); 1117 1118 for (size_t i = 0; i < wc->request.withdraw.num_coins; i++) 1119 { 1120 if (is_cs_denom[i]) 1121 wc->request.cs_indices[j++] = i; 1122 } 1123 } 1124 1125 wc->phase++; 1126 } 1127 1128 1129 /** 1130 * Check that the client signature authorizing the withdrawal is valid. 1131 * 1132 * @param[in,out] wc request context to check 1133 */ 1134 static void 1135 phase_check_reserve_signature ( 1136 struct WithdrawContext *wc) 1137 { 1138 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 1139 if (GNUNET_OK != 1140 TALER_wallet_withdraw_verify ( 1141 &wc->request.amount, 1142 &wc->request.fee, 1143 &wc->request.withdraw.planchets_h, 1144 wc->request.withdraw.no_blinding_seed 1145 ? NULL 1146 : &wc->request.withdraw.blinding_seed, 1147 (wc->request.withdraw.age_proof_required) 1148 ? &TEH_age_restriction_config.mask 1149 : NULL, 1150 (wc->request.withdraw.age_proof_required) 1151 ? wc->request.withdraw.max_age 1152 : 0, 1153 &wc->request.withdraw.reserve_pub, 1154 &wc->request.withdraw.reserve_sig)) 1155 { 1156 GNUNET_break_op (0); 1157 SET_ERROR (wc, 1158 WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID); 1159 return; 1160 } 1161 wc->phase++; 1162 } 1163 1164 1165 /** 1166 * Free data inside of @a wd, but not @a wd itself. 1167 * 1168 * @param[in] wd withdraw data to free 1169 */ 1170 static void 1171 free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd) 1172 { 1173 if (NULL != wd->denom_sigs) 1174 { 1175 for (unsigned int i = 0; i<wd->num_coins; i++) 1176 TALER_blinded_denom_sig_free (&wd->denom_sigs[i]); 1177 GNUNET_free (wd->denom_sigs); 1178 } 1179 GNUNET_free (wd->denom_serials); 1180 GNUNET_free (wd->cs_r_values); 1181 } 1182 1183 1184 /** 1185 * Cleanup routine for withdraw request. 1186 * The function is called upon completion of the request 1187 * that should clean up @a rh_ctx. Can be NULL. 1188 * 1189 * @param rc request context to clean up 1190 */ 1191 static void 1192 clean_withdraw_rc (struct TEH_RequestContext *rc) 1193 { 1194 struct WithdrawContext *wc = rc->rh_ctx; 1195 1196 if (NULL != wc->lch) 1197 { 1198 TEH_legitimization_check_cancel (wc->lch); 1199 wc->lch = NULL; 1200 } 1201 GNUNET_free (wc->request.denoms_h); 1202 for (unsigned int i = 0; i<wc->request.num_planchets; i++) 1203 TALER_blinded_planchet_free (&wc->request.planchets[i]); 1204 GNUNET_free (wc->request.planchets); 1205 free_db_withdraw_data (&wc->request.withdraw); 1206 GNUNET_free (wc->request.cs_indices); 1207 if (wc->request.is_idempotent) 1208 free_db_withdraw_data (&wc->request.withdraw_idem); 1209 if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) && 1210 (NULL != wc->error.details.legitimization_result.response) ) 1211 { 1212 MHD_destroy_response (wc->error.details.legitimization_result.response); 1213 wc->error.details.legitimization_result.response = NULL; 1214 } 1215 GNUNET_free (wc); 1216 } 1217 1218 1219 /** 1220 * Generates response for the withdraw request. 1221 * 1222 * @param wc withdraw operation context 1223 */ 1224 static void 1225 phase_generate_reply_success (struct WithdrawContext *wc) 1226 { 1227 struct TALER_EXCHANGEDB_Withdraw *db_obj; 1228 1229 db_obj = wc->request.is_idempotent 1230 ? &wc->request.withdraw_idem 1231 : &wc->request.withdraw; 1232 1233 if (wc->request.withdraw.age_proof_required) 1234 { 1235 struct TALER_ExchangePublicKeyP pub; 1236 struct TALER_ExchangeSignatureP sig; 1237 enum TALER_ErrorCode ec_confirmation_sign; 1238 1239 ec_confirmation_sign = 1240 TALER_exchange_online_withdraw_age_confirmation_sign ( 1241 &TEH_keys_exchange_sign_, 1242 &db_obj->planchets_h, 1243 db_obj->noreveal_index, 1244 &pub, 1245 &sig); 1246 if (TALER_EC_NONE != ec_confirmation_sign) 1247 { 1248 SET_ERROR_WITH_FIELD (wc, 1249 WITHDRAW_ERROR_CONFIRMATION_SIGN, 1250 ec_confirmation_sign); 1251 return; 1252 } 1253 1254 finish_loop (wc, 1255 TALER_MHD_REPLY_JSON_PACK ( 1256 wc->rc->connection, 1257 MHD_HTTP_CREATED, 1258 GNUNET_JSON_pack_uint64 ("noreveal_index", 1259 db_obj->noreveal_index), 1260 GNUNET_JSON_pack_data_auto ("exchange_sig", 1261 &sig), 1262 GNUNET_JSON_pack_data_auto ("exchange_pub", 1263 &pub))); 1264 } 1265 else /* not age restricted */ 1266 { 1267 json_t *sigs; 1268 1269 sigs = json_array (); 1270 GNUNET_assert (NULL != sigs); 1271 for (unsigned int i = 0; i<db_obj->num_coins; i++) 1272 { 1273 GNUNET_assert ( 1274 0 == 1275 json_array_append_new ( 1276 sigs, 1277 GNUNET_JSON_PACK ( 1278 TALER_JSON_pack_blinded_denom_sig ( 1279 NULL, 1280 &db_obj->denom_sigs[i])))); 1281 } 1282 finish_loop (wc, 1283 TALER_MHD_REPLY_JSON_PACK ( 1284 wc->rc->connection, 1285 MHD_HTTP_OK, 1286 GNUNET_JSON_pack_array_steal ("ev_sigs", 1287 sigs))); 1288 } 1289 1290 TEH_METRICS_withdraw_num_coins += db_obj->num_coins; 1291 } 1292 1293 1294 /** 1295 * Reports an error, potentially with details. 1296 * That is, it puts a error-type specific response into the MHD queue. 1297 * It will do a idempotency check first, if needed for the error type. 1298 * 1299 * @param wc withdraw context 1300 */ 1301 static void 1302 phase_generate_reply_error ( 1303 struct WithdrawContext *wc) 1304 { 1305 GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase); 1306 if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) && 1307 withdraw_is_idempotent (wc)) 1308 { 1309 return; 1310 } 1311 1312 switch (wc->error.code) 1313 { 1314 case WITHDRAW_ERROR_NONE: 1315 break; 1316 case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED: 1317 finish_loop (wc, 1318 TALER_MHD_reply_with_error ( 1319 wc->rc->connection, 1320 MHD_HTTP_BAD_REQUEST, 1321 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1322 wc->error.details.request_parameter_malformed)); 1323 return; 1324 case WITHDRAW_ERROR_KEYS_MISSING: 1325 finish_loop (wc, 1326 TALER_MHD_reply_with_error ( 1327 wc->rc->connection, 1328 MHD_HTTP_SERVICE_UNAVAILABLE, 1329 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 1330 NULL)); 1331 return; 1332 case WITHDRAW_ERROR_DB_FETCH_FAILED: 1333 finish_loop (wc, 1334 TALER_MHD_reply_with_error ( 1335 wc->rc->connection, 1336 MHD_HTTP_INTERNAL_SERVER_ERROR, 1337 TALER_EC_GENERIC_DB_FETCH_FAILED, 1338 wc->error.details.db_fetch_context)); 1339 return; 1340 case WITHDRAW_ERROR_DB_INVARIANT_FAILURE: 1341 finish_loop (wc, 1342 TALER_MHD_reply_with_error ( 1343 wc->rc->connection, 1344 MHD_HTTP_INTERNAL_SERVER_ERROR, 1345 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 1346 NULL)); 1347 return; 1348 case WITHDRAW_ERROR_RESERVE_UNKNOWN: 1349 finish_loop (wc, 1350 TALER_MHD_reply_with_error ( 1351 wc->rc->connection, 1352 MHD_HTTP_NOT_FOUND, 1353 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 1354 NULL)); 1355 return; 1356 case WITHDRAW_ERROR_DENOMINATION_SIGN: 1357 finish_loop (wc, 1358 TALER_MHD_reply_with_ec ( 1359 wc->rc->connection, 1360 wc->error.details.ec_denomination_sign, 1361 NULL)); 1362 return; 1363 case WITHDRAW_ERROR_KYC_REQUIRED: 1364 finish_loop (wc, 1365 TEH_RESPONSE_reply_kyc_required ( 1366 wc->rc->connection, 1367 &wc->h_normalized_payto, 1368 &wc->kyc, 1369 false)); 1370 return; 1371 case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN: 1372 GNUNET_break_op (0); 1373 finish_loop (wc, 1374 TEH_RESPONSE_reply_unknown_denom_pub_hash ( 1375 wc->rc->connection, 1376 wc->error.details.denom_h)); 1377 return; 1378 case WITHDRAW_ERROR_DENOMINATION_EXPIRED: 1379 GNUNET_break_op (0); 1380 finish_loop (wc, 1381 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1382 wc->rc->connection, 1383 wc->error.details.denom_h, 1384 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, 1385 "WITHDRAW")); 1386 return; 1387 case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE: 1388 finish_loop (wc, 1389 TEH_RESPONSE_reply_expired_denom_pub_hash ( 1390 wc->rc->connection, 1391 wc->error.details.denom_h, 1392 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, 1393 "WITHDRAW")); 1394 return; 1395 case WITHDRAW_ERROR_DENOMINATION_REVOKED: 1396 GNUNET_break_op (0); 1397 finish_loop (wc, 1398 TALER_MHD_reply_with_error ( 1399 wc->rc->connection, 1400 MHD_HTTP_GONE, 1401 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, 1402 NULL)); 1403 return; 1404 case WITHDRAW_ERROR_CIPHER_MISMATCH: 1405 finish_loop (wc, 1406 TALER_MHD_reply_with_error ( 1407 wc->rc->connection, 1408 MHD_HTTP_BAD_REQUEST, 1409 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1410 NULL)); 1411 return; 1412 case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED: 1413 finish_loop (wc, 1414 TALER_MHD_reply_with_error ( 1415 wc->rc->connection, 1416 MHD_HTTP_BAD_REQUEST, 1417 TALER_EC_GENERIC_PARAMETER_MISSING, 1418 "blinding_seed")); 1419 return; 1420 case WITHDRAW_ERROR_CRYPTO_HELPER: 1421 finish_loop (wc, 1422 TALER_MHD_reply_with_error ( 1423 wc->rc->connection, 1424 MHD_HTTP_INTERNAL_SERVER_ERROR, 1425 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1426 NULL)); 1427 return; 1428 case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN: 1429 finish_loop (wc, 1430 TALER_MHD_reply_with_error ( 1431 wc->rc->connection, 1432 MHD_HTTP_BAD_REQUEST, 1433 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 1434 "cipher")); 1435 return; 1436 case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION: 1437 { 1438 char msg[256]; 1439 1440 GNUNET_snprintf (msg, 1441 sizeof(msg), 1442 "denomination %s does not support age restriction", 1443 GNUNET_h2s (&wc->error.details.denom_h->hash)); 1444 finish_loop (wc, 1445 TALER_MHD_reply_with_error ( 1446 wc->rc->connection, 1447 MHD_HTTP_NOT_FOUND, 1448 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, 1449 msg)); 1450 return; 1451 } 1452 case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE: 1453 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1454 "Generating JSON response with code %d\n", 1455 (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE); 1456 finish_loop (wc, 1457 TALER_MHD_REPLY_JSON_PACK ( 1458 wc->rc->connection, 1459 MHD_HTTP_CONFLICT, 1460 TALER_MHD_PACK_EC ( 1461 TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE), 1462 GNUNET_JSON_pack_uint64 ( 1463 "allowed_maximum_age", 1464 wc->error.details.maximum_age_too_large.max_allowed), 1465 GNUNET_JSON_pack_uint64 ( 1466 "reserve_birthday", 1467 wc->error.details.maximum_age_too_large.birthday))); 1468 return; 1469 case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED: 1470 finish_loop (wc, 1471 TEH_RESPONSE_reply_reserve_age_restriction_required ( 1472 wc->rc->connection, 1473 wc->error.details.age_restriction_required)); 1474 return; 1475 case WITHDRAW_ERROR_AMOUNT_OVERFLOW: 1476 finish_loop (wc, 1477 TALER_MHD_reply_with_error ( 1478 wc->rc->connection, 1479 MHD_HTTP_BAD_REQUEST, 1480 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1481 "amount")); 1482 return; 1483 case WITHDRAW_ERROR_FEE_OVERFLOW: 1484 finish_loop (wc, 1485 TALER_MHD_reply_with_error ( 1486 wc->rc->connection, 1487 MHD_HTTP_BAD_REQUEST, 1488 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW, 1489 "fee")); 1490 return; 1491 case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW: 1492 finish_loop (wc, 1493 TALER_MHD_reply_with_error ( 1494 wc->rc->connection, 1495 MHD_HTTP_INTERNAL_SERVER_ERROR, 1496 TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW, 1497 "amount+fee")); 1498 return; 1499 case WITHDRAW_ERROR_CONFIRMATION_SIGN: 1500 finish_loop (wc, 1501 TALER_MHD_reply_with_ec ( 1502 wc->rc->connection, 1503 wc->error.details.ec_confirmation_sign, 1504 NULL)); 1505 return; 1506 case WITHDRAW_ERROR_INSUFFICIENT_FUNDS: 1507 finish_loop (wc, 1508 TEH_RESPONSE_reply_reserve_insufficient_balance ( 1509 wc->rc->connection, 1510 TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, 1511 &wc->error.details.insufficient_funds, 1512 &wc->request.withdraw.amount_with_fee, 1513 &wc->request.withdraw.reserve_pub)); 1514 return; 1515 case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET: 1516 finish_loop (wc, 1517 TALER_MHD_reply_with_error ( 1518 wc->rc->connection, 1519 MHD_HTTP_BAD_REQUEST, 1520 TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET, 1521 NULL)); 1522 return; 1523 case WITHDRAW_ERROR_NONCE_REUSE: 1524 finish_loop (wc, 1525 TALER_MHD_reply_with_error ( 1526 wc->rc->connection, 1527 MHD_HTTP_CONFLICT, 1528 TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE, 1529 NULL)); 1530 return; 1531 case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID: 1532 finish_loop (wc, 1533 TALER_MHD_reply_with_error ( 1534 wc->rc->connection, 1535 MHD_HTTP_FORBIDDEN, 1536 TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, 1537 NULL)); 1538 return; 1539 case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: { 1540 finish_loop ( 1541 wc, 1542 MHD_queue_response (wc->rc->connection, 1543 wc->error.details.legitimization_result.http_status, 1544 wc->error.details.legitimization_result.response)); 1545 return; 1546 } 1547 } 1548 GNUNET_break (0); 1549 finish_loop (wc, 1550 TALER_MHD_reply_with_error ( 1551 wc->rc->connection, 1552 MHD_HTTP_INTERNAL_SERVER_ERROR, 1553 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1554 "error phase without error")); 1555 } 1556 1557 1558 /** 1559 * Initializes the new context for the incoming withdraw request 1560 * 1561 * @param[in,out] wc withdraw request context 1562 * @param root json body of the request 1563 */ 1564 static void 1565 withdraw_phase_parse ( 1566 struct WithdrawContext *wc, 1567 const json_t *root) 1568 { 1569 const json_t *j_denoms_h; 1570 const json_t *j_coin_evs; 1571 const char *cipher; 1572 bool no_max_age; 1573 struct GNUNET_JSON_Specification spec[] = { 1574 GNUNET_JSON_spec_string ("cipher", 1575 &cipher), 1576 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 1577 &wc->request.withdraw.reserve_pub), 1578 GNUNET_JSON_spec_array_const ("denoms_h", 1579 &j_denoms_h), 1580 GNUNET_JSON_spec_array_const ("coin_evs", 1581 &j_coin_evs), 1582 GNUNET_JSON_spec_mark_optional ( 1583 GNUNET_JSON_spec_uint16 ("max_age", 1584 &wc->request.withdraw.max_age), 1585 &no_max_age), 1586 GNUNET_JSON_spec_mark_optional ( 1587 GNUNET_JSON_spec_fixed_auto ("blinding_seed", 1588 &wc->request.withdraw.blinding_seed), 1589 &wc->request.withdraw.no_blinding_seed), 1590 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 1591 &wc->request.withdraw.reserve_sig), 1592 GNUNET_JSON_spec_end () 1593 }; 1594 enum GNUNET_GenericReturnValue res; 1595 1596 res = TALER_MHD_parse_json_data (wc->rc->connection, 1597 root, 1598 spec); 1599 if (GNUNET_YES != res) 1600 { 1601 GNUNET_break_op (0); 1602 wc->phase = (GNUNET_SYSERR == res) 1603 ? WITHDRAW_PHASE_RETURN_NO 1604 : WITHDRAW_PHASE_RETURN_YES; 1605 return; 1606 } 1607 1608 /* For now, we only support cipher "ED25519" for signatures by the reserve */ 1609 if (0 != strcmp ("ED25519", 1610 cipher)) 1611 { 1612 GNUNET_break_op (0); 1613 SET_ERROR_WITH_DETAIL (wc, 1614 WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN, 1615 reserve_cipher_unknown, 1616 cipher); 1617 return; 1618 } 1619 1620 wc->request.withdraw.age_proof_required = ! no_max_age; 1621 1622 if (wc->request.withdraw.age_proof_required) 1623 { 1624 /* The age value MUST be on the beginning of an age group */ 1625 if (wc->request.withdraw.max_age != 1626 TALER_get_lowest_age (&TEH_age_restriction_config.mask, 1627 wc->request.withdraw.max_age)) 1628 { 1629 GNUNET_break_op (0); 1630 SET_ERROR_WITH_DETAIL ( 1631 wc, 1632 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1633 request_parameter_malformed, 1634 "max_age must be the lower edge of an age group"); 1635 return; 1636 } 1637 } 1638 1639 /* validate array size */ 1640 { 1641 size_t num_coins = json_array_size (j_denoms_h); 1642 size_t array_size = json_array_size (j_coin_evs); 1643 const char *error; 1644 1645 GNUNET_static_assert ( 1646 TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA); 1647 1648 #define BAIL_IF(cond, msg) \ 1649 if ((cond)) { \ 1650 GNUNET_break_op (0); \ 1651 error = (msg); break; \ 1652 } 1653 1654 do { 1655 BAIL_IF (0 == num_coins, 1656 "denoms_h must not be empty") 1657 1658 /** 1659 * The wallet had committed to more than the maximum coins allowed, the 1660 * reserve has been charged, but now the user can not withdraw any money 1661 * from it. Note that the user can't get their money back in this case! 1662 */ 1663 BAIL_IF (num_coins > TALER_MAX_COINS, 1664 "maximum number of coins that can be withdrawn has been exceeded") 1665 1666 BAIL_IF ((! wc->request.withdraw.age_proof_required) && 1667 (num_coins != array_size), 1668 "denoms_h and coin_evs must be arrays of the same size") 1669 1670 BAIL_IF (wc->request.withdraw.age_proof_required && 1671 ((TALER_CNC_KAPPA * num_coins) != array_size), 1672 "coin_evs must be an array of length " 1673 TALER_CNC_KAPPA_STR 1674 "*len(denoms_h)") 1675 1676 wc->request.withdraw.num_coins = num_coins; 1677 wc->request.num_planchets = array_size; 1678 error = NULL; 1679 1680 } while (0); 1681 #undef BAIL_IF 1682 1683 if (NULL != error) 1684 { 1685 SET_ERROR_WITH_DETAIL (wc, 1686 WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED, 1687 request_parameter_malformed, 1688 error); 1689 return; 1690 } 1691 } 1692 /* extract the denomination hashes */ 1693 { 1694 size_t idx; 1695 json_t *value; 1696 1697 wc->request.denoms_h 1698 = GNUNET_new_array (wc->request.withdraw.num_coins, 1699 struct TALER_DenominationHashP); 1700 1701 json_array_foreach (j_denoms_h, idx, value) { 1702 struct GNUNET_JSON_Specification ispec[] = { 1703 GNUNET_JSON_spec_fixed_auto (NULL, 1704 &wc->request.denoms_h[idx]), 1705 GNUNET_JSON_spec_end () 1706 }; 1707 1708 res = TALER_MHD_parse_json_data (wc->rc->connection, 1709 value, 1710 ispec); 1711 if (GNUNET_YES != res) 1712 { 1713 GNUNET_break_op (0); 1714 wc->phase = (GNUNET_SYSERR == res) 1715 ? WITHDRAW_PHASE_RETURN_NO 1716 : WITHDRAW_PHASE_RETURN_YES; 1717 return; 1718 } 1719 } 1720 } 1721 /* Parse the blinded coin envelopes */ 1722 { 1723 json_t *j_cev; 1724 size_t idx; 1725 1726 wc->request.planchets = 1727 GNUNET_new_array (wc->request.num_planchets, 1728 struct TALER_BlindedPlanchet); 1729 json_array_foreach (j_coin_evs, idx, j_cev) 1730 { 1731 /* Now parse the individual envelopes and calculate the hash of 1732 * the commitment along the way. */ 1733 struct GNUNET_JSON_Specification kspec[] = { 1734 TALER_JSON_spec_blinded_planchet (NULL, 1735 &wc->request.planchets[idx]), 1736 GNUNET_JSON_spec_end () 1737 }; 1738 1739 res = TALER_MHD_parse_json_data (wc->rc->connection, 1740 j_cev, 1741 kspec); 1742 if (GNUNET_OK != res) 1743 { 1744 GNUNET_break_op (0); 1745 wc->phase = (GNUNET_SYSERR == res) 1746 ? WITHDRAW_PHASE_RETURN_NO 1747 : WITHDRAW_PHASE_RETURN_YES; 1748 return; 1749 } 1750 1751 /* Check for duplicate planchets. Technically a bug on 1752 * the client side that is harmless for us, but still 1753 * not allowed per protocol */ 1754 for (size_t i = 0; i < idx; i++) 1755 { 1756 if (0 == 1757 TALER_blinded_planchet_cmp ( 1758 &wc->request.planchets[idx], 1759 &wc->request.planchets[i])) 1760 { 1761 GNUNET_break_op (0); 1762 SET_ERROR (wc, 1763 WITHDRAW_ERROR_IDEMPOTENT_PLANCHET); 1764 return; 1765 } 1766 } /* end duplicate check */ 1767 } /* json_array_foreach over j_coin_evs */ 1768 } /* scope of j_kappa_planchets, idx */ 1769 wc->phase = WITHDRAW_PHASE_CHECK_KEYS; 1770 } 1771 1772 1773 enum MHD_Result 1774 TEH_handler_withdraw ( 1775 struct TEH_RequestContext *rc, 1776 const json_t *root, 1777 const char *const args[0]) 1778 { 1779 struct WithdrawContext *wc = rc->rh_ctx; 1780 1781 (void) args; 1782 if (NULL == wc) 1783 { 1784 wc = GNUNET_new (struct WithdrawContext); 1785 rc->rh_ctx = wc; 1786 rc->rh_cleaner = &clean_withdraw_rc; 1787 wc->rc = rc; 1788 wc->now = GNUNET_TIME_timestamp_get (); 1789 } 1790 while (true) 1791 { 1792 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1793 "withdrawal%s processing in phase %d\n", 1794 wc->request.withdraw.age_proof_required 1795 ? " (with required age proof)" 1796 : "", 1797 wc->phase); 1798 switch (wc->phase) 1799 { 1800 case WITHDRAW_PHASE_PARSE: 1801 withdraw_phase_parse (wc, 1802 root); 1803 break; 1804 case WITHDRAW_PHASE_CHECK_KEYS: 1805 phase_check_keys (wc); 1806 break; 1807 case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE: 1808 phase_check_reserve_signature (wc); 1809 break; 1810 case WITHDRAW_PHASE_RUN_LEGI_CHECK: 1811 phase_run_legi_check (wc); 1812 break; 1813 case WITHDRAW_PHASE_SUSPENDED: 1814 return MHD_YES; 1815 case WITHDRAW_PHASE_CHECK_KYC_RESULT: 1816 phase_check_kyc_result (wc); 1817 break; 1818 case WITHDRAW_PHASE_PREPARE_TRANSACTION: 1819 phase_prepare_transaction (wc); 1820 break; 1821 case WITHDRAW_PHASE_RUN_TRANSACTION: 1822 phase_run_transaction (wc); 1823 break; 1824 case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS: 1825 phase_generate_reply_success (wc); 1826 break; 1827 case WITHDRAW_PHASE_GENERATE_REPLY_ERROR: 1828 phase_generate_reply_error (wc); 1829 break; 1830 case WITHDRAW_PHASE_RETURN_YES: 1831 return MHD_YES; 1832 case WITHDRAW_PHASE_RETURN_NO: 1833 return MHD_NO; 1834 } 1835 } 1836 }