donau-httpd_post-batch-issue-CHARITY_ID.c (19354B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024, 2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file donau-httpd_post-batch-issue-CHARITY_ID.c 18 * @brief Handle request to issue receipts. 19 * @author Lukas Matyja 20 */ 21 #include <donau_config.h> 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_json_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_mhd_lib.h> 28 #include <taler/taler_signatures.h> 29 #include "donau-httpd_post-batch-issue-CHARITY_ID.h" 30 #include "donau-httpd_db.h" 31 #include "donau_json_lib.h" 32 #include "donau-httpd_get-keys.h" 33 #include "donau-database/lookup_charity.h" 34 #include "donau-database/lookup_issued_receipts.h" 35 #include "donau-database/insert_issued_receipt.h" 36 37 /** 38 * Parse a bkp encoded in JSON. 39 * 40 * @param[out] bkp where to return the result 41 * @param bkp_key_obj json to parse 42 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a bkp_key_obj 43 * is malformed. 44 */ 45 static enum GNUNET_GenericReturnValue 46 parse_json_bkp (struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 47 const json_t *bkp_key_obj) 48 { 49 struct GNUNET_JSON_Specification spec[] = { 50 GNUNET_JSON_spec_fixed_auto ("h_donation_unit_pub", 51 &bkp->h_donation_unit_pub), 52 DONAU_JSON_spec_blinded_donation_identifier ("blinded_udi", 53 &bkp->blinded_udi), 54 GNUNET_JSON_spec_end () 55 }; 56 57 if (GNUNET_OK != 58 GNUNET_JSON_parse (bkp_key_obj, 59 spec, 60 NULL, NULL)) 61 { 62 GNUNET_break_op (0); 63 return GNUNET_SYSERR; 64 } 65 return GNUNET_OK; 66 } 67 68 69 /** 70 * Free @a bkps array. 71 * 72 * @param num_bkps length of the array 73 * @param[in] bkps array to release 74 */ 75 static void 76 free_bkps (size_t num_bkps, 77 struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps) 78 { 79 for (unsigned int i = 0; i<num_bkps; i++) 80 { 81 struct DONAU_BlindedUniqueDonorIdentifier *budi = &bkps[i].blinded_udi; 82 83 if (NULL != budi->blinded_message) 84 GNUNET_CRYPTO_blinded_message_decref (budi->blinded_message); 85 } 86 GNUNET_free (bkps); 87 } 88 89 90 /** 91 * Parse signatures to JSON. 92 * 93 * @param num_sig number of signatures 94 * @param signatures Blinded donation unit signatures 95 * @param[out] j_signatures JSON object 96 */ 97 static void 98 signatures_to_json (const size_t num_sig, 99 struct DONAU_BlindedDonationUnitSignature *signatures, 100 json_t *j_signatures) 101 { 102 for (size_t i = 0; i < num_sig; i++) 103 { 104 struct DONAU_BlindedDonationUnitSignature *signature = &signatures[i]; 105 106 GNUNET_assert ( 107 0 == json_array_append_new ( 108 j_signatures, 109 GNUNET_JSON_PACK ( 110 DONAU_JSON_pack_blinded_donation_unit_sig ("blinded_signature", 111 signature)))); 112 } 113 } 114 115 116 enum MHD_Result 117 DH_handler_post_batch_issue (struct DH_RequestContext *rc, 118 const json_t *root, 119 const char *const args[1]) 120 { 121 unsigned int current_year = GNUNET_TIME_get_current_year (); 122 struct DONAU_CharitySignatureP charity_sig; 123 uint32_t year; 124 bool second_time = false; 125 unsigned long long charity_id; 126 char dummy; 127 const json_t *budikeypairs; 128 size_t num_bkps; 129 struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps; 130 struct DONAUDB_CharityMetaData charity_meta; 131 json_t *blind_signatures; 132 struct DONAU_DonationReceiptHashP h_receipts; 133 struct TALER_Amount receipts_sum; 134 135 if ( (NULL == args[0]) || 136 (1 != sscanf (args[0], 137 "%llu%c", 138 &charity_id, 139 &dummy)) ) 140 { 141 GNUNET_break_op (0); 142 return TALER_MHD_reply_with_error (rc->connection, 143 MHD_HTTP_BAD_REQUEST, 144 TALER_EC_GENERIC_PARAMETER_MALFORMED, 145 "charity_id"); 146 } 147 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 148 "issue receipts for charity id: %llu\n", 149 charity_id); 150 151 { 152 struct GNUNET_JSON_Specification spec[] = { 153 GNUNET_JSON_spec_array_const ("budikeypairs", 154 &budikeypairs), 155 GNUNET_JSON_spec_fixed_auto ("charity_sig", 156 &charity_sig), 157 GNUNET_JSON_spec_uint32 ("year", 158 &year), 159 GNUNET_JSON_spec_end () 160 }; 161 enum GNUNET_GenericReturnValue res; 162 163 res = TALER_MHD_parse_json_data (rc->connection, 164 root, 165 spec); 166 if (GNUNET_SYSERR == res) 167 return MHD_NO; /* hard failure */ 168 if (GNUNET_NO == res) 169 { 170 GNUNET_break_op (0); 171 return MHD_YES; /* failure */ 172 } 173 } 174 175 /* parse the budikeypairs array */ 176 num_bkps = json_array_size (budikeypairs); 177 /* Bound the request-controlled count: du_sigs[]/bkps_sign_data[] below are 178 stack-allocated VLAs of this size. */ 179 if ( (0 == num_bkps) || 180 (num_bkps > 1024) ) 181 { 182 GNUNET_break_op (0); 183 return TALER_MHD_reply_with_error (rc->connection, 184 MHD_HTTP_BAD_REQUEST, 185 TALER_EC_GENERIC_PARAMETER_MALFORMED, 186 "budikeypairs"); 187 188 } 189 190 bkps = GNUNET_new_array (num_bkps, 191 struct DONAU_BlindedUniqueDonorIdentifierKeyPair); 192 { 193 json_t *bkp_obj; 194 size_t index; 195 196 json_array_foreach (budikeypairs, 197 index, 198 bkp_obj) 199 { 200 if (GNUNET_SYSERR == 201 parse_json_bkp (&bkps[index], 202 bkp_obj)) 203 { 204 GNUNET_break_op (0); 205 free_bkps (num_bkps, 206 bkps); 207 return TALER_MHD_reply_with_error (rc->connection, 208 MHD_HTTP_BAD_REQUEST, 209 TALER_EC_GENERIC_PARAMETER_MALFORMED, 210 "budikeypairs"); 211 } 212 } 213 } 214 215 /* Reject duplicate blinded UDIs within the request. */ 216 for (size_t i = 0; i < num_bkps; i++) 217 for (size_t j = i + 1; j < num_bkps; j++) 218 if (0 == GNUNET_CRYPTO_blinded_message_cmp ( 219 bkps[i].blinded_udi.blinded_message, 220 bkps[j].blinded_udi.blinded_message)) 221 { 222 GNUNET_break_op (0); 223 free_bkps (num_bkps, 224 bkps); 225 return TALER_MHD_reply_with_error (rc->connection, 226 MHD_HTTP_BAD_REQUEST, 227 TALER_EC_GENERIC_PARAMETER_MALFORMED, 228 "budikeypairs"); 229 } 230 231 { 232 enum GNUNET_DB_QueryStatus qs_charity; 233 234 qs_charity = DONAUDB_lookup_charity (DH_context, 235 charity_id, 236 &charity_meta); 237 switch (qs_charity) 238 { 239 case GNUNET_DB_STATUS_HARD_ERROR: 240 case GNUNET_DB_STATUS_SOFT_ERROR: 241 GNUNET_break_op (0); 242 free_bkps (num_bkps, 243 bkps); 244 return TALER_MHD_reply_with_error (rc->connection, 245 MHD_HTTP_INTERNAL_SERVER_ERROR, 246 TALER_EC_GENERIC_DB_FETCH_FAILED, 247 NULL); 248 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 249 GNUNET_break_op (0); 250 free_bkps (num_bkps, 251 bkps); 252 return TALER_MHD_reply_with_error ( 253 rc->connection, 254 MHD_HTTP_NOT_FOUND, 255 TALER_EC_DONAU_CHARITY_NOT_FOUND, 256 NULL); 257 break; 258 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 259 break; 260 } 261 } 262 263 /* verify charity signature */ 264 if (GNUNET_OK != 265 DONAU_charity_bkp_verify (num_bkps, 266 bkps, 267 &charity_meta.charity_pub, 268 &charity_sig)) 269 { 270 GNUNET_break_op (0); 271 free_bkps (num_bkps, 272 bkps); 273 GNUNET_free (charity_meta.charity_name); 274 GNUNET_free (charity_meta.charity_url); 275 return TALER_MHD_reply_with_error ( 276 rc->connection, 277 MHD_HTTP_FORBIDDEN, 278 TALER_EC_DONAU_CHARITY_SIGNATURE_INVALID, 279 "charity_sig"); 280 } 281 282 GNUNET_free (charity_meta.charity_name); 283 GNUNET_free (charity_meta.charity_url); 284 { 285 /* request already made? -> idempotent */ 286 enum GNUNET_DB_QueryStatus qs_check_receipts; 287 struct DONAUDB_IssuedReceiptsMetaData check_receipts_meta; 288 struct GNUNET_HashContext *hc; 289 290 blind_signatures = json_array (); 291 GNUNET_assert (NULL != blind_signatures); 292 hc = GNUNET_CRYPTO_hash_context_start (); 293 for (size_t i = 0; i < num_bkps; i++) 294 { 295 const struct GNUNET_CRYPTO_BlindedMessage *bm 296 = bkps[i].blinded_udi.blinded_message; 297 298 GNUNET_CRYPTO_hash_context_read (hc, 299 &bkps[i].h_donation_unit_pub, 300 sizeof (bkps[i].h_donation_unit_pub)); 301 switch (bm->cipher) 302 { 303 case GNUNET_CRYPTO_BSA_INVALID: 304 GNUNET_assert (0); 305 break; 306 case GNUNET_CRYPTO_BSA_CS: 307 GNUNET_CRYPTO_hash_context_read ( 308 hc, 309 &bm->details.cs_blinded_message, 310 sizeof (bm->details.cs_blinded_message)); 311 break; 312 case GNUNET_CRYPTO_BSA_RSA: 313 GNUNET_CRYPTO_hash_context_read ( 314 hc, 315 bm->details.rsa_blinded_message.blinded_msg, 316 bm->details.rsa_blinded_message.blinded_msg_size); 317 break; 318 } 319 } 320 GNUNET_CRYPTO_hash_context_read (hc, 321 &charity_sig, 322 sizeof (struct DONAU_CharitySignatureP)); 323 { 324 uint32_t year_be = htonl (year); 325 326 GNUNET_CRYPTO_hash_context_read (hc, 327 &year_be, 328 sizeof (uint32_t)); 329 } 330 GNUNET_CRYPTO_hash_context_finish (hc, 331 &h_receipts.hash); 332 333 start: 334 qs_check_receipts 335 = DONAUDB_lookup_issued_receipts (DH_context, 336 &h_receipts, 337 &check_receipts_meta); 338 switch (qs_check_receipts) 339 { 340 case GNUNET_DB_STATUS_HARD_ERROR: 341 case GNUNET_DB_STATUS_SOFT_ERROR: 342 GNUNET_break (0); 343 free_bkps (num_bkps, 344 bkps); 345 json_decref (blind_signatures); 346 return TALER_MHD_reply_with_error (rc->connection, 347 MHD_HTTP_INTERNAL_SERVER_ERROR, 348 TALER_EC_GENERIC_DB_FETCH_FAILED, 349 NULL); 350 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 351 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 352 "request has not been made yet (first time)!\n"); 353 break; /* it's the first request from the charity, we can proceed */ 354 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 355 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 356 "request has been made already!\n"); 357 signatures_to_json (check_receipts_meta.num_sig, 358 check_receipts_meta.blinded_sigs, 359 blind_signatures); 360 for (size_t i = 0; i < check_receipts_meta.num_sig; i++) 361 { 362 GNUNET_CRYPTO_blinded_sig_decref ( 363 check_receipts_meta.blinded_sigs[i].blinded_sig); 364 } 365 GNUNET_free (check_receipts_meta.blinded_sigs); 366 free_bkps (num_bkps, 367 bkps); 368 return TALER_MHD_REPLY_JSON_PACK ( 369 rc->connection, 370 MHD_HTTP_OK, 371 TALER_JSON_pack_amount ("issued_amount", 372 &check_receipts_meta.amount), 373 GNUNET_JSON_pack_array_steal ("blind_signatures", 374 blind_signatures)); 375 } 376 } 377 378 /* calculate the sum of all receipts */ 379 380 GNUNET_assert (GNUNET_OK == 381 TALER_amount_set_zero (DH_currency, 382 &receipts_sum)); 383 for (size_t i = 0; i < num_bkps; i++) 384 { 385 struct DH_DonationUnitKey *dk; 386 387 if (NULL == (dk = DH_keys_donation_unit_by_hash ( 388 &bkps[i].h_donation_unit_pub))) 389 { 390 GNUNET_break_op (0); 391 free_bkps (num_bkps, 392 bkps); 393 json_decref (blind_signatures); 394 return TALER_MHD_reply_with_error (rc->connection, 395 MHD_HTTP_NOT_FOUND, 396 TALER_EC_DONAU_GENERIC_DONATION_UNIT_UNKNOWN, 397 NULL); 398 } 399 if (dk->validity_year != year) 400 { 401 char year_str[20]; 402 403 GNUNET_break_op (0); 404 free_bkps (num_bkps, 405 bkps); 406 GNUNET_snprintf (year_str, 407 sizeof (year_str), 408 "%llu", 409 (unsigned long long) dk->validity_year); 410 json_decref (blind_signatures); 411 return TALER_MHD_reply_with_error (rc->connection, 412 MHD_HTTP_CONFLICT, 413 TALER_EC_DONAU_GENERIC_DONATION_UNIT_WRONG_YEAR, 414 year_str); 415 } 416 if (dk->validity_year < current_year) 417 { 418 char year_str[20]; 419 420 GNUNET_break_op (0); 421 free_bkps (num_bkps, 422 bkps); 423 GNUNET_snprintf (year_str, 424 sizeof (year_str), 425 "%llu", 426 (unsigned long long) dk->validity_year); 427 json_decref (blind_signatures); 428 return TALER_MHD_reply_with_error (rc->connection, 429 MHD_HTTP_GONE, 430 TALER_EC_DONAU_GENERIC_DONATION_UNIT_EXPIRED, 431 year_str); 432 } 433 if (dk->validity_year > current_year) 434 { 435 char year_str[20]; 436 437 GNUNET_break_op (0); 438 GNUNET_snprintf (year_str, 439 sizeof (year_str), 440 "%llu", 441 (unsigned long long) dk->validity_year); 442 free_bkps (num_bkps, 443 bkps); 444 json_decref (blind_signatures); 445 return TALER_MHD_reply_with_error (rc->connection, 446 MHD_HTTP_TOO_EARLY, 447 TALER_EC_DONAU_GENERIC_DONATION_UNIT_TOO_EARLY, 448 year_str); 449 } 450 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 451 "public key value: %s\n", 452 TALER_amount2s (&dk->value)); 453 GNUNET_assert (0 <= TALER_amount_add (&receipts_sum, 454 &receipts_sum, 455 &dk->value)); 456 } 457 458 /* sign budis and send the signatures back */ 459 { 460 struct DONAU_BlindedDonationUnitSignature du_sigs[num_bkps]; 461 struct DONAU_BkpSignData bkps_sign_data[num_bkps]; 462 enum TALER_ErrorCode batch_sign_ec; 463 enum GNUNET_DB_QueryStatus qs_insert_ir; 464 bool smaller_than_max_per_year = false; 465 466 for (size_t i = 0; i < num_bkps; i++) 467 { 468 bkps_sign_data[i].h_donation_unit_pub = &bkps[i].h_donation_unit_pub; 469 bkps_sign_data[i].budi = &bkps[i].blinded_udi; 470 } 471 batch_sign_ec = DH_keys_donation_unit_batch_sign (num_bkps, 472 bkps_sign_data, 473 du_sigs); 474 if (TALER_EC_NONE != batch_sign_ec) 475 { 476 GNUNET_break_op (0); 477 free_bkps (num_bkps, 478 bkps); 479 json_decref (blind_signatures); 480 return TALER_MHD_reply_with_ec (rc->connection, 481 batch_sign_ec, 482 NULL); 483 } 484 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 485 "made blind signatures!\n"); 486 487 /* save new receipts to date and save receipts Request (blinded signatures, 488 * charity id, amount, hash over bkps) to make it idempotent*/ 489 qs_insert_ir = DONAUDB_insert_issued_receipt (DH_context, 490 year, 491 num_bkps, 492 du_sigs, 493 (uint64_t) charity_id, 494 &h_receipts, 495 &receipts_sum, 496 &smaller_than_max_per_year); 497 switch (qs_insert_ir) 498 { 499 case GNUNET_DB_STATUS_HARD_ERROR: 500 case GNUNET_DB_STATUS_SOFT_ERROR: 501 GNUNET_break (0); 502 free_bkps (num_bkps, 503 bkps); 504 json_decref (blind_signatures); 505 for (unsigned int i = 0; i<num_bkps; i++) 506 GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig); 507 return TALER_MHD_reply_with_error (rc->connection, 508 MHD_HTTP_INTERNAL_SERVER_ERROR, 509 TALER_EC_GENERIC_DB_FETCH_FAILED, 510 NULL); 511 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 512 GNUNET_assert (! second_time); 513 second_time = true; 514 for (unsigned int i = 0; i<num_bkps; i++) 515 GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig); 516 goto start; 517 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 518 if (! smaller_than_max_per_year) 519 { 520 GNUNET_break_op (0); 521 free_bkps (num_bkps, 522 bkps); 523 json_decref (blind_signatures); 524 for (unsigned int i = 0; i<num_bkps; i++) 525 GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig); 526 return TALER_MHD_reply_with_error (rc->connection, 527 MHD_HTTP_BAD_REQUEST, 528 TALER_EC_DONAU_EXCEEDING_DONATION_LIMIT, 529 NULL); 530 } 531 break; 532 } 533 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 534 "issue receipts request is saved! (idempotent)\n"); 535 536 signatures_to_json (num_bkps, 537 du_sigs, 538 blind_signatures); 539 for (unsigned int i = 0; i<num_bkps; i++) 540 GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig); 541 } 542 free_bkps (num_bkps, 543 bkps); 544 return TALER_MHD_REPLY_JSON_PACK ( 545 rc->connection, 546 MHD_HTTP_OK, 547 TALER_JSON_pack_amount ("issued_amount", 548 &receipts_sum), 549 GNUNET_JSON_pack_array_steal ("blind_signatures", 550 blind_signatures)); 551 } 552 553 554 /* end of donau-httpd_post-batch-issue-CHARITY_ID.c */