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