exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_reserves_attest.c (12353B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2022, 2024 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 taler-exchange-httpd_reserves_attest.c
     18  * @brief Handle /reserves/$RESERVE_PUB/attest requests
     19  * @author Florian Dold
     20  * @author Benedikt Mueller
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <jansson.h>
     26 #include "taler/taler_dbevents.h"
     27 #include "taler/taler_kyclogic_lib.h"
     28 #include "taler/taler_json_lib.h"
     29 #include "taler/taler_mhd_lib.h"
     30 #include "taler-exchange-httpd_keys.h"
     31 #include "taler-exchange-httpd_reserves_attest.h"
     32 #include "taler-exchange-httpd_responses.h"
     33 
     34 
     35 /**
     36  * How far do we allow a client's time to be off when
     37  * checking the request timestamp?
     38  */
     39 #define TIMESTAMP_TOLERANCE \
     40         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
     41 
     42 
     43 /**
     44  * Closure for #reserve_attest_transaction.
     45  */
     46 struct ReserveAttestContext
     47 {
     48   /**
     49    * Public key of the reserve the inquiry is about.
     50    */
     51   struct TALER_ReservePublicKeyP reserve_pub;
     52 
     53   /**
     54    * Hash of the payto URI of this reserve.
     55    */
     56   struct TALER_NormalizedPaytoHashP h_payto;
     57 
     58   /**
     59    * Timestamp of the request.
     60    */
     61   struct GNUNET_TIME_Timestamp timestamp;
     62 
     63   /**
     64    * Expiration time for the attestation.
     65    */
     66   struct GNUNET_TIME_Timestamp etime;
     67 
     68   /**
     69    * List of requested details.
     70    */
     71   const json_t *details;
     72 
     73   /**
     74    * Client signature approving the request.
     75    */
     76   struct TALER_ReserveSignatureP reserve_sig;
     77 
     78   /**
     79    * Attributes we are affirming. JSON object.
     80    */
     81   json_t *json_attest;
     82 
     83   /**
     84    * Database error codes encountered.
     85    */
     86   enum GNUNET_DB_QueryStatus qs;
     87 
     88   /**
     89    * Set to true if we did not find the reserve.
     90    */
     91   bool not_found;
     92 
     93 };
     94 
     95 
     96 /**
     97  * Send reserve attest to client.
     98  *
     99  * @param connection connection to the client
    100  * @param rhc reserve attest to return
    101  * @return MHD result code
    102  */
    103 static MHD_RESULT
    104 reply_reserve_attest_success (struct MHD_Connection *connection,
    105                               const struct ReserveAttestContext *rhc)
    106 {
    107   struct TALER_ExchangeSignatureP exchange_sig;
    108   struct TALER_ExchangePublicKeyP exchange_pub;
    109   enum TALER_ErrorCode ec;
    110   struct GNUNET_TIME_Timestamp now;
    111 
    112   if (NULL == rhc->json_attest)
    113   {
    114     GNUNET_break (0);
    115     return TALER_MHD_reply_with_error (connection,
    116                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    117                                        TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
    118                                        NULL);
    119   }
    120   now = GNUNET_TIME_timestamp_get ();
    121   ec = TALER_exchange_online_reserve_attest_details_sign (
    122     &TEH_keys_exchange_sign_,
    123     now,
    124     rhc->etime,
    125     &rhc->reserve_pub,
    126     rhc->json_attest,
    127     &exchange_pub,
    128     &exchange_sig);
    129   if (TALER_EC_NONE != ec)
    130   {
    131     GNUNET_break (0);
    132     return TALER_MHD_reply_with_ec (connection,
    133                                     ec,
    134                                     NULL);
    135   }
    136   return TALER_MHD_REPLY_JSON_PACK (
    137     connection,
    138     MHD_HTTP_OK,
    139     GNUNET_JSON_pack_data_auto ("exchange_sig",
    140                                 &exchange_sig),
    141     GNUNET_JSON_pack_data_auto ("exchange_pub",
    142                                 &exchange_pub),
    143     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
    144                                 now),
    145     GNUNET_JSON_pack_timestamp ("expiration_time",
    146                                 rhc->etime),
    147     GNUNET_JSON_pack_object_steal ("attributes",
    148                                    rhc->json_attest));
    149 }
    150 
    151 
    152 /**
    153  * Function called with information about all applicable
    154  * legitimization processes for the given user.  Finds the
    155  * available attributes and merges them into our result
    156  * set based on the details requested by the client.
    157  *
    158  * @param cls our `struct ReserveAttestContext *`
    159  * @param h_payto account for which the attribute data is stored
    160  * @param provider_name provider that must be checked
    161  * @param collection_time when was the data collected
    162  * @param expiration_time when does the data expire
    163  * @param enc_attributes_size number of bytes in @a enc_attributes
    164  * @param enc_attributes encrypted attribute data
    165  */
    166 static void
    167 kyc_process_cb (void *cls,
    168                 const struct TALER_NormalizedPaytoHashP *h_payto,
    169                 const char *provider_name,
    170                 struct GNUNET_TIME_Timestamp collection_time,
    171                 struct GNUNET_TIME_Timestamp expiration_time,
    172                 size_t enc_attributes_size,
    173                 const void *enc_attributes)
    174 {
    175   struct ReserveAttestContext *rsc = cls;
    176   json_t *attrs;
    177   json_t *val;
    178   const char *name;
    179   bool match = false;
    180 
    181   if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
    182     return;
    183   attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
    184                                                enc_attributes,
    185                                                enc_attributes_size);
    186   if (NULL == attrs)
    187   {
    188     GNUNET_break (0);
    189     return;
    190   }
    191   json_object_foreach (attrs, name, val)
    192   {
    193     bool requested = strcmp (name,
    194                              "FORM_ID"); /* we always return the FORM_ID */
    195     size_t idx;
    196     json_t *str;
    197 
    198     if (NULL != json_object_get (rsc->json_attest,
    199                                  name))
    200       continue;   /* duplicate */
    201     json_array_foreach (rsc->details, idx, str)
    202     {
    203       if (0 == strcmp (json_string_value (str),
    204                        name))
    205       {
    206         requested = true;
    207         break;
    208       }
    209     }
    210     if (! requested)
    211     {
    212       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    213                   "Skipping attribute `%s': not requested\n",
    214                   name);
    215       continue;
    216     }
    217     match = true;
    218     GNUNET_assert (0 ==
    219                    json_object_set (rsc->json_attest,   /* NOT set_new! */
    220                                     name,
    221                                     val));
    222   }
    223   json_decref (attrs);
    224   if (! match)
    225     return;
    226   rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
    227                                           rsc->etime);
    228 }
    229 
    230 
    231 /**
    232  * Function implementing /reserves/$RID/attest transaction.  Given the public
    233  * key of a reserve, return the associated transaction attest.  Runs the
    234  * transaction logic; IF it returns a non-error code, the transaction logic
    235  * MUST NOT queue a MHD response.  IF it returns an hard error, the
    236  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF it
    237  * returns the soft error code, the function MAY be called again to retry and
    238  * MUST not queue a MHD response.
    239  *
    240  * @param cls a `struct ReserveAttestContext *`
    241  * @param connection MHD request which triggered the transaction
    242  * @param[out] mhd_ret set to MHD response status for @a connection,
    243  *             if transaction failed (!); unused
    244  * @return transaction status
    245  */
    246 static enum GNUNET_DB_QueryStatus
    247 reserve_attest_transaction (void *cls,
    248                             struct MHD_Connection *connection,
    249                             MHD_RESULT *mhd_ret)
    250 {
    251   struct ReserveAttestContext *rsc = cls;
    252   enum GNUNET_DB_QueryStatus qs;
    253 
    254   rsc->json_attest = json_object ();
    255   GNUNET_assert (NULL != rsc->json_attest);
    256   qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
    257                                           &rsc->h_payto,
    258                                           &kyc_process_cb,
    259                                           rsc);
    260   switch (qs)
    261   {
    262   case GNUNET_DB_STATUS_HARD_ERROR:
    263     GNUNET_break (0);
    264     *mhd_ret
    265       = TALER_MHD_reply_with_error (connection,
    266                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    267                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
    268                                     "select_kyc_attributes");
    269     return qs;
    270   case GNUNET_DB_STATUS_SOFT_ERROR:
    271     GNUNET_break (0);
    272     return qs;
    273   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    274     rsc->not_found = true;
    275     return qs;
    276   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    277     rsc->not_found = false;
    278     break;
    279   }
    280   return qs;
    281 }
    282 
    283 
    284 MHD_RESULT
    285 TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
    286                              const json_t *root,
    287                              const char *const args[1])
    288 {
    289   struct ReserveAttestContext rsc = {
    290     .etime = GNUNET_TIME_UNIT_FOREVER_TS
    291   };
    292   MHD_RESULT mhd_ret;
    293   struct GNUNET_JSON_Specification spec[] = {
    294     GNUNET_JSON_spec_timestamp ("request_timestamp",
    295                                 &rsc.timestamp),
    296     GNUNET_JSON_spec_array_const ("details",
    297                                   &rsc.details),
    298     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    299                                  &rsc.reserve_sig),
    300     GNUNET_JSON_spec_end ()
    301   };
    302   struct GNUNET_TIME_Timestamp now;
    303 
    304   if (GNUNET_OK !=
    305       GNUNET_STRINGS_string_to_data (args[0],
    306                                      strlen (args[0]),
    307                                      &rsc.reserve_pub,
    308                                      sizeof (rsc.reserve_pub)))
    309   {
    310     GNUNET_break_op (0);
    311     return TALER_MHD_reply_with_error (rc->connection,
    312                                        MHD_HTTP_BAD_REQUEST,
    313                                        TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
    314                                        args[0]);
    315   }
    316   {
    317     enum GNUNET_GenericReturnValue res;
    318 
    319     res = TALER_MHD_parse_json_data (rc->connection,
    320                                      root,
    321                                      spec);
    322     if (GNUNET_SYSERR == res)
    323     {
    324       GNUNET_break (0);
    325       return MHD_NO; /* hard failure */
    326     }
    327     if (GNUNET_NO == res)
    328     {
    329       GNUNET_break_op (0);
    330       return MHD_YES; /* failure */
    331     }
    332   }
    333   now = GNUNET_TIME_timestamp_get ();
    334   if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
    335                                         rsc.timestamp.abs_time,
    336                                         TIMESTAMP_TOLERANCE))
    337   {
    338     GNUNET_break_op (0);
    339     return TALER_MHD_reply_with_error (rc->connection,
    340                                        MHD_HTTP_BAD_REQUEST,
    341                                        TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
    342                                        NULL);
    343   }
    344 
    345   if (GNUNET_OK !=
    346       TALER_wallet_reserve_attest_request_verify (rsc.timestamp,
    347                                                   rsc.details,
    348                                                   &rsc.reserve_pub,
    349                                                   &rsc.reserve_sig))
    350   {
    351     GNUNET_break_op (0);
    352     return TALER_MHD_reply_with_error (rc->connection,
    353                                        MHD_HTTP_FORBIDDEN,
    354                                        TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE,
    355                                        NULL);
    356   }
    357 
    358   {
    359     struct TALER_NormalizedPayto payto_uri;
    360 
    361     payto_uri = TALER_reserve_make_payto (TEH_base_url,
    362                                           &rsc.reserve_pub);
    363     TALER_normalized_payto_hash (payto_uri,
    364                                  &rsc.h_payto);
    365     GNUNET_free (payto_uri.normalized_payto);
    366   }
    367 
    368   if (GNUNET_OK !=
    369       TEH_DB_run_transaction (rc->connection,
    370                               "post reserve attest",
    371                               TEH_MT_REQUEST_OTHER,
    372                               &mhd_ret,
    373                               &reserve_attest_transaction,
    374                               &rsc))
    375   {
    376     return mhd_ret;
    377   }
    378   if (rsc.not_found)
    379   {
    380     json_decref (rsc.json_attest);
    381     return TALER_MHD_reply_with_error (rc->connection,
    382                                        MHD_HTTP_NOT_FOUND,
    383                                        TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    384                                        args[0]);
    385   }
    386   return reply_reserve_attest_success (rc->connection,
    387                                        &rsc);
    388 }
    389 
    390 
    391 /* end of taler-exchange-httpd_reserves_attest.c */