testing_api_cmd_issue_receipts.c (18693B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 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 * @file testing/testing_api_cmd_issue_receipts.c 21 * @brief Implement the POST /charities test command. 22 * @author Lukas Matyja 23 */ 24 #include <donau_config.h> 25 #include <taler/taler_json_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include <taler/taler_testing_lib.h> 28 struct StatusState; 29 struct CSR_Data; 30 #define DONAU_BATCH_ISSUE_RECEIPTS_RESULT_CLOSURE struct StatusState 31 #define DONAU_CSR_BATCH_ISSUE_RESULT_CLOSURE struct CSR_Data 32 #include "donau_testing_lib.h" 33 34 35 /** 36 * State for a "status" CMD. 37 */ 38 struct StatusState 39 { 40 /** 41 * Handle to the "batch issue receipt status" operation. 42 */ 43 struct DONAU_BatchIssueReceiptHandle *birh; 44 45 /** 46 * Reference to charity post command. 47 */ 48 const char *charity_reference; 49 50 /** 51 * Issue amount 52 */ 53 struct TALER_Amount amount; 54 55 /** 56 * Expected HTTP response code. 57 */ 58 unsigned int expected_response_code; 59 60 /** 61 */ 62 bool uses_cs; 63 64 /** 65 * Interpreter state. 66 */ 67 struct TALER_TESTING_Interpreter *is; 68 69 /** 70 * charity id 71 */ 72 uint64_t charity_id; 73 74 /** 75 * charity id 76 */ 77 uint32_t year; 78 79 /** 80 * Private key of the charity, for signature. 81 */ 82 struct DONAU_CharityPrivateKeyP charity_priv; 83 84 /** 85 * number of budi key pair. 86 */ 87 size_t num_bkp; 88 89 /** 90 * budi key pair array 91 */ 92 struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps; 93 94 /** 95 * donau keys 96 */ 97 struct DONAU_Keys *keys; 98 99 /** 100 * The cleartext tax id of the user used for @h_donor_tax_id. 101 */ 102 const char *donor_tax_id; 103 104 /** 105 * The salt used for @h_donor_tax_id. 106 */ 107 const char *donor_salt; 108 109 /** 110 * Hashed and salted tax id of the donor. 111 */ 112 struct DONAU_HashDonorTaxId h_donor_tax_id; 113 114 /** 115 * Selected donation-unit pubkeys for this request 116 */ 117 struct DONAU_DonationUnitPublicKey *selected_pks; 118 119 /** 120 * Array of donation receipts; 121 */ 122 struct DONAU_DonationReceipt *receipts; 123 124 /** 125 * Blinding secrets 126 */ 127 union GNUNET_CRYPTO_BlindingSecretP *blinding_secrets; 128 129 /** 130 * Blinding values. Cs-nonces, cipher. 131 */ 132 struct DONAU_BatchIssueValues **alg_values; 133 134 /** 135 * Array of hashed udis. 136 */ 137 struct DONAU_UniqueDonorIdentifierHashP *h_udis; 138 139 /** 140 * Number of pending CS requests. 141 */ 142 size_t cs_pending; 143 }; 144 145 146 struct CSR_Data 147 { 148 /** 149 * Handle to the "batch issue receipt status" operation. 150 */ 151 struct DONAU_CsRBatchIssueHandle *csr_handle; 152 153 /** 154 * CS-Nonce 155 */ 156 union GNUNET_CRYPTO_BlindSessionNonce nonce; 157 158 /** 159 * batch issue receipt status state 160 */ 161 struct StatusState *ss; 162 163 /** 164 * array position in batch issue receipt request (first position is zero) 165 */ 166 size_t position; 167 }; 168 169 170 /** 171 * Check that the reserve balance and HTTP response code are 172 * both acceptable. 173 * 174 * @param ss closure. 175 * @param biresp HTTP response details 176 */ 177 static void 178 issue_receipts_status_cb (struct StatusState *ss, 179 const struct DONAU_BatchIssueResponse *biresp) 180 { 181 ss->birh = NULL; 182 if (ss->expected_response_code != biresp->hr.http_status) 183 { 184 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 185 "Unexpected HTTP response code: %d in %s:%u\n", 186 biresp->hr.http_status, 187 __FILE__, 188 __LINE__); 189 json_dumpf (biresp->hr.reply, 190 stderr, 191 0); 192 TALER_TESTING_interpreter_fail (ss->is); 193 return; 194 } 195 { 196 struct DONAU_BlindedDonationUnitSignature *blinded_sigs = 197 biresp->details.ok.blinded_sigs; 198 if (biresp->details.ok.num_blinded_sigs != ss->num_bkp) 199 { 200 GNUNET_break (0); 201 TALER_TESTING_interpreter_fail (ss->is); 202 return; 203 } 204 for (size_t i = 0; i < ss->num_bkp; i++) 205 { 206 struct DONAU_UniqueDonorIdentifierHashP checkudi_hash; 207 208 GNUNET_assert (GNUNET_OK == 209 DONAU_donation_unit_sig_unblind ( 210 &ss->receipts[i].donation_unit_sig, 211 &blinded_sigs[i], 212 &ss->blinding_secrets[i], 213 &ss->h_udis[i], 214 ss->alg_values[i], 215 &ss->selected_pks[i])); 216 217 /* check udi message */ 218 DONAU_unique_donor_id_hash ( 219 &ss->h_donor_tax_id, 220 &ss->receipts[i].nonce, 221 &checkudi_hash); 222 GNUNET_assert (0 == GNUNET_CRYPTO_hash_cmp (&checkudi_hash.hash, 223 &ss->h_udis[i].hash)); 224 /* check signature */ 225 if (GNUNET_OK != 226 DONAU_donation_receipt_verify ( 227 &ss->selected_pks[i], 228 &checkudi_hash, 229 &ss->receipts[i].donation_unit_sig)) 230 { 231 GNUNET_break_op (0); 232 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 233 "Donation receipt signature invalid!\n"); 234 TALER_TESTING_interpreter_fail (ss->is); 235 return; 236 } 237 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 238 "Donation receipt signature valid!\n"); 239 } 240 } 241 TALER_TESTING_interpreter_next (ss->is); 242 } 243 244 245 /** 246 * Runs phase two, the actual issue receipts operation. 247 * Started once the preparation for CS-donation-units is 248 * done. 249 * @param cls closure. 250 */ 251 static void 252 phase_two (void *cls) 253 { 254 struct StatusState *ss = cls; 255 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps = ss->bkps; 256 257 ss->birh = DONAU_charity_issue_receipt ( 258 TALER_TESTING_interpreter_get_context (ss->is), 259 TALER_TESTING_get_donau_url (ss->is), 260 &ss->charity_priv, 261 ss->charity_id, 262 ss->year, 263 ss->num_bkp, 264 bkps, 265 &issue_receipts_status_cb, 266 ss); 267 } 268 269 270 /** 271 * Function called when stage 1 of CS issue is finished (request r_pub's) 272 * 273 * @param csr_data the `struct CSR_Data *` 274 * @param csrresp replies from the /csr-issue request 275 */ 276 static void 277 cs_stage_two_callback ( 278 struct CSR_Data *csr_data, 279 const struct DONAU_CsRBatchIssueResponse *csrresp) 280 { 281 struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi = 282 &csr_data->ss->bkps[csr_data->position].blinded_udi; 283 284 if (csrresp->hr.http_status != MHD_HTTP_CREATED) 285 { 286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 287 "Unexpected HTTP response code: %d in %s:%u\n", 288 csrresp->hr.http_status, 289 __FILE__, 290 __LINE__); 291 json_dumpf (csrresp->hr.reply, 292 stderr, 293 0); 294 TALER_TESTING_interpreter_fail (csr_data->ss->is); 295 GNUNET_free (csr_data); 296 return; 297 } 298 299 { 300 struct DONAU_DonationUnitPublicKey *cs_pk = 301 &csr_data->ss->selected_pks[csr_data->position]; 302 struct DONAU_BatchIssueValues *alg_values 303 = GNUNET_new (struct DONAU_BatchIssueValues); 304 struct DONAU_BudiMasterSecretP ps; 305 struct DONAU_UniqueDonorIdentifierHashP *udi_hash = 306 &csr_data->ss->h_udis[csr_data->position]; 307 union GNUNET_CRYPTO_BlindingSecretP *blinding_secret = 308 &csr_data->ss->blinding_secrets[csr_data->position]; 309 struct DONAU_UniqueDonorIdentifierNonce *udi_nonce = 310 &csr_data->ss->receipts[csr_data->position].nonce; 311 312 GNUNET_assert (GNUNET_CRYPTO_BSA_CS == 313 cs_pk->bsign_pub_key->cipher); 314 315 DONAU_donation_unit_ewv_copy ( 316 alg_values, 317 &csrresp->details.ok.alg_values); 318 GNUNET_CRYPTO_random_block (&ps, 319 sizeof (ps)); 320 DONAU_budi_secret_create (&ps, 321 alg_values, 322 blinding_secret); 323 GNUNET_assert (GNUNET_OK == 324 DONAU_donation_unit_blind ( 325 cs_pk, 326 blinding_secret, 327 &csr_data->nonce, /* nonce only needed for cs */ 328 udi_nonce, 329 &csr_data->ss->h_donor_tax_id, 330 alg_values, 331 udi_hash, 332 blinded_udi)); 333 csr_data->ss->alg_values[csr_data->position] = alg_values; 334 csr_data->ss->cs_pending--; 335 } 336 { 337 struct StatusState *ss = csr_data->ss; 338 339 GNUNET_free (csr_data); 340 if (0 == ss->cs_pending) 341 phase_two (ss); 342 } 343 } 344 345 346 /** 347 * Run the command. 348 * 349 * @param cls closure. 350 * @param cmd the command being executed. 351 * @param is the interpreter state. 352 */ 353 static void 354 status_run (void *cls, 355 const struct TALER_TESTING_Command *cmd, 356 struct TALER_TESTING_Interpreter *is) 357 { 358 struct StatusState *ss = cls; 359 360 (void) cmd; 361 ss->is = is; 362 363 /* Get charity id and the charity private key from trait */ 364 { 365 const struct TALER_TESTING_Command *charity_post_cmd; 366 const uint64_t *charity_id; 367 const struct DONAU_CharityPrivateKeyP *charity_priv; 368 369 370 charity_post_cmd = TALER_TESTING_interpreter_lookup_command (is, 371 ss-> 372 charity_reference); 373 374 if ( (GNUNET_OK != 375 TALER_TESTING_get_trait_charity_id (charity_post_cmd, 376 &charity_id) ) || 377 (GNUNET_OK != 378 TALER_TESTING_get_trait_charity_priv (charity_post_cmd, 379 &charity_priv)) ) 380 { 381 GNUNET_break (0); 382 TALER_TESTING_interpreter_fail (is); 383 return; 384 } 385 ss->charity_id = (uint64_t) *(charity_id); 386 ss->charity_priv = *(charity_priv); 387 } 388 389 /* Get donau keys from trait */ 390 { 391 const struct TALER_TESTING_Command *keys_cmd; 392 struct DONAU_Keys *keys; 393 394 keys_cmd = TALER_TESTING_interpreter_lookup_command (is, 395 "get-donau"); 396 397 if (GNUNET_OK != 398 TALER_TESTING_get_trait_donau_keys (keys_cmd, &keys)) 399 { 400 GNUNET_break (0); 401 TALER_TESTING_interpreter_fail (is); 402 return; 403 } 404 ss->keys = keys; 405 } 406 407 /* Get the donation_unit_keys for requested amount */ 408 { 409 enum GNUNET_GenericReturnValue sret; 410 411 sret = DONAU_select_donation_unit_keys_for_amount ( 412 ss->keys, 413 &ss->amount, 414 ss->year, 415 &ss->selected_pks, 416 &ss->num_bkp); 417 418 if (GNUNET_SYSERR == sret) 419 { 420 GNUNET_break (0); 421 TALER_TESTING_interpreter_fail (is); 422 return; 423 } 424 if ((GNUNET_NO == sret) || (0 == ss->num_bkp)) 425 { 426 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 427 "Could not compose exact amount %s from donation units for %u\n", 428 TALER_amount2s (&ss->amount), 429 (unsigned int) ss->year); 430 TALER_TESTING_interpreter_fail (is); 431 return; 432 } 433 } 434 435 ss->bkps = 436 GNUNET_new_array (ss->num_bkp, 437 struct DONAU_BlindedUniqueDonorIdentifierKeyPair); 438 ss->blinding_secrets = 439 GNUNET_new_array (ss->num_bkp, 440 union GNUNET_CRYPTO_BlindingSecretP); 441 ss->receipts = 442 GNUNET_new_array (ss->num_bkp, 443 struct DONAU_DonationReceipt); 444 ss->alg_values = 445 GNUNET_new_array (ss->num_bkp, 446 struct DONAU_BatchIssueValues *); 447 ss->h_udis = 448 GNUNET_new_array (ss->num_bkp, 449 struct DONAU_UniqueDonorIdentifierHashP); 450 for (size_t cnt = 0; cnt < ss->num_bkp; cnt++) 451 { 452 struct DONAU_UniqueDonorIdentifierNonce *udi_nonce 453 = &ss->receipts[cnt].nonce; 454 struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi 455 = &ss->bkps[cnt].blinded_udi; 456 struct DONAU_UniqueDonorIdentifierHashP *udi_hash 457 = &ss->h_udis[cnt]; 458 struct DONAU_BudiMasterSecretP ps; 459 struct DONAU_BatchIssueValues *alg_values; 460 461 DONAU_donation_unit_pub_hash (&ss->selected_pks[cnt], 462 &ss->bkps[cnt].h_donation_unit_pub); 463 ss->receipts[cnt].h_donation_unit_pub = ss->bkps[cnt].h_donation_unit_pub; 464 GNUNET_CRYPTO_random_block (&ps, 465 sizeof (ps)); 466 GNUNET_CRYPTO_random_block (udi_nonce, 467 sizeof (*udi_nonce)); 468 switch (ss->selected_pks[cnt].bsign_pub_key->cipher) 469 { 470 case GNUNET_CRYPTO_BSA_RSA: 471 alg_values = DONAU_donation_unit_ewv_rsa_singleton (); 472 DONAU_budi_secret_create (&ps, 473 alg_values, 474 &ss->blinding_secrets[cnt]); 475 GNUNET_assert (GNUNET_OK == 476 DONAU_donation_unit_blind ( 477 &ss->selected_pks[cnt], 478 &ss->blinding_secrets[cnt], 479 NULL, /* no cs-nonce needed for rsa */ 480 udi_nonce, 481 &ss->h_donor_tax_id, 482 alg_values, 483 udi_hash, 484 blinded_udi)); 485 ss->alg_values[cnt] = alg_values; 486 break; 487 case GNUNET_CRYPTO_BSA_CS: 488 { 489 struct CSR_Data *csr_data = GNUNET_new (struct CSR_Data); 490 491 TALER_cs_withdraw_nonce_derive ( // FIXME: write new method 492 (struct TALER_PlanchetMasterSecretP *) &ps, 493 &csr_data->nonce.cs_nonce); 494 csr_data->ss = ss; 495 csr_data->position = cnt; 496 csr_data->csr_handle = DONAU_csr_issue ( 497 TALER_TESTING_interpreter_get_context (is), 498 TALER_TESTING_get_donau_url (is), 499 &ss->selected_pks[cnt], 500 &csr_data->nonce.cs_nonce, 501 &cs_stage_two_callback, 502 csr_data); 503 if (NULL == csr_data->csr_handle) 504 { 505 GNUNET_break (0); 506 } 507 ss->cs_pending++; 508 break; 509 } 510 default: 511 GNUNET_break (0); 512 } 513 } 514 if (0 == ss->cs_pending) 515 phase_two (ss); 516 } 517 518 519 /** 520 * Cleanup the state from a "issue receipt status" CMD, and possibly 521 * cancel a pending operation thereof. 522 * 523 * @param cls closure. 524 * @param cmd the command which is being cleaned up. 525 */ 526 static void 527 cleanup (void *cls, 528 const struct TALER_TESTING_Command *cmd) 529 { 530 struct StatusState *ss = cls; 531 532 if (NULL != ss->birh) 533 { 534 // log incomplete command 535 TALER_TESTING_command_incomplete (ss->is, 536 cmd->label); 537 DONAU_charity_issue_receipt_cancel (ss->birh); 538 ss->birh = NULL; 539 } 540 541 GNUNET_free (ss->selected_pks); 542 GNUNET_free (ss->h_udis); 543 if (NULL != ss->alg_values) 544 { 545 /* For CS units, cs_stage_two_callback allocated a per-unit 546 DONAU_BatchIssueValues with an incref'd blinding_inputs; for RSA 547 units the element is the shared static singleton (must not be 548 freed), and a still-pending CS unit leaves a NULL slot. */ 549 for (unsigned int i = 0; i < ss->num_bkp; i++) 550 { 551 struct DONAU_BatchIssueValues *av = ss->alg_values[i]; 552 553 if ( (NULL != av) && 554 (av != DONAU_donation_unit_ewv_rsa_singleton ()) ) 555 { 556 GNUNET_CRYPTO_blinding_input_values_decref ( 557 av->blinding_inputs); 558 GNUNET_free (av); 559 } 560 } 561 } 562 GNUNET_free (ss->alg_values); 563 GNUNET_free (ss->blinding_secrets); 564 if (NULL != ss->receipts) 565 { 566 for (unsigned int i = 0; i < ss->num_bkp; i++) 567 GNUNET_CRYPTO_unblinded_sig_decref ( 568 ss->receipts[i].donation_unit_sig.unblinded_sig); 569 GNUNET_free (ss->receipts); 570 } 571 if (NULL != ss->bkps) 572 { 573 for (unsigned int i = 0; i < ss->num_bkp; i++) 574 if (NULL != ss->bkps[i].blinded_udi.blinded_message) 575 GNUNET_CRYPTO_blinded_message_decref ( 576 ss->bkps[i].blinded_udi.blinded_message); 577 GNUNET_free (ss->bkps); 578 } 579 GNUNET_free (ss); 580 } 581 582 583 /** 584 * Offer internal data from a "deposit" CMD, to other commands. 585 * 586 * @param cls closure. 587 * @param[out] ret result. 588 * @param trait name of the trait. 589 * @param index index number of the object to offer. 590 * @return #GNUNET_OK on success. 591 */ 592 static enum GNUNET_GenericReturnValue 593 issue_receipts_traits (void *cls, 594 const void **ret, 595 const char *trait, 596 unsigned int index) 597 { 598 struct StatusState *ss = cls; 599 struct TALER_TESTING_Trait traits[] = { 600 TALER_TESTING_make_trait_donor_salt (ss->donor_salt), 601 TALER_TESTING_make_trait_donor_tax_id (ss->donor_tax_id), 602 TALER_TESTING_make_trait_salted_tax_id_hash ( 603 (const struct DONAU_HashDonorTaxId *) &ss->h_donor_tax_id), 604 TALER_TESTING_make_trait_donation_receipts ( 605 (const struct DONAU_DonationReceipt **) &ss->receipts), 606 TALER_TESTING_make_trait_number_receipts (&ss->num_bkp), 607 TALER_TESTING_trait_end () 608 }; 609 610 return TALER_TESTING_get_trait (traits, 611 ret, 612 trait, 613 index); 614 } 615 616 617 struct TALER_TESTING_Command 618 TALER_TESTING_cmd_issue_receipts (const char *label, 619 const char *charity_reference, 620 bool uses_cs, 621 uint32_t year, 622 const char *donor_tax_id, 623 const char *salt, 624 const char *issue_amount, 625 unsigned int expected_response_code) 626 { 627 struct StatusState *ss; 628 629 ss = GNUNET_new (struct StatusState); 630 ss->year = year; 631 ss->charity_reference = charity_reference; 632 ss->expected_response_code = expected_response_code; 633 ss->uses_cs = uses_cs; 634 ss->donor_salt = (const char*) salt; 635 ss->donor_tax_id = (const char*) donor_tax_id; 636 if (GNUNET_OK != 637 TALER_string_to_amount (issue_amount, 638 &ss->amount)) 639 { 640 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 641 "Failed to parse amount `%s' at %s\n", 642 issue_amount, 643 label); 644 GNUNET_assert (0); 645 } 646 647 if (! DONAU_compute_salted_tax_id_hash (donor_tax_id, 648 salt, 649 ss->h_donor_tax_id.hash)) 650 { 651 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 652 "Hash was not received"); 653 GNUNET_assert (0); 654 } 655 656 { 657 struct TALER_TESTING_Command cmd = { 658 .cls = ss, 659 .label = label, 660 .run = &status_run, 661 .cleanup = &cleanup, 662 .traits = &issue_receipts_traits 663 }; 664 665 return cmd; 666 } 667 }