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