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