donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

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 */