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_post-reserves-RESERVE_PUB-attest.c (12271B)


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