donau

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

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