diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/anastasis-httpd_truth.c | 374 |
1 files changed, 257 insertions, 117 deletions
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c index 4dd3ddc..aedd0a2 100644 --- a/src/backend/anastasis-httpd_truth.c +++ b/src/backend/anastasis-httpd_truth.c @@ -882,6 +882,38 @@ return_key_share ( /** + * Mark @a gc as suspended and update the respective + * data structures and jobs. + * + * @param[in,out] gc context of the suspended operation + */ +static void +gc_suspended (struct GetContext *gc) +{ + gc->suspended = true; + if (NULL == AH_to_heap) + AH_to_heap = GNUNET_CONTAINER_heap_create ( + GNUNET_CONTAINER_HEAP_ORDER_MIN); + gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, + gc, + gc->timeout.abs_value_us); + if (NULL != to_task) + { + GNUNET_SCHEDULER_cancel (to_task); + to_task = NULL; + } + { + struct GetContext *rn; + + rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); + to_task = GNUNET_SCHEDULER_add_at (rn->timeout, + &do_timeout, + NULL); + } +} + + +/** * Run the authorization method-specific 'process' function and continue * based on its result with generating an HTTP response. * @@ -923,26 +955,7 @@ run_authorization_process (struct MHD_Connection *connection, return MHD_YES; case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: /* connection was suspended */ - gc->suspended = true; - if (NULL == AH_to_heap) - AH_to_heap = GNUNET_CONTAINER_heap_create ( - GNUNET_CONTAINER_HEAP_ORDER_MIN); - gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, - gc, - gc->timeout.abs_value_us); - if (NULL != to_task) - { - GNUNET_SCHEDULER_cancel (to_task); - to_task = NULL; - } - { - struct GetContext *rn; - - rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); - to_task = GNUNET_SCHEDULER_add_at (rn->timeout, - &do_timeout, - NULL); - } + gc_suspended (gc); return MHD_YES; case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: /* Challenge sent successfully */ @@ -978,6 +991,212 @@ run_authorization_process (struct MHD_Connection *connection, } +/** + * Use the database to rate-limit queries to the + * authentication procedure, but without actually + * storing 'real' challenge codes. + * + * @param[in,out] gc context to rate limit requests for + * @return #GNUNET_OK if rate-limiting passes, + * #GNUNET_NO if a reply was sent (rate limited) + * #GNUNET_SYSERR if we failed and no reply + * was queued + */ +static enum GNUNET_GenericReturnValue +rate_limit (struct GetContext *gc) +{ + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute rt; + uint64_t code; + enum ANASTASIS_DB_CodeStatus cs; + struct GNUNET_HashCode hc; + bool satisfied; + uint64_t dummy; + + rt = GNUNET_TIME_UNIT_FOREVER_ABS; + qs = db->create_challenge_code (db->cls, + &gc->truth_uuid, + MAX_QUESTION_FREQ, + GNUNET_TIME_UNIT_HOURS, + INITIAL_RETRY_COUNTER, + &rt, + &code); + if (0 > qs) + { + GNUNET_break (0 < qs); + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "create_challenge_code (for rate limiting)")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, + NULL)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + /* decrement trial counter */ + ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ + &hc); + cs = db->verify_challenge_code (db->cls, + &gc->truth_uuid, + &hc, + &dummy, + &satisfied); + switch (cs) + { + case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: + /* good, what we wanted */ + return GNUNET_OK; + case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: + case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: + GNUNET_break (0); + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "verify_challenge_code")) + ? GNUNET_NO + : GNUNET_SYSERR; + case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, + NULL)) + ? GNUNET_NO + : GNUNET_SYSERR; + case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: + /* this should be impossible, we used code+1 */ + GNUNET_assert (0); + } + return GNUNET_SYSERR; +} + + +/** + * Handle special case of a security question where we do not + * generate a code. Rate limits answers against brute forcing. + * + * @param[in,out] gc request to handle + * @param decrypted_truth hash to check against + * @param decrypted_truth_size number of bytes in @a decrypted_truth + * @return MHD status code + */ +static MHD_RESULT +handle_security_question (struct GetContext *gc, + const void *decrypted_truth, + size_t decrypted_truth_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling security question challenge\n"); + if (! gc->have_response) + { + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED, + NULL); + } + /* rate limit */ + { + enum GNUNET_GenericReturnValue ret; + + ret = rate_limit (gc); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + /* check reply matches truth */ + if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) || + (0 != memcmp (&gc->challenge_response, + decrypted_truth, + decrypted_truth_size)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Wrong answer provided to secure question had %u bytes, wanted %u\n", + (unsigned int) decrypted_truth_size, + (unsigned int) sizeof (struct GNUNET_HashCode)); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, + NULL); + } + /* good, return the key share */ + return return_key_share (&gc->truth_uuid, + gc->connection); +} + + +/** + * Handle special case of an answer being directly checked by the + * plugin and not by our database. Rate limits answers against brute + * forcing. + * + * @param[in,out] gc request to handle + * @param decrypted_truth hash to check against + * @param decrypted_truth_size number of bytes in @a decrypted_truth + * @return MHD status code + */ +static MHD_RESULT +direct_validation (struct GetContext *gc, + const void *decrypted_truth, + size_t decrypted_truth_size) +{ + /* Non-random code, call plugin directly! */ + enum ANASTASIS_AUTHORIZATION_Result aar; + enum GNUNET_GenericReturnValue res; + + res = rate_limit (gc); + if (GNUNET_OK != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + gc->as = gc->authorization->start (gc->authorization->cls, + &AH_trigger_daemon, + NULL, + &gc->truth_uuid, + 0LLU, + decrypted_truth, + decrypted_truth_size); + if (NULL == gc->as) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + NULL); + } + aar = gc->authorization->process (gc->as, + GNUNET_TIME_UNIT_ZERO_ABS, + gc->connection); + switch (aar) + { + case ANASTASIS_AUTHORIZATION_RES_SUCCESS: + GNUNET_break (0); + return MHD_YES; + case ANASTASIS_AUTHORIZATION_RES_FAILED: + return MHD_YES; + case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: + gc_suspended (gc); + return MHD_YES; + case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: + GNUNET_break (0); + return MHD_NO; + case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED: + return MHD_NO; + case ANASTASIS_AUTHORIZATION_RES_FINISHED: + return return_key_share (&gc->truth_uuid, + gc->connection); + } + GNUNET_break (0); + return MHD_NO; +} + + MHD_RESULT AH_handler_truth_get ( struct MHD_Connection *connection, @@ -1113,7 +1332,6 @@ AH_handler_truth_get ( GNUNET_TIME_UNIT_SECONDS); } } - } /* end of first-time initialization (if NULL == gc) */ else { @@ -1291,104 +1509,14 @@ AH_handler_truth_get ( but check that the hash matches */ if (is_question) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling security question challenge\n"); - if (! gc->have_response) - { - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED, - NULL); - } + MHD_RESULT ret; - { - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Absolute rt; - uint64_t code; - enum ANASTASIS_DB_CodeStatus cs; - struct GNUNET_HashCode hc; - bool satisfied; - uint64_t dummy; - - rt = GNUNET_TIME_UNIT_FOREVER_ABS; - qs = db->create_challenge_code (db->cls, - &gc->truth_uuid, - MAX_QUESTION_FREQ, - GNUNET_TIME_UNIT_HOURS, - INITIAL_RETRY_COUNTER, - &rt, - &code); - if (0 > qs) - { - GNUNET_break (0 < qs); - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "create_challenge_code (for rate limiting)"); - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); - } - /* decrement trial counter */ - ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ - &hc); - cs = db->verify_challenge_code (db->cls, - &gc->truth_uuid, - &hc, - &dummy, - &satisfied); - switch (cs) - { - case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: - /* good, what we wanted */ - break; - case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: - case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "verify_challenge_code"); - case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); - case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: - /* this should be impossible, we used code+1 */ - GNUNET_assert (0); - } - } - if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) || - (0 != memcmp (&gc->challenge_response, - decrypted_truth, - decrypted_truth_size)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Wrong answer provided to secure question had %u bytes, wanted %u\n", - (unsigned int) decrypted_truth_size, - (unsigned int) sizeof (struct GNUNET_HashCode)); - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, - NULL); - } - GNUNET_free (decrypted_truth); + ret = handle_security_question (gc, + decrypted_truth, + decrypted_truth_size); GNUNET_free (truth_mime); - return return_key_share (&gc->truth_uuid, - connection); + GNUNET_free (decrypted_truth); + return ret; } /* Not security question, check for answer in DB */ @@ -1399,6 +1527,18 @@ AH_handler_truth_get ( uint64_t code; GNUNET_free (truth_mime); + if (gc->authorization->user_provided_code) + { + MHD_RESULT res; + + res = direct_validation (gc, + decrypted_truth, + decrypted_truth_size); + GNUNET_free (decrypted_truth); + return res; + } + + /* random code, check against database */ cs = db->verify_challenge_code (db->cls, &gc->truth_uuid, &gc->challenge_response, |