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