taler-merchant-httpd_post-fountain-withdraw.c (38608B)
1 /* 2 This file is part of TALER 3 (C) 2026 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, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file src/backend/taler-merchant-httpd_post-fountain-withdraw.c 22 * @brief implementing POST /fountain/withdraw request handling (DD 98) 23 * @author Bohdan Potuzhnyi 24 */ 25 #include "platform.h" 26 #include "taler-merchant-httpd_post-fountain-withdraw.h" 27 #include "taler-merchant-httpd_token-keys.h" 28 #include <taler/taler_json_lib.h> 29 #include "merchant-database/do_fountain_withdraw.h" 30 #include "merchant-database/get_fountain_by_secret.h" 31 #include "merchant-database/get_fountain_withdraw.h" 32 #include "merchant-database/get_token_family.h" 33 #include "merchant-database/insert_fountain_withdraw_sig.h" 34 #include "merchant-database/insert_issued_token.h" 35 #include "merchant-database/iterate_fountain_grants.h" 36 #include "merchant-database/start.h" 37 38 39 /** 40 * Phases of processing a fountain withdrawal. 41 */ 42 enum WithdrawPhase 43 { 44 /** 45 * Parse the request envelope. 46 */ 47 WP_PARSE_REQUEST = 0, 48 49 /** 50 * Authenticate the fountain credential. 51 */ 52 WP_AUTHENTICATE, 53 54 /** 55 * Start the withdrawal transaction. 56 */ 57 WP_START_TRANSACTION, 58 59 /** 60 * Lock the fountain and restore any completed withdrawal. 61 */ 62 WP_CHECK_REPLAY, 63 64 /** 65 * Load the current grants for a new withdrawal. 66 */ 67 WP_LOAD_GRANTS, 68 69 /** 70 * Parse the requested tokens and check their grants. 71 */ 72 WP_PARSE_ENTRIES, 73 74 /** 75 * Resolve issue keys and reject duplicate slots. 76 */ 77 WP_RESOLVE_KEYS, 78 79 /** 80 * Check and consume the quota for all entries. 81 */ 82 WP_CONSUME_QUOTA, 83 84 /** 85 * Sign tokens and store issued-token and replay records. 86 */ 87 WP_SIGN_TOKENS, 88 89 /** 90 * Commit the withdrawal before sending its result. 91 */ 92 WP_COMMIT_TRANSACTION, 93 94 /** 95 * Return the newly issued or restored signatures. 96 */ 97 WP_SUCCESS_RESPONSE, 98 99 /** 100 * Return #MHD_YES to end processing. 101 */ 102 WP_END_YES, 103 104 /** 105 * Return #MHD_NO to end processing. 106 */ 107 WP_END_NO 108 }; 109 110 111 /** 112 * A grant of the fountain the request authenticated as. 113 */ 114 struct GrantInfo 115 { 116 /** 117 * Slug of the granted token family. 118 */ 119 char *token_family_slug; 120 121 /** 122 * Serial of the granted token family. 123 */ 124 uint64_t token_family_serial; 125 126 /** 127 * Number of issue-key slots ahead the wallet may withdraw for. 128 */ 129 uint32_t key_window_size; 130 }; 131 132 133 /** 134 * One entry of the withdraw request being processed. 135 */ 136 struct WithdrawEntry 137 { 138 /** 139 * Slug of the token family withdrawn from; aliases into the 140 * request body. 141 */ 142 const char *token_family_slug; 143 144 /** 145 * Desired token validity time; selects a key from the grant's window. 146 */ 147 struct GNUNET_TIME_Timestamp valid_at; 148 149 /** 150 * Blinded envelopes to sign. 151 */ 152 struct TALER_TokenEnvelope *envelopes; 153 154 /** 155 * Length of the @e envelopes array. 156 */ 157 unsigned int envelopes_len; 158 159 /** 160 * Serial of the token family. 161 */ 162 uint64_t token_family_serial; 163 164 /** 165 * Issue key (and token family details) of the selected slot. 166 */ 167 struct TALER_MERCHANTDB_TokenFamilyKeyDetails kd; 168 169 /** 170 * True if @e kd was initialized and must be released. 171 */ 172 bool have_kd; 173 }; 174 175 176 /** 177 * Request-specific context of a POST /fountain/withdraw request. 178 */ 179 struct WithdrawContext 180 { 181 /** 182 * Connection to respond on. 183 */ 184 struct MHD_Connection *connection; 185 186 /** 187 * Handler context, including the instance and request body. 188 */ 189 struct TMH_HandlerContext *hc; 190 191 /** 192 * Current processing phase. 193 */ 194 enum WithdrawPhase phase; 195 196 /** 197 * Fountain credential borrowed from the request body. 198 */ 199 const char *fountain_secret; 200 201 /** 202 * JSON array of withdrawal entries borrowed from the request body. 203 */ 204 const json_t *jentries; 205 206 /** 207 * Canonical hash identifying this withdrawal within the fountain. 208 */ 209 struct GNUNET_HashCode h_request; 210 211 /** 212 * Grant results to return after replay or successful commit. 213 */ 214 json_t *results; 215 216 /** 217 * True while this synchronous handler owns an open transaction. 218 */ 219 bool transaction_open; 220 221 /** 222 * Grants of the fountain the request authenticated as. 223 */ 224 struct GrantInfo *grants; 225 226 /** 227 * Length of the @e grants array. 228 */ 229 unsigned int grants_len; 230 231 /** 232 * Entries of the request. 233 */ 234 struct WithdrawEntry *entries; 235 236 /** 237 * Length of the @e entries array. 238 */ 239 unsigned int entries_len; 240 241 /** 242 * Serial of the fountain the request authenticated as. 243 */ 244 uint64_t fountain_serial; 245 246 /** 247 * Time this request is processed at. 248 */ 249 struct GNUNET_TIME_Timestamp now; 250 }; 251 252 253 /** 254 * Release the request-specific context. 255 * 256 * @param cls a `struct WithdrawContext *` 257 */ 258 static void 259 withdraw_context_cleanup (void *cls) 260 { 261 struct WithdrawContext *wc = cls; 262 263 for (unsigned int i = 0; i < wc->grants_len; i++) 264 GNUNET_free (wc->grants[i].token_family_slug); 265 GNUNET_array_grow (wc->grants, 266 wc->grants_len, 267 0); 268 /* @e entries is only allocated once the request body was parsed; 269 @e entries_len is known before that. */ 270 for (unsigned int i = 0; (NULL != wc->entries) && (i < wc->entries_len); i++) 271 { 272 struct WithdrawEntry *we = &wc->entries[i]; 273 274 for (unsigned int j = 0; j < we->envelopes_len; j++) 275 if (NULL != we->envelopes[j].blinded_pub) 276 GNUNET_CRYPTO_blinded_message_decref (we->envelopes[j].blinded_pub); 277 GNUNET_free (we->envelopes); 278 if (we->have_kd) 279 TMH_token_key_details_free (&we->kd); 280 } 281 GNUNET_free (wc->entries); 282 json_decref (wc->results); 283 GNUNET_free (wc); 284 } 285 286 287 /** 288 * Add a grant of the fountain to the context in @a cls. 289 * 290 * @param cls a `struct WithdrawContext *` 291 * @param token_family_slug slug of the granted token family 292 * @param token_family_serial serial of the granted token family 293 * @param tokens_per_period_limit maximum withdrawals per period 294 * @param tokens_per_period_stash suggested tokens to hold per period 295 * @param key_window_size number of slots ahead withdrawals are allowed 296 */ 297 static void 298 add_grant (void *cls, 299 const char *token_family_slug, 300 uint64_t token_family_serial, 301 uint64_t tokens_per_period_limit, 302 uint64_t tokens_per_period_stash, 303 uint32_t key_window_size) 304 { 305 struct WithdrawContext *wc = cls; 306 struct GrantInfo gi = { 307 .token_family_slug = GNUNET_strdup (token_family_slug), 308 .token_family_serial = token_family_serial, 309 .key_window_size = key_window_size 310 }; 311 312 (void) tokens_per_period_limit; 313 (void) tokens_per_period_stash; 314 GNUNET_array_append (wc->grants, 315 wc->grants_len, 316 gi); 317 } 318 319 320 /** 321 * Find the grant for @a slug among the grants of the fountain. 322 * 323 * @param wc context to search 324 * @param slug token family slug to look for 325 * @return NULL if the fountain does not grant @a slug 326 */ 327 static const struct GrantInfo * 328 find_grant (const struct WithdrawContext *wc, 329 const char *slug) 330 { 331 for (unsigned int i = 0; i < wc->grants_len; i++) 332 if (0 == strcmp (wc->grants[i].token_family_slug, 333 slug)) 334 return &wc->grants[i]; 335 return NULL; 336 } 337 338 339 /** 340 * Authenticate the request using the supplied fountain secret. 341 * 342 * @param[in,out] wc context to initialize 343 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 344 * queued, #GNUNET_SYSERR on hard failure 345 */ 346 static enum GNUNET_GenericReturnValue 347 phase_authenticate (struct WithdrawContext *wc) 348 { 349 struct MHD_Connection *connection = wc->connection; 350 const char *instance_id = wc->hc->instance->settings.id; 351 const char *fountain_secret = wc->fountain_secret; 352 struct GNUNET_HashCode h_secret; 353 struct GNUNET_TIME_Relative poll_freq; 354 enum GNUNET_DB_QueryStatus qs; 355 356 { 357 char secret[32]; 358 359 if (GNUNET_OK != 360 GNUNET_STRINGS_string_to_data (fountain_secret, 361 strlen (fountain_secret), 362 secret, 363 sizeof (secret))) 364 { 365 /* Reply as for an unknown credential, so that the endpoint does 366 not become an oracle for the shape of valid secrets. */ 367 GNUNET_break_op (0); 368 return (MHD_YES == 369 TALER_MHD_reply_with_error (connection, 370 MHD_HTTP_UNAUTHORIZED, 371 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 372 "fountain secret")) 373 ? GNUNET_NO 374 : GNUNET_SYSERR; 375 } 376 GNUNET_CRYPTO_hash (secret, 377 sizeof (secret), 378 &h_secret); 379 memset (secret, 380 0, 381 sizeof (secret)); 382 } 383 qs = TALER_MERCHANTDB_get_fountain_by_secret (TMH_db, 384 instance_id, 385 &h_secret, 386 &wc->fountain_serial, 387 &poll_freq); 388 if (0 > qs) 389 { 390 GNUNET_break (0); 391 return (MHD_YES == 392 TALER_MHD_reply_with_error (connection, 393 MHD_HTTP_INTERNAL_SERVER_ERROR, 394 TALER_EC_GENERIC_DB_FETCH_FAILED, 395 "get_fountain_by_secret")) 396 ? GNUNET_NO 397 : GNUNET_SYSERR; 398 } 399 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 400 return (MHD_YES == 401 TALER_MHD_reply_with_error (connection, 402 MHD_HTTP_UNAUTHORIZED, 403 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 404 "fountain secret")) 405 ? GNUNET_NO 406 : GNUNET_SYSERR; 407 wc->phase = WP_START_TRANSACTION; 408 return GNUNET_OK; 409 } 410 411 412 /** 413 * Load current grants after locking the fountain and checking for a replay. 414 * 415 * @param[in,out] wc context to populate 416 * @return #GNUNET_OK on success, #GNUNET_NO if an error was queued, 417 * #GNUNET_SYSERR if queueing failed 418 */ 419 static enum GNUNET_GenericReturnValue 420 phase_load_grants (struct WithdrawContext *wc) 421 { 422 struct MHD_Connection *connection = wc->connection; 423 const char *instance_id = wc->hc->instance->settings.id; 424 enum GNUNET_DB_QueryStatus qs; 425 426 qs = TALER_MERCHANTDB_iterate_fountain_grants (TMH_db, 427 instance_id, 428 wc->fountain_serial, 429 &add_grant, 430 wc); 431 if (0 > qs) 432 { 433 GNUNET_break (0); 434 return (MHD_YES == 435 TALER_MHD_reply_with_error (connection, 436 MHD_HTTP_INTERNAL_SERVER_ERROR, 437 TALER_EC_GENERIC_DB_FETCH_FAILED, 438 "iterate_fountain_grants")) 439 ? GNUNET_NO 440 : GNUNET_SYSERR; 441 } 442 wc->phase = WP_PARSE_ENTRIES; 443 return GNUNET_OK; 444 } 445 446 447 /** 448 * Parse the envelopes of one request entry. 449 * 450 * @param connection connection to report errors on 451 * @param jenvelopes JSON array of token envelopes 452 * @param total_envelopes total number of envelopes parsed so far 453 * @param[in,out] we entry to complete 454 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 455 * queued, #GNUNET_SYSERR on hard failure 456 */ 457 static enum GNUNET_GenericReturnValue 458 parse_envelopes (struct MHD_Connection *connection, 459 const json_t *jenvelopes, 460 unsigned int total_envelopes, 461 struct WithdrawEntry *we) 462 { 463 unsigned int len = (unsigned int) json_array_size (jenvelopes); 464 size_t idx; 465 json_t *jev; 466 467 if ( (0 == len) || 468 (total_envelopes + len > TMH_MAX_FOUNTAIN_ENVELOPES) ) 469 { 470 GNUNET_break_op (0); 471 return (MHD_YES == 472 TALER_MHD_reply_with_error (connection, 473 MHD_HTTP_BAD_REQUEST, 474 TALER_EC_GENERIC_PARAMETER_MALFORMED, 475 "'envelopes' empty or too many envelopes")) 476 ? GNUNET_NO 477 : GNUNET_SYSERR; 478 } 479 we->envelopes = GNUNET_new_array (len, 480 struct TALER_TokenEnvelope); 481 we->envelopes_len = len; 482 json_array_foreach ((json_t *) jenvelopes, idx, jev) 483 { 484 struct GNUNET_JSON_Specification ispec[] = { 485 TALER_JSON_spec_token_envelope (NULL, 486 &we->envelopes[idx]), 487 GNUNET_JSON_spec_end () 488 }; 489 enum GNUNET_GenericReturnValue res; 490 491 res = TALER_MHD_parse_json_data (connection, 492 jev, 493 ispec); 494 if (GNUNET_OK != res) 495 { 496 GNUNET_break_op (0); 497 return res; 498 } 499 } 500 return GNUNET_OK; 501 } 502 503 504 /** 505 * Parse the entries of the request into @a wc, checking each against 506 * the grants of the fountain. Resolve keys after all envelopes are parsed. 507 * 508 * @param[in,out] wc context to complete 509 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 510 * queued, #GNUNET_SYSERR on hard failure 511 */ 512 static enum GNUNET_GenericReturnValue 513 phase_parse_entries (struct WithdrawContext *wc) 514 { 515 struct MHD_Connection *connection = wc->connection; 516 const json_t *jentries = wc->jentries; 517 unsigned int total_envelopes = 0; 518 size_t idx; 519 json_t *jentry; 520 521 wc->entries = GNUNET_new_array (wc->entries_len, 522 struct WithdrawEntry); 523 json_array_foreach ((json_t *) jentries, idx, jentry) 524 { 525 struct WithdrawEntry *we = &wc->entries[idx]; 526 const json_t *jenvelopes; 527 const struct GrantInfo *gi; 528 enum GNUNET_GenericReturnValue res; 529 struct GNUNET_JSON_Specification espec[] = { 530 GNUNET_JSON_spec_string ("token_family_slug", 531 &we->token_family_slug), 532 GNUNET_JSON_spec_mark_optional ( 533 GNUNET_JSON_spec_timestamp ("valid_at", 534 &we->valid_at), 535 NULL), 536 GNUNET_JSON_spec_array_const ("envelopes", 537 &jenvelopes), 538 GNUNET_JSON_spec_end () 539 }; 540 541 we->valid_at = wc->now; 542 res = TALER_MHD_parse_json_data (connection, 543 jentry, 544 espec); 545 if (GNUNET_OK != res) 546 { 547 GNUNET_break_op (0); 548 return res; 549 } 550 gi = find_grant (wc, 551 we->token_family_slug); 552 if (NULL == gi) 553 { 554 GNUNET_break_op (0); 555 return (MHD_YES == 556 TALER_MHD_reply_with_error ( 557 connection, 558 MHD_HTTP_CONFLICT, 559 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_GRANT_UNKNOWN, 560 we->token_family_slug)) 561 ? GNUNET_NO 562 : GNUNET_SYSERR; 563 } 564 we->token_family_serial = gi->token_family_serial; 565 res = parse_envelopes (connection, 566 jenvelopes, 567 total_envelopes, 568 we); 569 if (GNUNET_OK != res) 570 return res; 571 total_envelopes += we->envelopes_len; 572 } 573 wc->phase = WP_RESOLVE_KEYS; 574 return GNUNET_OK; 575 } 576 577 578 /** 579 * Select each entry's key from the same expiry-based window advertised 580 * by GET /fountain/info, and reject duplicate (token family, key) pairs. 581 * 582 * @param[in,out] wc context to complete 583 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 584 * queued, #GNUNET_SYSERR on hard failure 585 */ 586 static enum GNUNET_GenericReturnValue 587 phase_resolve_keys (struct WithdrawContext *wc) 588 { 589 struct MHD_Connection *connection = wc->connection; 590 const char *instance_id = wc->hc->instance->settings.id; 591 592 for (unsigned int i = 0; i < wc->entries_len; i++) 593 { 594 struct WithdrawEntry *we = &wc->entries[i]; 595 const struct GrantInfo *gi; 596 struct TALER_MERCHANTDB_TokenFamilyDetails tf; 597 struct TMH_TokenKeyWindow window; 598 unsigned int key_index = 0; 599 enum GNUNET_GenericReturnValue res; 600 enum GNUNET_DB_QueryStatus qs; 601 602 gi = find_grant (wc, 603 we->token_family_slug); 604 GNUNET_assert (NULL != gi); 605 qs = TALER_MERCHANTDB_get_token_family (TMH_db, 606 instance_id, 607 we->token_family_slug, 608 &tf); 609 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 610 { 611 GNUNET_break (0); 612 return (MHD_YES == 613 TALER_MHD_reply_with_error (connection, 614 MHD_HTTP_INTERNAL_SERVER_ERROR, 615 TALER_EC_GENERIC_DB_FETCH_FAILED, 616 "get_token_family")) 617 ? GNUNET_NO 618 : GNUNET_SYSERR; 619 } 620 res = TMH_token_key_window_get (connection, 621 instance_id, 622 &tf, 623 wc->now, 624 gi->key_window_size, 625 &window); 626 TALER_MERCHANTDB_token_family_details_free (&tf); 627 if (GNUNET_OK != res) 628 return res; 629 if ( (0 == window.keys_len) || 630 (GNUNET_OK != 631 TMH_token_key_window_find (&window, 632 we->valid_at, 633 &key_index)) ) 634 { 635 TMH_token_key_window_free (&window); 636 return (MHD_YES == 637 TALER_MHD_reply_with_error ( 638 connection, 639 MHD_HTTP_CONFLICT, 640 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_SLOT_OUTSIDE_WINDOW, 641 we->token_family_slug)) 642 ? GNUNET_NO 643 : GNUNET_SYSERR; 644 } 645 /* Transfer the selected key, rather than looking it up again by a 646 timestamp that an overlapping earlier key could also cover. */ 647 we->kd = window.keys[key_index]; 648 memset (&window.keys[key_index], 649 0, 650 sizeof (window.keys[key_index])); 651 we->have_kd = true; 652 TMH_token_key_window_free (&window); 653 if (NULL == we->kd.priv.private_key) 654 { 655 GNUNET_break (0); 656 return (MHD_YES == 657 TALER_MHD_reply_with_error ( 658 connection, 659 MHD_HTTP_INTERNAL_SERVER_ERROR, 660 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 661 "issue private key unavailable")) 662 ? GNUNET_NO 663 : GNUNET_SYSERR; 664 } 665 for (unsigned int j = 0; j < we->envelopes_len; j++) 666 { 667 if (we->envelopes[j].blinded_pub->cipher != 668 we->kd.priv.private_key->cipher) 669 { 670 GNUNET_break_op (0); 671 return (MHD_YES == 672 TALER_MHD_reply_with_error ( 673 connection, 674 MHD_HTTP_BAD_REQUEST, 675 TALER_EC_GENERIC_PARAMETER_MALFORMED, 676 "envelope cipher does not match issue key")) 677 ? GNUNET_NO 678 : GNUNET_SYSERR; 679 } 680 } 681 for (unsigned int j = 0; j < i; j++) 682 { 683 if ( (wc->entries[j].token_family_serial == 684 we->token_family_serial) && 685 (GNUNET_TIME_timestamp_cmp ( 686 wc->entries[j].kd.signature_validity_start, 687 ==, 688 we->kd.signature_validity_start)) ) 689 { 690 GNUNET_break_op (0); 691 return (MHD_YES == 692 TALER_MHD_reply_with_error ( 693 connection, 694 MHD_HTTP_BAD_REQUEST, 695 TALER_EC_GENERIC_PARAMETER_MALFORMED, 696 "multiple entries for one token family and key slot")) 697 ? GNUNET_NO 698 : GNUNET_SYSERR; 699 } 700 } 701 } 702 wc->phase = WP_CONSUME_QUOTA; 703 return GNUNET_OK; 704 } 705 706 707 /** 708 * Atomically check and consume the per-period withdrawal quota of 709 * all entries. Either the whole request fits, or nothing is 710 * consumed. 711 * 712 * @param[in,out] wc context of the request 713 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 714 * queued, #GNUNET_SYSERR on hard failure 715 */ 716 static enum GNUNET_GenericReturnValue 717 phase_consume_quota (struct WithdrawContext *wc) 718 { 719 struct MHD_Connection *connection = wc->connection; 720 const char *instance_id = wc->hc->instance->settings.id; 721 uint64_t family_serials[GNUNET_NZL (wc->entries_len)]; 722 struct GNUNET_TIME_Timestamp slot_starts[GNUNET_NZL (wc->entries_len)]; 723 uint64_t counts[GNUNET_NZL (wc->entries_len)]; 724 unsigned int failed_index; 725 bool no_grant; 726 bool exceeded; 727 enum GNUNET_DB_QueryStatus qs; 728 729 for (unsigned int i = 0; i < wc->entries_len; i++) 730 { 731 family_serials[i] = wc->entries[i].token_family_serial; 732 slot_starts[i] = wc->entries[i].kd.signature_validity_start; 733 counts[i] = wc->entries[i].envelopes_len; 734 } 735 qs = TALER_MERCHANTDB_do_fountain_withdraw (TMH_db, 736 instance_id, 737 wc->fountain_serial, 738 wc->entries_len, 739 family_serials, 740 slot_starts, 741 counts, 742 &failed_index, 743 &no_grant, 744 &exceeded); 745 if (0 > qs) 746 { 747 GNUNET_break (0); 748 return (MHD_YES == 749 TALER_MHD_reply_with_error (connection, 750 MHD_HTTP_INTERNAL_SERVER_ERROR, 751 TALER_EC_GENERIC_DB_STORE_FAILED, 752 "do_fountain_withdraw")) 753 ? GNUNET_NO 754 : GNUNET_SYSERR; 755 } 756 if (exceeded) 757 return (MHD_YES == 758 TALER_MHD_reply_with_error ( 759 connection, 760 MHD_HTTP_TOO_MANY_REQUESTS, 761 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_LIMIT_EXCEEDED, 762 wc->entries[failed_index].token_family_slug)) 763 ? GNUNET_NO 764 : GNUNET_SYSERR; 765 if (no_grant) 766 return (MHD_YES == 767 TALER_MHD_reply_with_error ( 768 connection, 769 MHD_HTTP_CONFLICT, 770 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_GRANT_UNKNOWN, 771 wc->entries[failed_index].token_family_slug)) 772 ? GNUNET_NO 773 : GNUNET_SYSERR; 774 wc->phase = WP_SIGN_TOKENS; 775 return GNUNET_OK; 776 } 777 778 779 /** 780 * Blind-sign the envelopes of all entries, record the issued tokens 781 * and construct the grant results. The caller must commit the quota, 782 * issued-token records and replay results together before responding. 783 * 784 * @param[in,out] wc context of the request 785 * @return #GNUNET_OK on success, #GNUNET_NO if an error was queued, 786 * #GNUNET_SYSERR if queueing failed 787 */ 788 static enum GNUNET_GenericReturnValue 789 phase_sign_tokens (struct WithdrawContext *wc) 790 { 791 struct MHD_Connection *connection = wc->connection; 792 const struct GNUNET_HashCode *h_request = &wc->h_request; 793 json_t *jresults; 794 795 jresults = json_array (); 796 GNUNET_assert (NULL != jresults); 797 for (unsigned int i = 0; i < wc->entries_len; i++) 798 { 799 const struct WithdrawEntry *we = &wc->entries[i]; 800 struct TALER_TokenIssuePublicKeyHashP h_issue = { 801 .hash = we->kd.pub.public_key->pub_key_hash 802 }; 803 json_t *jsigs; 804 805 jsigs = json_array (); 806 GNUNET_assert (NULL != jsigs); 807 for (unsigned int j = 0; j < we->envelopes_len; j++) 808 { 809 struct TALER_BlindedTokenIssueSignature sig; 810 enum GNUNET_DB_QueryStatus qs; 811 bool no_family; 812 813 TALER_token_issue_sign (&we->kd.priv, 814 &we->envelopes[j], 815 &sig); 816 if (NULL == sig.signature) 817 { 818 GNUNET_break (0); 819 json_decref (jsigs); 820 json_decref (jresults); 821 return (MHD_YES == TALER_MHD_reply_with_error ( 822 connection, 823 MHD_HTTP_INTERNAL_SERVER_ERROR, 824 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 825 "token_issue_sign")) 826 ? GNUNET_NO 827 : GNUNET_SYSERR; 828 } 829 qs = TALER_MERCHANTDB_insert_issued_token (TMH_db, 830 NULL, 831 &h_issue, 832 &sig, 833 &no_family); 834 if (0 > qs) 835 { 836 GNUNET_break (0); 837 GNUNET_CRYPTO_blinded_sig_decref (sig.signature); 838 json_decref (jsigs); 839 json_decref (jresults); 840 return (MHD_YES == TALER_MHD_reply_with_error (connection, 841 MHD_HTTP_INTERNAL_SERVER_ERROR, 842 TALER_EC_GENERIC_DB_STORE_FAILED, 843 "insert_issued_token")) 844 ? GNUNET_NO 845 : GNUNET_SYSERR; 846 } 847 if (no_family) 848 { 849 /* The token family key was deleted after resolving the grant, 850 so we cannot issue this token anymore. */ 851 GNUNET_break_op (0); 852 GNUNET_CRYPTO_blinded_sig_decref (sig.signature); 853 json_decref (jsigs); 854 json_decref (jresults); 855 return (MHD_YES == TALER_MHD_reply_with_error (connection, 856 MHD_HTTP_NOT_FOUND, 857 TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN, 858 NULL)) 859 ? GNUNET_NO 860 : GNUNET_SYSERR; 861 } 862 qs = TALER_MERCHANTDB_insert_fountain_withdraw_sig (TMH_db, 863 wc->fountain_serial, 864 h_request, 865 i, 866 j, 867 &h_issue, 868 &sig); 869 if (0 >= qs) 870 { 871 GNUNET_break (0); 872 GNUNET_CRYPTO_blinded_sig_decref (sig.signature); 873 json_decref (jsigs); 874 json_decref (jresults); 875 return (MHD_YES == TALER_MHD_reply_with_error ( 876 connection, 877 MHD_HTTP_INTERNAL_SERVER_ERROR, 878 TALER_EC_GENERIC_DB_STORE_FAILED, 879 "insert_fountain_withdraw_sig")) 880 ? GNUNET_NO 881 : GNUNET_SYSERR; 882 } 883 GNUNET_assert (0 == 884 json_array_append_new ( 885 jsigs, 886 GNUNET_JSON_PACK ( 887 GNUNET_JSON_pack_blinded_sig ("blind_sig", 888 sig.signature)))); 889 GNUNET_CRYPTO_blinded_sig_decref (sig.signature); 890 } 891 GNUNET_assert (0 == 892 json_array_append_new ( 893 jresults, 894 GNUNET_JSON_PACK ( 895 GNUNET_JSON_pack_string ("token_family_slug", 896 we->token_family_slug), 897 GNUNET_JSON_pack_data_auto ("h_issue", 898 &h_issue), 899 GNUNET_JSON_pack_array_steal ("token_sigs", 900 jsigs)))); 901 } 902 wc->results = jresults; 903 wc->phase = WP_COMMIT_TRANSACTION; 904 return GNUNET_OK; 905 } 906 907 908 /** 909 * Closure for #restore_grant_cb(). 910 */ 911 struct RestoreState 912 { 913 /** 914 * Grant results rebuilt so far. 915 */ 916 json_t *grants; 917 918 /** 919 * Signatures of the grant currently being rebuilt. 920 */ 921 json_t *sigs; 922 923 /** 924 * Slug of that grant. 925 */ 926 char *slug; 927 928 /** 929 * Issue key hash of that grant. 930 */ 931 struct TALER_TokenIssuePublicKeyHashP h_issue; 932 933 /** 934 * Index of that grant in the original request. 935 */ 936 uint32_t grant_index; 937 938 /** 939 * True once a grant has been started, so @e slug and @e h_issue 940 * are meaningful. 941 */ 942 bool started; 943 }; 944 945 946 /** 947 * Append the grant currently being rebuilt to the results. 948 * 949 * @param[in,out] rs state to flush 950 */ 951 static void 952 flush_grant (struct RestoreState *rs) 953 { 954 if (! rs->started) 955 return; 956 GNUNET_assert (0 == 957 json_array_append_new ( 958 rs->grants, 959 GNUNET_JSON_PACK ( 960 GNUNET_JSON_pack_string ("token_family_slug", 961 rs->slug), 962 GNUNET_JSON_pack_data_auto ("h_issue", 963 &rs->h_issue), 964 GNUNET_JSON_pack_array_steal ("token_sigs", 965 rs->sigs)))); 966 GNUNET_free (rs->slug); 967 rs->sigs = NULL; 968 rs->started = false; 969 } 970 971 972 /** 973 * Rebuild the response of an earlier withdrawal from its stored 974 * signatures. Rows arrive in response order, so a change of 975 * @a grant_index closes the grant being assembled. 976 * 977 * @param cls a `struct RestoreState *` 978 * @param grant_index offset of the grant in the original request 979 * @param token_family_slug token family of that grant 980 * @param h_issue issue key the signature was made with 981 * @param blind_sig the blind signature handed out originally 982 */ 983 static void 984 restore_grant_cb (void *cls, 985 uint32_t grant_index, 986 const char *token_family_slug, 987 const struct TALER_TokenIssuePublicKeyHashP *h_issue, 988 const struct GNUNET_CRYPTO_BlindedSignature *blind_sig) 989 { 990 struct RestoreState *rs = cls; 991 992 if ( (! rs->started) || 993 (grant_index != rs->grant_index) ) 994 { 995 flush_grant (rs); 996 rs->sigs = json_array (); 997 GNUNET_assert (NULL != rs->sigs); 998 rs->slug = GNUNET_strdup (token_family_slug); 999 rs->h_issue = *h_issue; 1000 rs->grant_index = grant_index; 1001 rs->started = true; 1002 } 1003 GNUNET_assert (0 == 1004 json_array_append_new ( 1005 rs->sigs, 1006 GNUNET_JSON_PACK ( 1007 GNUNET_JSON_pack_blinded_sig ( 1008 "blind_sig", 1009 (struct GNUNET_CRYPTO_BlindedSignature *) blind_sig)))); 1010 } 1011 1012 1013 /** 1014 * Parse the top-level request without inspecting the current grants. 1015 * 1016 * @param[in,out] wc request context 1017 * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued, 1018 * #GNUNET_SYSERR if queueing failed 1019 */ 1020 static enum GNUNET_GenericReturnValue 1021 phase_parse_request (struct WithdrawContext *wc) 1022 { 1023 struct MHD_Connection *connection = wc->connection; 1024 struct GNUNET_JSON_Specification spec[] = { 1025 GNUNET_JSON_spec_string ("fountain_secret", 1026 &wc->fountain_secret), 1027 GNUNET_JSON_spec_array_const ("grants", 1028 &wc->jentries), 1029 GNUNET_JSON_spec_end () 1030 }; 1031 enum GNUNET_GenericReturnValue res; 1032 1033 res = TALER_MHD_parse_json_data (connection, 1034 wc->hc->request_body, 1035 spec); 1036 if (GNUNET_OK != res) 1037 { 1038 GNUNET_break_op (0); 1039 return res; 1040 } 1041 { 1042 size_t len = json_array_size (wc->jentries); 1043 1044 if (len > TMH_MAX_FOUNTAIN_ENVELOPES) 1045 { 1046 GNUNET_break_op (0); 1047 return (MHD_YES == TALER_MHD_reply_with_error ( 1048 connection, 1049 MHD_HTTP_BAD_REQUEST, 1050 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1051 "'grants' array too long")) 1052 ? GNUNET_NO 1053 : GNUNET_SYSERR; 1054 } 1055 wc->entries_len = (unsigned int) len; 1056 } 1057 wc->phase = WP_AUTHENTICATE; 1058 return GNUNET_OK; 1059 } 1060 1061 1062 /** 1063 * Start the transaction covering replay, quotas and token issuance. 1064 * 1065 * @param[in,out] wc request context 1066 * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued, 1067 * #GNUNET_SYSERR if queueing failed 1068 */ 1069 static enum GNUNET_GenericReturnValue 1070 phase_start_transaction (struct WithdrawContext *wc) 1071 { 1072 TALER_json_hash (wc->jentries, 1073 &wc->h_request); 1074 if (GNUNET_OK != TALER_MERCHANTDB_start_read_committed ( 1075 TMH_db, 1076 "fountain withdraw")) 1077 return (MHD_YES == TALER_MHD_reply_with_error ( 1078 wc->connection, 1079 MHD_HTTP_INTERNAL_SERVER_ERROR, 1080 TALER_EC_GENERIC_DB_START_FAILED, 1081 NULL)) 1082 ? GNUNET_NO 1083 : GNUNET_SYSERR; 1084 wc->transaction_open = true; 1085 wc->phase = WP_CHECK_REPLAY; 1086 return GNUNET_OK; 1087 } 1088 1089 1090 /** 1091 * Lock the fountain and restore a completed withdrawal, if any. 1092 * 1093 * @param[in,out] wc request context 1094 * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued, 1095 * #GNUNET_SYSERR if queueing failed 1096 */ 1097 static enum GNUNET_GenericReturnValue 1098 phase_check_replay (struct WithdrawContext *wc) 1099 { 1100 struct RestoreState rs = { 0 }; 1101 enum GNUNET_DB_QueryStatus qs; 1102 bool not_found; 1103 1104 /* Serialize withdrawals of this fountain. Replay before consulting 1105 grants or key windows, which may have changed since the first call. */ 1106 rs.grants = json_array (); 1107 GNUNET_assert (NULL != rs.grants); 1108 qs = TALER_MERCHANTDB_get_fountain_withdraw (TMH_db, 1109 wc->fountain_serial, 1110 &wc->h_request, 1111 ¬_found, 1112 &restore_grant_cb, 1113 &rs); 1114 flush_grant (&rs); 1115 if (qs < 0) 1116 { 1117 json_decref (rs.grants); 1118 return (MHD_YES == TALER_MHD_reply_with_error ( 1119 wc->connection, 1120 MHD_HTTP_INTERNAL_SERVER_ERROR, 1121 TALER_EC_GENERIC_DB_FETCH_FAILED, 1122 "get_fountain_withdraw")) 1123 ? GNUNET_NO 1124 : GNUNET_SYSERR; 1125 } 1126 if (not_found) 1127 { 1128 json_decref (rs.grants); 1129 return (MHD_YES == TALER_MHD_reply_with_error ( 1130 wc->connection, 1131 MHD_HTTP_UNAUTHORIZED, 1132 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 1133 "fountain secret")) 1134 ? GNUNET_NO 1135 : GNUNET_SYSERR; 1136 } 1137 if (0 != json_array_size (rs.grants)) 1138 { 1139 TALER_MERCHANTDB_rollback (TMH_db); 1140 wc->transaction_open = false; 1141 wc->results = rs.grants; 1142 wc->phase = WP_SUCCESS_RESPONSE; 1143 return GNUNET_OK; 1144 } 1145 json_decref (rs.grants); 1146 wc->phase = WP_LOAD_GRANTS; 1147 return GNUNET_OK; 1148 } 1149 1150 1151 /** 1152 * Commit quota, issued tokens and replay records together. 1153 * 1154 * @param[in,out] wc request context 1155 * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued, 1156 * #GNUNET_SYSERR if queueing failed 1157 */ 1158 static enum GNUNET_GenericReturnValue 1159 phase_commit_transaction (struct WithdrawContext *wc) 1160 { 1161 enum GNUNET_DB_QueryStatus qs; 1162 1163 qs = TALER_MERCHANTDB_commit (TMH_db); 1164 if (qs < 0) 1165 { 1166 return (MHD_YES == TALER_MHD_reply_with_error ( 1167 wc->connection, 1168 MHD_HTTP_INTERNAL_SERVER_ERROR, 1169 TALER_EC_GENERIC_DB_COMMIT_FAILED, 1170 NULL)) 1171 ? GNUNET_NO 1172 : GNUNET_SYSERR; 1173 } 1174 wc->transaction_open = false; 1175 wc->phase = WP_SUCCESS_RESPONSE; 1176 return GNUNET_OK; 1177 } 1178 1179 1180 /** 1181 * Return the newly issued or restored grant results. 1182 * 1183 * @param[in,out] wc request context 1184 * @return #GNUNET_NO if the response was queued, #GNUNET_SYSERR otherwise 1185 */ 1186 static enum GNUNET_GenericReturnValue 1187 phase_success_response (struct WithdrawContext *wc) 1188 { 1189 enum MHD_Result ret; 1190 1191 GNUNET_assert (! wc->transaction_open); 1192 ret = TALER_MHD_REPLY_JSON_PACK ( 1193 wc->connection, 1194 MHD_HTTP_OK, 1195 GNUNET_JSON_pack_array_steal ("grants", 1196 wc->results)); 1197 wc->results = NULL; 1198 return (MHD_YES == ret) ? GNUNET_NO : GNUNET_SYSERR; 1199 } 1200 1201 1202 enum MHD_Result 1203 TMH_post_fountain_withdraw (const struct TMH_RequestHandler *rh, 1204 struct MHD_Connection *connection, 1205 struct TMH_HandlerContext *hc) 1206 { 1207 struct WithdrawContext *wc = hc->ctx; 1208 1209 GNUNET_assert (NULL != hc->instance); 1210 if (NULL == wc) 1211 { 1212 wc = GNUNET_new (struct WithdrawContext); 1213 wc->connection = connection; 1214 wc->hc = hc; 1215 wc->now = GNUNET_TIME_timestamp_get (); 1216 hc->ctx = wc; 1217 hc->cc = &withdraw_context_cleanup; 1218 } 1219 while (1) 1220 { 1221 enum GNUNET_GenericReturnValue res; 1222 1223 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1224 "Processing /fountain/withdraw in phase %d\n", 1225 (int) wc->phase); 1226 switch (wc->phase) 1227 { 1228 case WP_PARSE_REQUEST: 1229 res = phase_parse_request (wc); 1230 break; 1231 case WP_AUTHENTICATE: 1232 res = phase_authenticate (wc); 1233 break; 1234 case WP_START_TRANSACTION: 1235 res = phase_start_transaction (wc); 1236 break; 1237 case WP_CHECK_REPLAY: 1238 res = phase_check_replay (wc); 1239 break; 1240 case WP_LOAD_GRANTS: 1241 res = phase_load_grants (wc); 1242 break; 1243 case WP_PARSE_ENTRIES: 1244 res = phase_parse_entries (wc); 1245 break; 1246 case WP_RESOLVE_KEYS: 1247 res = phase_resolve_keys (wc); 1248 break; 1249 case WP_CONSUME_QUOTA: 1250 res = phase_consume_quota (wc); 1251 break; 1252 case WP_SIGN_TOKENS: 1253 res = phase_sign_tokens (wc); 1254 break; 1255 case WP_COMMIT_TRANSACTION: 1256 res = phase_commit_transaction (wc); 1257 break; 1258 case WP_SUCCESS_RESPONSE: 1259 res = phase_success_response (wc); 1260 break; 1261 case WP_END_YES: 1262 return MHD_YES; 1263 case WP_END_NO: 1264 return MHD_NO; 1265 default: 1266 GNUNET_assert (0); 1267 return MHD_NO; 1268 } 1269 if (GNUNET_OK != res) 1270 { 1271 /* All phases run synchronously: release the transaction before 1272 returning control to the HTTP server, including on queueing errors. */ 1273 if (wc->transaction_open) 1274 { 1275 TALER_MERCHANTDB_rollback (TMH_db); 1276 wc->transaction_open = false; 1277 } 1278 wc->phase = (GNUNET_NO == res) ? WP_END_YES : WP_END_NO; 1279 } 1280 } 1281 } 1282 1283 1284 /* end of taler-merchant-httpd_post-fountain-withdraw.c */