donau-httpd_batch-issue.c (15659B)
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_batch-issue.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 <pthread.h> 27 #include <taler/taler_json_lib.h> 28 #include <taler/taler_mhd_lib.h> 29 #include <taler/taler_signatures.h> 30 #include "donaudb_plugin.h" 31 #include "donau-httpd_batch-issue.h" 32 #include "donau-httpd_db.h" 33 #include "donau_json_lib.h" 34 #include "donau-httpd_keys.h" 35 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 /* FIXME: Check for duplicate blinded UDIs.*/ 66 return GNUNET_OK; 67 } 68 69 70 /** 71 * Free @a bkps array. 72 * 73 * @param num_bkps length of the array 74 * @param[in] bkps array to release 75 */ 76 static void 77 free_bkps (size_t num_bkps, 78 struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps) 79 { 80 for (unsigned int i = 0; i<num_bkps; i++) 81 { 82 struct DONAU_BlindedUniqueDonorIdentifier *budi = &bkps[i].blinded_udi; 83 84 if (NULL != budi->blinded_message) 85 GNUNET_CRYPTO_blinded_message_decref (budi->blinded_message); 86 } 87 GNUNET_free (bkps); 88 } 89 90 91 /** 92 * Parse signatures to JSON. 93 * 94 * @param num_sig number of signatures 95 * @param signatures Blinded donation unit signatures 96 * @param[out] j_signatures JSON object 97 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if we could not parse 98 * is malformed. 99 */ 100 static void 101 signatures_to_json (const size_t num_sig, 102 struct DONAU_BlindedDonationUnitSignature *signatures, 103 json_t *j_signatures) 104 { 105 for (size_t i = 0; i < num_sig; i++) 106 { 107 struct DONAU_BlindedDonationUnitSignature *signature = &signatures[i]; 108 109 GNUNET_assert ( 110 0 == json_array_append_new ( 111 j_signatures, 112 GNUNET_JSON_PACK ( 113 DONAU_JSON_pack_blinded_donation_unit_sig ("blinded_signature", 114 signature)))); 115 } 116 } 117 118 119 MHD_RESULT 120 DH_handler_issue_receipts_post (struct DH_RequestContext *rc, 121 const json_t *root, 122 const char *const args[1]) 123 { 124 struct DONAU_CharitySignatureP charity_sig; 125 uint64_t year; 126 bool second_time = false; 127 unsigned long long charity_id; 128 char dummy; 129 const json_t *budikeypairs; 130 size_t num_bkps; 131 struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps; 132 struct DONAUDB_CharityMetaData charity_meta; 133 json_t *blind_signatures; 134 struct DONAU_DonationReceiptHashP h_receipts; 135 struct TALER_Amount receipts_sum; 136 137 if ( (NULL == args[0]) || 138 (1 != sscanf (args[0], 139 "%llu%c", 140 &charity_id, 141 &dummy)) ) 142 { 143 GNUNET_break_op (0); 144 return TALER_MHD_reply_with_error (rc->connection, 145 MHD_HTTP_BAD_REQUEST, 146 TALER_EC_GENERIC_PARAMETER_MALFORMED, 147 "charity_id"); 148 } 149 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 150 "issue receipts for charity id: %llu\n", 151 charity_id); 152 153 { 154 struct GNUNET_JSON_Specification spec[] = { 155 GNUNET_JSON_spec_array_const ("budikeypairs", 156 &budikeypairs), 157 GNUNET_JSON_spec_fixed_auto ("charity_sig", 158 &charity_sig), 159 GNUNET_JSON_spec_uint64 ("year", 160 &year), 161 GNUNET_JSON_spec_end () 162 }; 163 enum GNUNET_GenericReturnValue res; 164 165 res = TALER_MHD_parse_json_data (rc->connection, 166 root, 167 spec); 168 if (GNUNET_SYSERR == res) 169 return MHD_NO; /* hard failure */ 170 if (GNUNET_NO == res) 171 { 172 GNUNET_break_op (0); 173 return MHD_YES; /* failure */ 174 } 175 } 176 177 /* parse the budikeypairs array */ 178 num_bkps = json_array_size (budikeypairs); 179 if (0 == num_bkps) 180 { 181 GNUNET_break_op (0); 182 return TALER_MHD_reply_with_error (rc->connection, 183 MHD_HTTP_BAD_REQUEST, 184 TALER_EC_GENERIC_PARAMETER_MALFORMED, 185 "budikeypairs"); 186 187 } 188 189 bkps = GNUNET_new_array (num_bkps, 190 struct DONAU_BlindedUniqueDonorIdentifierKeyPair); 191 { 192 json_t *bkp_obj; 193 size_t index; 194 195 json_array_foreach (budikeypairs, 196 index, 197 bkp_obj) 198 { 199 if (GNUNET_SYSERR == 200 parse_json_bkp (&bkps[index], 201 bkp_obj)) 202 { 203 GNUNET_break_op (0); 204 free_bkps (num_bkps, 205 bkps); 206 return TALER_MHD_reply_with_error (rc->connection, 207 MHD_HTTP_BAD_REQUEST, 208 TALER_EC_GENERIC_PARAMETER_MALFORMED, 209 "budikeypairs"); 210 } 211 } 212 } 213 214 { 215 enum GNUNET_DB_QueryStatus qs_charity; 216 217 qs_charity = DH_plugin->lookup_charity (DH_plugin->cls, 218 charity_id, 219 &charity_meta); 220 switch (qs_charity) 221 { 222 case GNUNET_DB_STATUS_HARD_ERROR: 223 case GNUNET_DB_STATUS_SOFT_ERROR: 224 GNUNET_break_op (0); 225 free_bkps (num_bkps, 226 bkps); 227 return TALER_MHD_reply_with_error (rc->connection, 228 MHD_HTTP_INTERNAL_SERVER_ERROR, 229 TALER_EC_GENERIC_DB_FETCH_FAILED, 230 NULL); 231 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 232 GNUNET_break_op (0); 233 free_bkps (num_bkps, 234 bkps); 235 return TALER_MHD_reply_with_error ( 236 rc->connection, 237 MHD_HTTP_NOT_FOUND, 238 TALER_EC_DONAU_CHARITY_NOT_FOUND, 239 NULL); 240 break; 241 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 242 break; 243 } 244 } 245 246 /* verify charity signature */ 247 if (GNUNET_OK != 248 DONAU_charity_bkp_verify (num_bkps, 249 bkps, 250 &charity_meta.charity_pub, 251 &charity_sig)) 252 { 253 GNUNET_break_op (0); 254 free_bkps (num_bkps, 255 bkps); 256 GNUNET_free (charity_meta.charity_name); 257 GNUNET_free (charity_meta.charity_url); 258 return TALER_MHD_reply_with_error ( 259 rc->connection, 260 MHD_HTTP_FORBIDDEN, 261 TALER_EC_DONAU_CHARITY_SIGNATURE_INVALID, 262 "charity_sig"); 263 } 264 265 GNUNET_free (charity_meta.charity_name); 266 GNUNET_free (charity_meta.charity_url); 267 { 268 /* request already made? -> idempotent */ 269 enum GNUNET_DB_QueryStatus qs_check_receipts; 270 struct DONAUDB_IssuedReceiptsMetaData check_receipts_meta; 271 struct GNUNET_HashContext *hc; 272 273 blind_signatures = json_array (); 274 GNUNET_assert (NULL != blind_signatures); 275 hc = GNUNET_CRYPTO_hash_context_start (); 276 for (size_t i = 0; i < num_bkps; i++) 277 { 278 const struct GNUNET_CRYPTO_BlindedMessage *bm 279 = bkps[i].blinded_udi.blinded_message; 280 281 GNUNET_CRYPTO_hash_context_read (hc, 282 &bkps[i].h_donation_unit_pub, 283 sizeof (bkps[i].h_donation_unit_pub)); 284 switch (bm->cipher) 285 { 286 case GNUNET_CRYPTO_BSA_INVALID: 287 GNUNET_assert (0); 288 break; 289 case GNUNET_CRYPTO_BSA_CS: 290 GNUNET_CRYPTO_hash_context_read ( 291 hc, 292 &bm->details.cs_blinded_message, 293 sizeof (bm->details.cs_blinded_message)); 294 break; 295 case GNUNET_CRYPTO_BSA_RSA: 296 GNUNET_CRYPTO_hash_context_read ( 297 hc, 298 bm->details.rsa_blinded_message.blinded_msg, 299 bm->details.rsa_blinded_message.blinded_msg_size); 300 break; 301 } 302 } 303 GNUNET_CRYPTO_hash_context_read (hc, 304 &charity_sig, 305 sizeof (struct DONAU_CharitySignatureP)); 306 GNUNET_CRYPTO_hash_context_read (hc, 307 &year, 308 sizeof (uint64_t)); 309 GNUNET_CRYPTO_hash_context_finish (hc, 310 &h_receipts.hash); 311 312 start: 313 qs_check_receipts 314 = DH_plugin->lookup_issued_receipts (DH_plugin->cls, 315 &h_receipts, 316 &check_receipts_meta); 317 switch (qs_check_receipts) 318 { 319 case GNUNET_DB_STATUS_HARD_ERROR: 320 case GNUNET_DB_STATUS_SOFT_ERROR: 321 GNUNET_break (0); 322 free_bkps (num_bkps, 323 bkps); 324 return TALER_MHD_reply_with_error (rc->connection, 325 MHD_HTTP_INTERNAL_SERVER_ERROR, 326 TALER_EC_GENERIC_DB_FETCH_FAILED, 327 NULL); 328 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 329 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 330 "request has not been made yet (first time)!\n"); 331 break; /* it's the first request from the charity, we can proceed */ 332 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 333 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 334 "request has been made already!\n"); 335 signatures_to_json (num_bkps, 336 check_receipts_meta.blinded_sigs, 337 blind_signatures); 338 for (size_t i = 0; i < check_receipts_meta.num_sig; i++) 339 { 340 GNUNET_CRYPTO_blinded_sig_decref ( 341 check_receipts_meta.blinded_sigs[i].blinded_sig); 342 } 343 free_bkps (num_bkps, 344 bkps); 345 return TALER_MHD_REPLY_JSON_PACK ( 346 rc->connection, 347 MHD_HTTP_OK, 348 TALER_JSON_pack_amount ("issued_amount", 349 &check_receipts_meta.amount), 350 GNUNET_JSON_pack_array_steal ("blind_signatures", 351 blind_signatures)); 352 } 353 } 354 355 /* calculate the sum of all receipts */ 356 357 GNUNET_assert (GNUNET_OK == 358 TALER_amount_set_zero (DH_currency, 359 &receipts_sum)); 360 for (size_t i = 0; i < num_bkps; i++) 361 { 362 struct DH_DonationUnitKey *dk; 363 364 if (NULL == (dk = DH_keys_donation_unit_by_hash ( 365 &bkps[i].h_donation_unit_pub))) 366 { 367 GNUNET_break_op (0); 368 free_bkps (num_bkps, 369 bkps); 370 return TALER_MHD_reply_with_error (rc->connection, 371 MHD_HTTP_NOT_FOUND, 372 TALER_EC_DONAU_GENERIC_KEYS_MISSING, 373 NULL); 374 } 375 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 376 "public key value: %lu.%u\n", 377 dk->value.value, 378 dk->value.fraction); 379 GNUNET_assert (0 <= TALER_amount_add (&receipts_sum, 380 &receipts_sum, 381 &dk->value)); 382 } 383 384 /* sign budis and send the signatures back */ 385 { 386 struct DONAU_BlindedDonationUnitSignature du_sigs[num_bkps]; 387 struct DONAU_BkpSignData bkps_sign_data[num_bkps]; 388 enum TALER_ErrorCode batch_sign_ec; 389 enum GNUNET_DB_QueryStatus qs_insert_ir; 390 bool smaller_than_max_per_year = false; 391 392 for (size_t i = 0; i < num_bkps; i++) 393 { 394 bkps_sign_data[i].h_donation_unit_pub = &bkps[i].h_donation_unit_pub; 395 bkps_sign_data[i].budi = &bkps[i].blinded_udi; 396 } 397 batch_sign_ec = DH_keys_donation_unit_batch_sign (num_bkps, 398 bkps_sign_data, 399 du_sigs); 400 if (TALER_EC_NONE != batch_sign_ec) 401 { 402 GNUNET_break_op (0); 403 free_bkps (num_bkps, 404 bkps); 405 return TALER_MHD_reply_with_error (rc->connection, 406 MHD_HTTP_INTERNAL_SERVER_ERROR, 407 batch_sign_ec, 408 NULL); 409 } 410 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 411 "made blind signatures!\n"); 412 free_bkps (num_bkps, 413 bkps); 414 415 /* save new receipts to date and save receipts Request (blinded signatures, 416 * charity id, amount, hash over bkps) to make it idempotent*/ 417 qs_insert_ir = DH_plugin->insert_issued_receipt ( 418 DH_plugin->cls, 419 num_bkps, 420 du_sigs, 421 (uint64_t) charity_id, 422 &h_receipts, 423 &receipts_sum, 424 &smaller_than_max_per_year); 425 switch (qs_insert_ir) 426 { 427 case GNUNET_DB_STATUS_HARD_ERROR: 428 case GNUNET_DB_STATUS_SOFT_ERROR: 429 GNUNET_break (0); 430 return TALER_MHD_reply_with_error (rc->connection, 431 MHD_HTTP_INTERNAL_SERVER_ERROR, 432 TALER_EC_GENERIC_DB_FETCH_FAILED, 433 NULL); 434 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 435 GNUNET_assert (! second_time); 436 second_time = true; 437 goto start; 438 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 439 if (! smaller_than_max_per_year) 440 { 441 GNUNET_break_op (0); 442 return TALER_MHD_reply_with_error (rc->connection, 443 MHD_HTTP_BAD_REQUEST, 444 TALER_EC_DONAU_EXCEEDING_DONATION_LIMIT, 445 NULL); 446 } 447 break; 448 } 449 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 450 "issue receipts request is saved! (idempotent)\n"); 451 452 signatures_to_json (num_bkps, 453 du_sigs, 454 blind_signatures); 455 for (unsigned int i = 0; i<num_bkps; i++) 456 GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig); 457 } 458 return TALER_MHD_REPLY_JSON_PACK ( 459 rc->connection, 460 MHD_HTTP_OK, 461 TALER_JSON_pack_amount ("issued_amount", 462 &receipts_sum), 463 GNUNET_JSON_pack_array_steal ("blind_signatures", 464 blind_signatures)); 465 } 466 467 468 /* end of donau-httpd_batch-issue.c */