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 }