donau

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

donau_api_batch_issue_receipts.c (11378B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   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 /**
     21  * @file lib/donau_api_batch_issue_receipts.c
     22  * @brief Implementation of the "handle" component of the donau's HTTP API
     23  * @author Lukas Matyja
     24  */
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_curl_lib.h>
     28 #include "donau_service.h"
     29 #include "donau_util.h"
     30 #include "donau_api_curl_defaults.h"
     31 #include "donau_json_lib.h"
     32 
     33 
     34 /**
     35  * Handle for a POST /batch-issue/$CHARITY_ID request.
     36  */
     37 struct DONAU_BatchIssueReceiptHandle
     38 {
     39   /**
     40    * The url for the /batch-issue/$CHARITY_ID request.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Minor context that holds body and headers.
     46    */
     47   struct TALER_CURL_PostContext post_ctx;
     48 
     49   /**
     50    * Entry for this request with the `struct GNUNET_CURL_Context`.
     51    */
     52   struct GNUNET_CURL_Job *job;
     53 
     54   /**
     55    * Function to call with the result.
     56    */
     57   DONAU_BatchIssueReceiptsCallback cb;
     58 
     59   /**
     60    * BUDI-key-pair signature.
     61    */
     62   struct DONAU_CharitySignatureP charity_sig;
     63 
     64   /**
     65    * number of requested signatures.
     66    */
     67   size_t num_blinded_sigs;
     68 
     69   /**
     70    * Closure to pass to @e cb.
     71    */
     72   void *cb_cls;
     73 
     74   /**
     75    * Reference to the execution context.
     76    */
     77   struct GNUNET_CURL_Context *ctx;
     78 
     79 };
     80 
     81 
     82 /**
     83  * Decode the JSON in @a resp_obj from the /batch-issue/$CHARITY_ID response
     84  * and store the data in the @a biresp.
     85  *
     86  * @param[in] resp_obj JSON object to parse
     87  * @param[in] birh contains the callback function
     88  * @param[out] biresp where to store the results we decoded
     89  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     90  * (malformed JSON)
     91  */
     92 static enum GNUNET_GenericReturnValue
     93 handle_batch_issue_ok (const json_t *resp_obj,
     94                        struct DONAU_BatchIssueReceiptHandle *birh,
     95                        struct DONAU_BatchIssueResponse *biresp)
     96 {
     97   const json_t *j_blind_signatures;
     98   struct GNUNET_JSON_Specification spec[] = {
     99     TALER_JSON_spec_amount_any ("issued_amount",
    100                                 &biresp->details.ok.issued_amount),
    101     GNUNET_JSON_spec_array_const ("blind_signatures",
    102                                   &j_blind_signatures),
    103     GNUNET_JSON_spec_end ()
    104   };
    105 
    106   if (GNUNET_OK !=
    107       GNUNET_JSON_parse (resp_obj,
    108                          spec,
    109                          NULL,
    110                          NULL))
    111   {
    112     GNUNET_break_op (0);
    113     return GNUNET_SYSERR;
    114   }
    115   if ( (NULL == j_blind_signatures) ||
    116        (! json_is_array (j_blind_signatures)) )
    117   {
    118     GNUNET_break (0);
    119     return GNUNET_SYSERR;
    120   }
    121   biresp->details.ok.num_blinded_sigs
    122     = json_array_size (j_blind_signatures);
    123   if (biresp->details.ok.num_blinded_sigs != birh->num_blinded_sigs)
    124   {
    125     /* The Donau must return exactly as many signatures as we requested;
    126        anything else would make the array indexing below go out of bounds. */
    127     GNUNET_break_op (0);
    128     return GNUNET_SYSERR;
    129   }
    130   biresp->details.ok.blinded_sigs =
    131     GNUNET_new_array (birh->num_blinded_sigs,
    132                       struct DONAU_BlindedDonationUnitSignature);
    133   {
    134     size_t index;
    135     json_t *du_sig_obj;
    136 
    137     json_array_foreach (j_blind_signatures,
    138                         index,
    139                         du_sig_obj)
    140     {
    141       struct GNUNET_JSON_Specification ispec[] = {
    142         DONAU_JSON_spec_blinded_donation_unit_sig (
    143           "blinded_signature",
    144           &biresp->details.ok.blinded_sigs[index]),
    145         GNUNET_JSON_spec_end ()
    146       };
    147 
    148       if (GNUNET_OK !=
    149           GNUNET_JSON_parse (du_sig_obj,
    150                              ispec,
    151                              NULL,
    152                              NULL))
    153       {
    154         GNUNET_break_op (0);
    155         for (size_t k = 0; k < index; k++)
    156           GNUNET_CRYPTO_blinded_sig_decref (
    157             biresp->details.ok.blinded_sigs[k].blinded_sig);
    158         GNUNET_free (biresp->details.ok.blinded_sigs);
    159         return GNUNET_SYSERR;
    160       }
    161     }
    162   }
    163   birh->cb (birh->cb_cls,
    164             biresp);
    165   birh->cb = NULL;
    166   for (unsigned int i=0; i<biresp->details.ok.num_blinded_sigs; i++)
    167   {
    168     struct DONAU_BlindedDonationUnitSignature *sig
    169       = &biresp->details.ok.blinded_sigs[i];
    170 
    171     GNUNET_CRYPTO_blinded_sig_decref (sig->blinded_sig);
    172   }
    173   GNUNET_free (biresp->details.ok.blinded_sigs);
    174   return GNUNET_OK;
    175 }
    176 
    177 
    178 /**
    179  * Transform issue receipt request into JSON.
    180  *
    181  * @param num_bkp number of budi-key-pairs in @a bkp
    182  * @param bkp budi-key-pair array
    183  * @param year corresponding year
    184  * @param charity_sig signature from charity over @a bkp
    185  */
    186 static json_t *
    187 issue_receipt_body_to_json (
    188   const unsigned int num_bkp,
    189   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
    190   const uint64_t year,
    191   const struct DONAU_CharitySignatureP *charity_sig)
    192 {
    193   json_t *budikeypairs = json_array ();
    194 
    195   GNUNET_assert (NULL != budikeypairs);
    196   for (size_t i = 0; i < num_bkp; i++)
    197   {
    198     json_t *budikeypair = GNUNET_JSON_PACK (
    199       GNUNET_JSON_pack_data_auto ("h_donation_unit_pub",
    200                                   &bkp[i].h_donation_unit_pub.hash),
    201       DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi",
    202                                                    &bkp[i].blinded_udi));
    203     GNUNET_assert (0 ==
    204                    json_array_append_new (budikeypairs,
    205                                           budikeypair));
    206   }
    207   return GNUNET_JSON_PACK (
    208     GNUNET_JSON_pack_array_steal ("budikeypairs",
    209                                   budikeypairs),
    210     GNUNET_JSON_pack_data_auto ("charity_sig",
    211                                 &charity_sig->eddsa_sig),
    212     GNUNET_JSON_pack_uint64 ("year",
    213                              year));
    214 }
    215 
    216 
    217 /**
    218  * Function called when we're done processing the
    219  * HTTP POST /batch-issue/$CHARITY_ID request.
    220  *
    221  * @param cls the `struct KeysRequest`
    222  * @param response_code HTTP response code, 0 on error
    223  * @param resp_obj parsed JSON result, NULL on error
    224  */
    225 static void
    226 handle_batch_issue_finished (void *cls,
    227                              long response_code,
    228                              const void *resp_obj)
    229 {
    230   struct DONAU_BatchIssueReceiptHandle *birh = cls;
    231   const json_t *j = resp_obj;
    232   struct DONAU_BatchIssueResponse biresp = {
    233     .hr.reply = j,
    234     .hr.http_status = (unsigned int) response_code
    235   };
    236 
    237   birh->job = NULL;
    238   switch (response_code)
    239   {
    240   case MHD_HTTP_OK:
    241     if (GNUNET_OK !=
    242         handle_batch_issue_ok (j,
    243                                birh,
    244                                &biresp))
    245     {
    246       biresp.hr.http_status = 0;
    247       biresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    248     }
    249     break;
    250   case MHD_HTTP_NO_CONTENT:
    251     biresp.hr.ec = TALER_JSON_get_error_code (j);
    252     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    253     break;
    254   case MHD_HTTP_FORBIDDEN:
    255     // invalid charity signature
    256     biresp.hr.ec = TALER_JSON_get_error_code (j);
    257     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    258     break;
    259   case MHD_HTTP_NOT_FOUND:
    260     // one or more donation units are not known to the Donau
    261     biresp.hr.ec = TALER_JSON_get_error_code (j);
    262     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    263     break;
    264   case MHD_HTTP_CONTENT_TOO_LARGE:
    265     biresp.hr.ec = TALER_JSON_get_error_code (j);
    266     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    267     break;
    268   case MHD_HTTP_CONFLICT:
    269     // Donation limit is not sufficient
    270     biresp.hr.ec = TALER_JSON_get_error_code (j);
    271     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    272     break;
    273   case MHD_HTTP_GONE:
    274     // donation unit key is no longer valid
    275     biresp.hr.ec = TALER_JSON_get_error_code (j);
    276     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    277     break;
    278   case MHD_HTTP_TOO_EARLY:
    279     // donation unit key is not yet valid
    280     biresp.hr.ec = TALER_JSON_get_error_code (j);
    281     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    282     break;
    283   default:
    284     /* unexpected response code */
    285     GNUNET_break_op (0);
    286     biresp.hr.ec = TALER_JSON_get_error_code (j);
    287     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    288     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    289                 "Unexpected response code %u/%d for POST %s\n",
    290                 (unsigned int) response_code,
    291                 (int) biresp.hr.ec,
    292                 birh->url);
    293     break;
    294   }
    295   if (NULL != birh->cb)
    296   {
    297     birh->cb (birh->cb_cls,
    298               &biresp);
    299     birh->cb = NULL;
    300   }
    301   DONAU_charity_issue_receipt_cancel (birh);
    302 }
    303 
    304 
    305 struct DONAU_BatchIssueReceiptHandle *
    306 DONAU_charity_issue_receipt (
    307   struct GNUNET_CURL_Context *ctx,
    308   const char *url,
    309   const struct DONAU_CharityPrivateKeyP *charity_priv,
    310   const uint64_t charity_id,
    311   const uint64_t year,
    312   const size_t num_bkp,
    313   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
    314   DONAU_BatchIssueReceiptsCallback cb,
    315   void *cb_cls)
    316 {
    317   CURL *eh;
    318   json_t *body;
    319   char arg_str[sizeof (charity_id) * 2 + 32];
    320   struct DONAU_BatchIssueReceiptHandle *birh;
    321 
    322   birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle);
    323   birh->num_blinded_sigs = num_bkp;
    324   DONAU_charity_bkp_sign (num_bkp, bkp,
    325                           charity_priv,
    326                           &birh->charity_sig);
    327   birh->cb = cb;
    328   birh->cb_cls = cb_cls;
    329   birh->ctx = ctx;
    330   GNUNET_snprintf (arg_str,
    331                    sizeof (arg_str),
    332                    "batch-issue/%llu",
    333                    (unsigned long long) charity_id);
    334   birh->url = TALER_url_join (url,
    335                               arg_str,
    336                               NULL);
    337   if (NULL == birh->url)
    338   {
    339     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    340                 "Could not construct request URL.\n");
    341     GNUNET_free (birh);
    342     return NULL;
    343   }
    344   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    345               "issue_receipts_with_URL `%s'.\n",
    346               birh->url);
    347   body = issue_receipt_body_to_json (num_bkp,
    348                                      bkp,
    349                                      year,
    350                                      &birh->charity_sig);
    351   eh = DONAU_curl_easy_get_ (birh->url);
    352   if ( (NULL == eh) ||
    353        (GNUNET_OK !=
    354         TALER_curl_easy_post (&birh->post_ctx,
    355                               eh,
    356                               body)) )
    357   {
    358     GNUNET_break (0);
    359     if (NULL != eh)
    360       curl_easy_cleanup (eh);
    361     json_decref (body);
    362     GNUNET_free (birh->url);
    363     GNUNET_free (birh);
    364     return NULL;
    365   }
    366   json_decref (body);
    367   birh->job = GNUNET_CURL_job_add2 (ctx,
    368                                     eh,
    369                                     birh->post_ctx.headers,
    370                                     &handle_batch_issue_finished,
    371                                     birh);
    372   return birh;
    373 }
    374 
    375 
    376 void
    377 DONAU_charity_issue_receipt_cancel (
    378   struct DONAU_BatchIssueReceiptHandle *birh)
    379 {
    380   if (NULL != birh->job)
    381   {
    382     GNUNET_CURL_job_cancel (birh->job);
    383     birh->job = NULL;
    384   }
    385   TALER_curl_easy_post_finished (&birh->post_ctx);
    386   GNUNET_free (birh->url);
    387   GNUNET_free (birh);
    388 }