testing_api_cmd_fountain_withdraw.c (18673B)
1 /* 2 This file is part of TALER 3 Copyright (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 General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (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, see 17 <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file src/testing/testing_api_cmd_fountain_withdraw.c 22 * @brief command simulating a wallet withdrawing tokens from a 23 * fountain (DD 98): fetches GET /fountain/info, prepares 24 * blinded envelopes, runs POST /fountain/withdraw and 25 * unblinds and verifies the resulting tokens 26 * @author Bohdan Potuzhnyi 27 */ 28 #include "platform.h" 29 struct FountainWithdrawState; 30 #define TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE \ 31 struct FountainWithdrawState 32 #define TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE \ 33 struct FountainWithdrawState 34 #include <gnunet/gnunet_time_lib.h> 35 #include <taler/taler_exchange_service.h> 36 #include <taler/taler_testing_lib.h> 37 #include "taler/taler_merchant_service.h" 38 #include "taler/taler_merchant_testing_lib.h" 39 #include <taler/merchant/get-fountain-info.h> 40 #include <taler/merchant/post-fountain-withdraw.h> 41 42 43 /** 44 * Client-side state of one token being withdrawn. 45 */ 46 struct FountainToken 47 { 48 49 /** 50 * Master secret used to derive the private key from. 51 */ 52 struct TALER_TokenUseMasterSecretP master; 53 54 /** 55 * Private key of the token. 56 */ 57 struct TALER_TokenUsePrivateKeyP token_priv; 58 59 /** 60 * Public key of the token. 61 */ 62 struct TALER_TokenUsePublicKeyP token_pub; 63 64 /** 65 * Hash of the public key of the token. 66 */ 67 struct TALER_TokenUsePublicKeyHashP h_token_pub; 68 69 /** 70 * Blinded public key of the token. 71 */ 72 struct TALER_TokenEnvelope envelope; 73 74 /** 75 * Value used to blind the key for the signature. 76 */ 77 union GNUNET_CRYPTO_BlindingSecretP blinding_secret; 78 79 /** 80 * Inputs needed from the merchant for blind signing. 81 */ 82 struct TALER_TokenUseMerchantValues blinding_inputs; 83 84 /** 85 * Token issue public key. 86 */ 87 struct TALER_TokenIssuePublicKey issue_pub; 88 89 /** 90 * Unblinded token issue signature made by the merchant. 91 */ 92 struct TALER_TokenIssueSignature issue_sig; 93 94 }; 95 96 97 /** 98 * State of a fountain withdraw CMD. 99 */ 100 struct FountainWithdrawState 101 { 102 103 /** 104 * Expected status code of the withdraw request. 105 */ 106 unsigned int http_status; 107 108 /** 109 * Handle for the "GET /fountain/info" request. 110 */ 111 struct TALER_MERCHANT_GetFountainInfoHandle *info_handle; 112 113 /** 114 * Handle for the "POST /fountain/withdraw" request. 115 */ 116 struct TALER_MERCHANT_PostFountainWithdrawHandle *withdraw_handle; 117 118 /** 119 * The interpreter state. 120 */ 121 struct TALER_TESTING_Interpreter *is; 122 123 /** 124 * Base URL of the merchant serving the request. 125 */ 126 const char *merchant_url; 127 128 /** 129 * Label of the command holding the fountain secret. 130 */ 131 const char *fountain_reference; 132 133 /** 134 * Slug of the token family to withdraw from. 135 */ 136 const char *token_family_slug; 137 138 /** 139 * Desired token validity time; zero for "now". 140 */ 141 struct GNUNET_TIME_Timestamp valid_at; 142 143 /** 144 * The fountain's bearer credential (from the referenced command). 145 */ 146 const char *fountain_secret; 147 148 /** 149 * Tokens being withdrawn. 150 */ 151 struct FountainToken *tokens; 152 153 /** 154 * Number of tokens in @e tokens. 155 */ 156 unsigned int num_tokens; 157 158 /** 159 * First successful response, retained to check an identical retry. 160 */ 161 json_t *first_reply; 162 }; 163 164 165 /** 166 * Submit the prepared envelopes, including when replaying a request. 167 * 168 * @param state our command state 169 */ 170 static void 171 start_withdraw (struct FountainWithdrawState *state); 172 173 174 /** 175 * Callback for the POST /fountain/withdraw operation: unblinds and 176 * verifies the tokens on success. 177 * 178 * @param state our command state 179 * @param fwr response being processed 180 */ 181 static void 182 withdraw_cb (struct FountainWithdrawState *state, 183 const struct TALER_MERCHANT_PostFountainWithdrawResponse *fwr) 184 { 185 state->withdraw_handle = NULL; 186 if (state->http_status != fwr->hr.http_status) 187 { 188 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 189 "Unexpected response code %u (%d) to command %s\n", 190 fwr->hr.http_status, 191 (int) fwr->hr.ec, 192 TALER_TESTING_interpreter_get_current_label (state->is)); 193 TALER_TESTING_interpreter_fail (state->is); 194 return; 195 } 196 if (MHD_HTTP_OK == fwr->hr.http_status) 197 { 198 const struct TALER_MERCHANT_FountainWithdrawResult *res; 199 200 if (NULL == state->first_reply) 201 { 202 state->first_reply = json_incref ((json_t *) fwr->hr.reply); 203 start_withdraw (state); 204 return; 205 } 206 if (! json_equal (state->first_reply, 207 fwr->hr.reply)) 208 { 209 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 210 "Fountain retry returned different signatures\n"); 211 TALER_TESTING_interpreter_fail (state->is); 212 return; 213 } 214 if (1 != fwr->details.ok.results_len) 215 { 216 GNUNET_break (0); 217 TALER_TESTING_interpreter_fail (state->is); 218 return; 219 } 220 res = &fwr->details.ok.results[0]; 221 /* The reported issue key must be the one we blinded against; 222 otherwise the signatures cannot verify. */ 223 if (0 != GNUNET_memcmp (&res->h_issue.hash, 224 &state->tokens[0].issue_pub.public_key-> 225 pub_key_hash)) 226 { 227 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 228 "Merchant signed with issue key %s, but we blinded against %s\n", 229 GNUNET_h2s (&res->h_issue.hash), 230 GNUNET_h2s2 (&state->tokens[0].issue_pub.public_key-> 231 pub_key_hash)); 232 GNUNET_break (0); 233 TALER_TESTING_interpreter_fail (state->is); 234 return; 235 } 236 if (res->num_sigs != state->num_tokens) 237 { 238 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 239 "Sent %u envelopes but got %u blind signatures\n", 240 state->num_tokens, 241 res->num_sigs); 242 TALER_TESTING_interpreter_fail (state->is); 243 return; 244 } 245 for (unsigned int i = 0; i < state->num_tokens; i++) 246 { 247 struct FountainToken *token = &state->tokens[i]; 248 249 /* The signatures are in the same order as the envelopes. */ 250 if (GNUNET_OK != 251 TALER_token_issue_sig_unblind (&token->issue_sig, 252 &res->token_sigs[i], 253 &token->blinding_secret, 254 &token->h_token_pub, 255 &token->blinding_inputs, 256 &token->issue_pub)) 257 { 258 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 259 "Failed to unblind token signature\n"); 260 GNUNET_break (0); 261 TALER_TESTING_interpreter_fail (state->is); 262 return; 263 } 264 if (GNUNET_OK != 265 TALER_token_issue_verify (&token->token_pub, 266 &token->issue_pub, 267 &token->issue_sig)) 268 { 269 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 270 "Unblinded token signature is invalid\n"); 271 GNUNET_break (0); 272 TALER_TESTING_interpreter_fail (state->is); 273 return; 274 } 275 } 276 } 277 TALER_TESTING_interpreter_next (state->is); 278 } 279 280 281 static void 282 start_withdraw (struct FountainWithdrawState *state) 283 { 284 struct TALER_TokenEnvelope envelopes[state->num_tokens]; 285 struct TALER_MERCHANT_FountainWithdrawEntry entry = { 286 .token_family_slug = state->token_family_slug, 287 .valid_at = state->valid_at, 288 .num_envelopes = state->num_tokens, 289 .envelopes = envelopes 290 }; 291 enum TALER_ErrorCode ec; 292 293 for (unsigned int i = 0; i < state->num_tokens; i++) 294 envelopes[i] = state->tokens[i].envelope; 295 state->withdraw_handle = TALER_MERCHANT_post_fountain_withdraw_create ( 296 TALER_TESTING_interpreter_get_context (state->is), 297 state->merchant_url, 298 state->fountain_secret, 299 1, 300 &entry); 301 ec = TALER_MERCHANT_post_fountain_withdraw_start ( 302 state->withdraw_handle, 303 &withdraw_cb, 304 state); 305 GNUNET_assert (TALER_EC_NONE == ec); 306 } 307 308 309 /** 310 * Callback for the GET /fountain/info operation: selects the issue 311 * key, blinds the token envelopes and starts the withdraw request. 312 * 313 * @param state our command state 314 * @param fir response being processed 315 */ 316 static void 317 info_cb (struct FountainWithdrawState *state, 318 const struct TALER_MERCHANT_GetFountainInfoResponse *fir) 319 { 320 const struct TALER_MERCHANT_FountainWalletGrant *grant = NULL; 321 const struct TALER_MERCHANT_ContractTokenFamilyKey *key = NULL; 322 struct GNUNET_TIME_Timestamp valid_at = state->valid_at; 323 324 state->info_handle = NULL; 325 if (MHD_HTTP_OK != fir->hr.http_status) 326 { 327 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 328 "GET /fountain/info failed with response code %u (%d) in command %s\n", 329 fir->hr.http_status, 330 (int) fir->hr.ec, 331 TALER_TESTING_interpreter_get_current_label (state->is)); 332 TALER_TESTING_interpreter_fail (state->is); 333 return; 334 } 335 if (GNUNET_TIME_absolute_is_zero (valid_at.abs_time)) 336 valid_at = GNUNET_TIME_timestamp_get (); 337 for (unsigned int i = 0; i < fir->details.ok.grants_len; i++) 338 { 339 if (0 == strcmp (fir->details.ok.grants[i].grant.token_family_slug, 340 state->token_family_slug)) 341 { 342 grant = &fir->details.ok.grants[i]; 343 break; 344 } 345 } 346 if (NULL == grant) 347 { 348 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 349 "No grant for token family %s in fountain info\n", 350 state->token_family_slug); 351 TALER_TESTING_interpreter_fail (state->is); 352 return; 353 } 354 /* An explicit advertised start selects that key, even in an overlap. 355 An omitted valid_at selects the first advertised key. */ 356 if (GNUNET_TIME_absolute_is_zero (state->valid_at.abs_time)) 357 { 358 if (0 != grant->token_family.keys_len) 359 key = &grant->token_family.keys[0]; 360 } 361 else 362 for (unsigned int i = 0; i < grant->token_family.keys_len; i++) 363 if (GNUNET_TIME_timestamp_cmp (grant->token_family.keys[i].valid_after, 364 ==, 365 valid_at)) 366 { 367 key = &grant->token_family.keys[i]; 368 break; 369 } 370 for (unsigned int i = 0; (NULL == key) && (i < grant->token_family.keys_len); i++) 371 { 372 const struct TALER_MERCHANT_ContractTokenFamilyKey *k 373 = &grant->token_family.keys[i]; 374 375 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 376 "Fountain info key %u: %s valid %llu - %llu (want %llu)\n", 377 i, 378 GNUNET_h2s (&k->pub.public_key->pub_key_hash), 379 (unsigned long long) k->valid_after.abs_time.abs_value_us, 380 (unsigned long long) k->valid_before.abs_time.abs_value_us, 381 (unsigned long long) valid_at.abs_time.abs_value_us); 382 if ( (GNUNET_TIME_timestamp_cmp (k->valid_after, 383 <=, 384 valid_at)) && 385 (GNUNET_TIME_timestamp_cmp (valid_at, 386 <=, 387 k->valid_before)) ) 388 { 389 key = k; 390 break; 391 } 392 } 393 if (NULL == key) 394 { 395 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 396 "No issue key covering the desired validity time in fountain info\n"); 397 TALER_TESTING_interpreter_fail (state->is); 398 return; 399 } 400 /* Prepare the blinded envelopes. */ 401 for (unsigned int i = 0; i < state->num_tokens; i++) 402 { 403 struct FountainToken *token = &state->tokens[i]; 404 405 TALER_token_issue_pub_copy (&token->issue_pub, 406 &key->pub); 407 /* Only RSA is supported for now. */ 408 GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == 409 token->issue_pub.public_key->cipher); 410 TALER_token_blind_input_copy (&token->blinding_inputs, 411 TALER_token_blind_input_rsa_singleton ()); 412 TALER_token_use_setup_random (&token->master); 413 TALER_token_use_setup_priv (&token->master, 414 &token->blinding_inputs, 415 &token->token_priv); 416 TALER_token_use_blinding_secret_create (&token->master, 417 &token->blinding_inputs, 418 &token->blinding_secret); 419 GNUNET_CRYPTO_eddsa_key_get_public ( 420 &token->token_priv.private_key, 421 &token->token_pub.public_key); 422 GNUNET_CRYPTO_hash (&token->token_pub.public_key, 423 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), 424 &token->h_token_pub.hash); 425 token->envelope.blinded_pub = 426 GNUNET_CRYPTO_message_blind_to_sign ( 427 token->issue_pub.public_key, 428 &token->blinding_secret, 429 NULL, /* session nonce, only needed for CS */ 430 &token->h_token_pub.hash, 431 sizeof (token->h_token_pub.hash), 432 token->blinding_inputs.blinding_inputs); 433 if (NULL == token->envelope.blinded_pub) 434 { 435 GNUNET_break (0); 436 TALER_TESTING_interpreter_fail (state->is); 437 return; 438 } 439 } 440 start_withdraw (state); 441 } 442 443 444 /** 445 * Run the fountain withdraw CMD. 446 * 447 * @param cls closure. 448 * @param cmd command being run now. 449 * @param is interpreter state. 450 */ 451 static void 452 fountain_withdraw_run (void *cls, 453 const struct TALER_TESTING_Command *cmd, 454 struct TALER_TESTING_Interpreter *is) 455 { 456 struct FountainWithdrawState *state = cls; 457 const struct TALER_TESTING_Command *fountain_cmd; 458 459 state->is = is; 460 fountain_cmd = TALER_TESTING_interpreter_lookup_command ( 461 is, 462 state->fountain_reference); 463 if (NULL == fountain_cmd) 464 { 465 GNUNET_break (0); 466 TALER_TESTING_interpreter_fail (is); 467 return; 468 } 469 if (GNUNET_OK != 470 TALER_TESTING_get_trait_fountain_secret (fountain_cmd, 471 &state->fountain_secret)) 472 { 473 GNUNET_break (0); 474 TALER_TESTING_interpreter_fail (is); 475 return; 476 } 477 state->info_handle = TALER_MERCHANT_get_fountain_info_create ( 478 TALER_TESTING_interpreter_get_context (is), 479 state->merchant_url, 480 state->fountain_secret); 481 { 482 enum TALER_ErrorCode ec; 483 484 ec = TALER_MERCHANT_get_fountain_info_start ( 485 state->info_handle, 486 &info_cb, 487 state); 488 GNUNET_assert (TALER_EC_NONE == ec); 489 } 490 } 491 492 493 /** 494 * Offers information from the fountain withdraw CMD state to other 495 * commands; in particular the withdrawn tokens can be spent by 496 * referencing this command from a pay command's token reference. 497 * 498 * @param cls closure 499 * @param[out] ret result (could be anything) 500 * @param trait name of the trait 501 * @param index index number of the object to extract. 502 * @return #GNUNET_OK on success 503 */ 504 static enum GNUNET_GenericReturnValue 505 fountain_withdraw_traits (void *cls, 506 const void **ret, 507 const char *trait, 508 unsigned int index) 509 { 510 struct FountainWithdrawState *state = cls; 511 512 if (index >= state->num_tokens) 513 return GNUNET_SYSERR; 514 { 515 struct TALER_TESTING_Trait traits[] = { 516 TALER_TESTING_make_trait_token_priv ( 517 index, 518 &state->tokens[index].token_priv), 519 TALER_TESTING_make_trait_token_issue_pub ( 520 index, 521 &state->tokens[index].issue_pub), 522 TALER_TESTING_make_trait_token_issue_sig ( 523 index, 524 &state->tokens[index].issue_sig), 525 TALER_TESTING_trait_end () 526 }; 527 528 return TALER_TESTING_get_trait (traits, 529 ret, 530 trait, 531 index); 532 } 533 } 534 535 536 /** 537 * Free the state of a fountain withdraw CMD, and possibly cancel 538 * pending operations thereof. 539 * 540 * @param cls closure. 541 * @param cmd command being run. 542 */ 543 static void 544 fountain_withdraw_cleanup (void *cls, 545 const struct TALER_TESTING_Command *cmd) 546 { 547 struct FountainWithdrawState *state = cls; 548 549 if (NULL != state->info_handle) 550 { 551 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 552 "GET /fountain/info operation did not complete\n"); 553 TALER_MERCHANT_get_fountain_info_cancel (state->info_handle); 554 } 555 if (NULL != state->withdraw_handle) 556 { 557 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 558 "POST /fountain/withdraw operation did not complete\n"); 559 TALER_MERCHANT_post_fountain_withdraw_cancel (state->withdraw_handle); 560 } 561 for (unsigned int i = 0; i < state->num_tokens; i++) 562 { 563 struct FountainToken *token = &state->tokens[i]; 564 565 if (NULL != token->issue_pub.public_key) 566 GNUNET_CRYPTO_blind_sign_pub_decref (token->issue_pub.public_key); 567 if (NULL != token->envelope.blinded_pub) 568 GNUNET_CRYPTO_blinded_message_decref (token->envelope.blinded_pub); 569 if (NULL != token->issue_sig.signature) 570 GNUNET_CRYPTO_unblinded_sig_decref (token->issue_sig.signature); 571 /* Note: blinding_inputs is a plain copy of the RSA singleton 572 (a static without refcount), so it must not be decref'ed. */ 573 } 574 GNUNET_free (state->tokens); 575 json_decref (state->first_reply); 576 GNUNET_free (state); 577 } 578 579 580 struct TALER_TESTING_Command 581 TALER_TESTING_cmd_merchant_fountain_withdraw ( 582 const char *label, 583 const char *merchant_url, 584 unsigned int http_status, 585 const char *fountain_reference, 586 const char *token_family_slug, 587 struct GNUNET_TIME_Timestamp valid_at, 588 unsigned int num_tokens) 589 { 590 struct FountainWithdrawState *state; 591 592 GNUNET_assert (num_tokens > 0); 593 state = GNUNET_new (struct FountainWithdrawState); 594 state->merchant_url = merchant_url; 595 state->http_status = http_status; 596 state->fountain_reference = fountain_reference; 597 state->token_family_slug = token_family_slug; 598 state->valid_at = valid_at; 599 state->num_tokens = num_tokens; 600 state->tokens = GNUNET_new_array (num_tokens, 601 struct FountainToken); 602 { 603 struct TALER_TESTING_Command cmd = { 604 .cls = state, 605 .label = label, 606 .run = &fountain_withdraw_run, 607 .cleanup = &fountain_withdraw_cleanup, 608 .traits = &fountain_withdraw_traits 609 }; 610 611 return cmd; 612 } 613 } 614 615 616 /* end of testing_api_cmd_fountain_withdraw.c */