frosix

Multiparty signature service (experimental)
Log | Files | Refs | README | LICENSE

frosix-httpd_auth-challenge.c (13956B)


      1 /*
      2   This file is part of Frosix
      3   Copyright (C) 2022, 2023 Joel Urech
      4 
      5   Frosix 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   Frosix 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   Frosix; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file backend/frosix-httpd_commitment.c
     18  * @brief functions to handle incoming requests on /auth-challenge
     19  * @author Joel Urech
     20  */
     21 #include "frosix-httpd_auth.h"
     22 #include "frosix-httpd.h"
     23 #include "frost_high.h"
     24 #include "frost_low.h"
     25 #include <taler/taler_util.h>
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include "frosix_database_plugin.h"
     28 #include "frosix_util_lib.h"
     29 #include "frosix_authorization_lib.h"
     30 
     31 #define HASHCONTEXT "FROSIX-DKG"
     32 
     33 /**
     34  * Context for an dkg commitment operation
     35 */
     36 struct ChallengeContext
     37 {
     38   /**
     39    *
     40   */
     41   struct FROSIX_EncryptionKey encryption_key;
     42 
     43   /**
     44    *
     45   */
     46   struct FROST_MessageHash message_hash;
     47 
     48   /**
     49    *
     50   */
     51   struct GNUNET_HashCode challenge_salt;
     52 
     53   /**
     54    *
     55   */
     56   const char *challenge_method;
     57 
     58   /**
     59    *
     60   */
     61   const char *challenge_data;
     62 
     63   /**
     64    * Id of the request, hash of all submitted values.
     65   */
     66   struct FROSIX_ChallengeRequestIdP request_id;
     67 
     68   /**
     69    * Our handler context
     70   */
     71   struct TM_HandlerContext *hc;
     72 
     73   /**
     74    * Uploaded JSON data, NULL if upload is not yet complete.
     75   */
     76   json_t *json;
     77 
     78   /**
     79    * Post parser context.
     80    */
     81   void *post_ctx;
     82 
     83   /**
     84    * Connection handle for closing or resuming
     85    */
     86   struct MHD_Connection *connection;
     87 
     88   /**
     89    * Reference to the authorization plugin which was loaded
     90   */
     91   struct FROSIX_AuthorizationPlugin *authorization;
     92 
     93   /**
     94    * Status of the authorization
     95    */
     96   struct FROSIX_AUTHORIZATION_State *as;
     97 
     98   /**
     99    * Random authorization code we are using.
    100    */
    101   uint64_t code;
    102 
    103   /**
    104    * FIXME
    105   */
    106   struct FROSIX_ChallengeIdP challenge_id;
    107 };
    108 
    109 
    110 /**
    111  * Generate a response telling the client that answering this
    112  * challenge failed because the rate limit has been exceeded.
    113  *
    114  * @param gc request to answer for
    115  * @return MHD status code
    116  */
    117 static MHD_RESULT
    118 reply_rate_limited (const struct ChallengeContext *cc)
    119 {
    120   return TALER_MHD_REPLY_JSON_PACK (
    121     cc->connection,
    122     MHD_HTTP_TOO_MANY_REQUESTS,
    123     TALER_MHD_PACK_EC (TALER_EC_GENERIC_TIMEOUT),
    124     GNUNET_JSON_pack_uint64 ("request_limit",
    125                              cc->authorization->retry_counter),
    126     GNUNET_JSON_pack_time_rel ("request_frequency",
    127                                cc->authorization->code_rotation_period));
    128 }
    129 
    130 
    131 /**
    132  * Run the authorization method-specific 'process' function and continue
    133  * based on its result with generating an HTTP response.
    134  *
    135  * @param connection the connection we are handling
    136  * @param gc our overall handler context
    137  */
    138 static MHD_RESULT
    139 run_authorization_process (struct MHD_Connection *connection,
    140                            struct ChallengeContext *gc)
    141 {
    142   enum FROSIX_AUTHORIZATION_ChallengeResult ret;
    143   enum GNUNET_DB_QueryStatus qs;
    144 
    145   if (NULL == gc->authorization->challenge)
    146   {
    147     GNUNET_break (0);
    148     return TALER_MHD_reply_with_error (gc->connection,
    149                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    150                                        TALER_EC_GENERIC_METHOD_INVALID,
    151                                        "challenge method not implemented for authorization method");
    152   }
    153   ret = gc->authorization->challenge (gc->as,
    154                                       connection);
    155   switch (ret)
    156   {
    157   case FROSIX_AUTHORIZATION_CRES_SUCCESS:
    158     /* Challenge sent successfully */
    159     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    160                 "Authorization request %llu for %s sent successfully\n",
    161                 (unsigned long long) gc->code,
    162                 TALER_B2S (&gc->challenge_id));
    163     qs = db->mark_challenge_sent (db->cls,
    164                                   &gc->challenge_id,
    165                                   gc->code);
    166     GNUNET_break (0 < qs);
    167     gc->authorization->cleanup (gc->as);
    168     gc->as = NULL;
    169     return MHD_YES;
    170   case FROSIX_AUTHORIZATION_CRES_FAILED:
    171     gc->authorization->cleanup (gc->as);
    172     gc->as = NULL;
    173     return MHD_YES;
    174   case FROSIX_AUTHORIZATION_CRES_SUSPENDED:
    175     /* connection was suspended */
    176     // gc_suspended (gc);
    177     return MHD_YES;
    178   case FROSIX_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED:
    179     /* Challenge sent successfully */
    180     qs = db->mark_challenge_sent (db->cls,
    181                                   &gc->challenge_id,
    182                                   gc->code);
    183     GNUNET_break (0 < qs);
    184     gc->authorization->cleanup (gc->as);
    185     gc->as = NULL;
    186     return MHD_NO;
    187   case FROSIX_AUTHORIZATION_CRES_FAILED_REPLY_FAILED:
    188     gc->authorization->cleanup (gc->as);
    189     gc->as = NULL;
    190     return MHD_NO;
    191   }
    192   GNUNET_break (0);
    193   return MHD_NO;
    194 }
    195 
    196 
    197 MHD_RESULT
    198 FH_handler_auth_challenge_post (
    199   struct MHD_Connection *connection,
    200   struct TM_HandlerContext *hc,
    201   const struct FROSIX_ChallengeRequestIdP *id,
    202   const char *auth_challenge_data,
    203   size_t *auth_challenge_data_size)
    204 {
    205   enum GNUNET_GenericReturnValue res;
    206   struct ChallengeContext *cc = hc->ctx;
    207 
    208   if (NULL == cc)
    209   {
    210     cc = GNUNET_new (struct ChallengeContext);
    211     cc->connection = connection;
    212     hc->ctx = cc;
    213   }
    214 
    215   /* parse request body */
    216   if (NULL == cc->json)
    217   {
    218     res = TALER_MHD_parse_post_json (connection,
    219                                      &cc->post_ctx,
    220                                      auth_challenge_data,
    221                                      auth_challenge_data_size,
    222                                      &cc->json);
    223     if (GNUNET_SYSERR == res)
    224     {
    225       GNUNET_break (0);
    226       return MHD_NO;
    227     }
    228     if ((GNUNET_NO == res ||
    229          (NULL == cc->json)))
    230     {
    231       return MHD_YES;
    232     }
    233   }
    234 
    235   struct GNUNET_JSON_Specification spec[] = {
    236     GNUNET_JSON_spec_fixed_auto ("encryption_key",
    237                                  &cc->encryption_key),
    238     GNUNET_JSON_spec_string ("auth_method",
    239                              &cc->challenge_method),
    240     GNUNET_JSON_spec_string ("auth_data",
    241                              &cc->challenge_data),
    242     GNUNET_JSON_spec_fixed_auto ("auth_nonce",
    243                                  &cc->challenge_salt),
    244     GNUNET_JSON_spec_fixed_auto ("message_hash",
    245                                  &cc->message_hash),
    246     GNUNET_JSON_spec_end ()
    247   };
    248 
    249   res = TALER_MHD_parse_json_data (connection,
    250                                    cc->json,
    251                                    spec);
    252   if (GNUNET_SYSERR == res)
    253   {
    254     GNUNET_break_op (0);
    255     return TALER_MHD_reply_with_error (connection,
    256                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    257                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    258                                        "Unable to parse request body");
    259   }
    260   if (GNUNET_NO == res)
    261   {
    262     GNUNET_break_op (0);
    263     return TALER_MHD_reply_with_error (connection,
    264                                        MHD_HTTP_BAD_REQUEST,
    265                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    266                                        "Unable to parse request body");
    267   }
    268 
    269   /* validate request id */
    270   {
    271     struct FROSIX_ChallengeRequestIdP req_id;
    272     FROSIX_compute_challenge_request_id (&req_id,
    273                                          &cc->encryption_key,
    274                                          &cc->message_hash);
    275 
    276     if (GNUNET_OK != FROST_hash_cmp (&req_id.id,
    277                                      &id->id))
    278     {
    279       GNUNET_JSON_parse_free (spec);
    280       GNUNET_break_op (0);
    281       return TALER_MHD_reply_with_error (connection,
    282                                          MHD_HTTP_BAD_REQUEST,
    283                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    284                                          "ID in URL not matching data in body");
    285     }
    286   }
    287 
    288   {
    289     /* hash encryption_key to get id from entry in db */
    290     struct FROST_HashCode enc_key_hash;
    291     FROSIX_hash_encryption_key (&enc_key_hash,
    292                                 &cc->encryption_key);
    293 
    294     struct FROSIX_ChallengeHashP auth_hash_db;
    295     enum GNUNET_DB_QueryStatus qs;
    296     qs = db->get_auth_hash (db->cls,
    297                             &enc_key_hash,
    298                             &auth_hash_db);
    299 
    300     switch (qs)
    301     {
    302     case GNUNET_DB_STATUS_HARD_ERROR:
    303     case GNUNET_DB_STATUS_SOFT_ERROR:
    304     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    305       GNUNET_break (0);
    306       return TALER_MHD_reply_with_error (connection,
    307                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    308                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    309                                          "auth_data_select");
    310     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    311       break;
    312     }
    313 
    314     /* hash received auth data */
    315     struct FROSIX_ChallengeHashP auth_hash_req;
    316     FROSIX_compute_auth_hash (&auth_hash_req,
    317                               cc->challenge_data,
    318                               &cc->challenge_salt);
    319 
    320     /* compare auth hash from db with hash of received data*/
    321     if (GNUNET_OK != FROSIX_gnunet_hash_cmp (&auth_hash_req.hash,
    322                                              &auth_hash_db.hash))
    323     {
    324       GNUNET_break_op (0);
    325       return TALER_MHD_reply_with_error (connection,
    326                                          MHD_HTTP_BAD_REQUEST,
    327                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    328                                          "Got invalid data from DB");
    329     }
    330 
    331     /* compute challenge id */
    332     FROSIX_compute_challenge_id (&cc->challenge_id,
    333                                  &enc_key_hash,
    334                                  &cc->message_hash);
    335   }
    336 
    337   /* try to load authorization plugin */
    338   cc->authorization
    339     = FROSIX_authorization_plugin_load (cc->challenge_method,
    340                                         db,
    341                                         FH_cfg);
    342 
    343   if (NULL == cc->authorization)
    344   {
    345     MHD_RESULT ret;
    346 
    347     ret = TALER_MHD_reply_with_error (
    348       connection,
    349       MHD_HTTP_INTERNAL_SERVER_ERROR,
    350       TALER_EC_GENERIC_METHOD_INVALID,
    351       cc->challenge_method);
    352     return ret;
    353   }
    354 
    355   /* use plugin to check if challenge data is valid! */
    356   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    357               "No challenge provided, creating fresh challenge\n");
    358   {
    359     enum GNUNET_GenericReturnValue ret;
    360 
    361     ret = cc->authorization->validate (cc->authorization->cls,
    362                                        connection,
    363                                        cc->challenge_method,
    364                                        cc->challenge_data,
    365                                        strlen (cc->challenge_data));
    366     switch (ret)
    367     {
    368     case GNUNET_OK:
    369       /* data valid, continued below */
    370       break;
    371     case GNUNET_NO:
    372       /* data invalid, reply was queued */
    373       return MHD_YES;
    374     case GNUNET_SYSERR:
    375       /* data invalid, reply was NOT queued */
    376       return MHD_NO;
    377     }
    378   }
    379 
    380   /* Setup challenge and begin authorization process */
    381   {
    382     struct GNUNET_TIME_Timestamp transmission_date;
    383     enum GNUNET_DB_QueryStatus qs;
    384 
    385     qs = db->create_challenge_code (db->cls,
    386                                     &cc->challenge_id,
    387                                     cc->authorization->code_rotation_period,
    388                                     cc->authorization->code_validity_period,
    389                                     cc->authorization->retry_counter,
    390                                     &transmission_date,
    391                                     &cc->code);
    392 
    393     switch (qs)
    394     {
    395     case GNUNET_DB_STATUS_HARD_ERROR:
    396     case GNUNET_DB_STATUS_SOFT_ERROR:
    397       GNUNET_break (0);
    398       return TALER_MHD_reply_with_error (cc->connection,
    399                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    400                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    401                                          "create_challenge_code");
    402     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    403       /* 0 == retry_counter of existing challenge => rate limit exceeded */
    404       return reply_rate_limited (cc);
    405     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    406       /* challenge code was stored successfully*/
    407       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    408                   "Created fresh challenge\n");
    409       break;
    410     }
    411 
    412     if (GNUNET_TIME_relative_cmp (
    413           GNUNET_TIME_absolute_get_duration (
    414             transmission_date.abs_time),
    415           <,
    416           cc->authorization->code_retransmission_frequency) )
    417     {
    418       /* Too early for a retransmission! */
    419       return TALER_MHD_REPLY_JSON_PACK (
    420         cc->connection,
    421         MHD_HTTP_OK,
    422         GNUNET_JSON_pack_string ("challenge_type",
    423                                  "TAN_ALREADY_SENT"));
    424     }
    425   }
    426 
    427   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    428               "Beginning authorization process\n");
    429   cc->as = cc->authorization->start (cc->authorization->cls,
    430                                      &FH_trigger_daemon,
    431                                      NULL,
    432                                      &cc->challenge_id,
    433                                      cc->code,
    434                                      cc->challenge_data,
    435                                      strlen (cc->challenge_data));
    436 
    437   if (NULL == cc->as)
    438   {
    439     GNUNET_break (0);
    440     return TALER_MHD_reply_with_error (cc->connection,
    441                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    442                                        TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
    443                                        NULL);
    444   }
    445 
    446   return run_authorization_process (connection,
    447                                     cc);
    448 }