summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/authorization/anastasis_authorization_plugin_totp.c47
-rw-r--r--src/backend/anastasis-httpd_truth.c374
-rw-r--r--src/include/anastasis_database_plugin.h2
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c9
4 files changed, 300 insertions, 132 deletions
diff --git a/src/authorization/anastasis_authorization_plugin_totp.c b/src/authorization/anastasis_authorization_plugin_totp.c
index 6fcdd39..ee1ab3f 100644
--- a/src/authorization/anastasis_authorization_plugin_totp.c
+++ b/src/authorization/anastasis_authorization_plugin_totp.c
@@ -59,14 +59,14 @@ struct ANASTASIS_AUTHORIZATION_State
struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
/**
- * Our context.
+ * Was the challenge satisfied?
*/
- const struct ANASTASIS_AuthorizationContext *ac;
+ struct GNUNET_HashCode valid_replies[TIME_INTERVAL_RANGE * 2 + 1];
/**
- * Was the challenge satisfied?
+ * Our context.
*/
- bool ok;
+ const struct ANASTASIS_AuthorizationContext *ac;
};
@@ -212,9 +212,9 @@ compute_totp (int time_off,
* @param trigger_cls closure for @a trigger
* @param truth_uuid Identifier of the challenge, to be (if possible) included in the
* interaction with the user
- * @param code set to secret code that the user provided to satisfy the challenge in
- * the main anastasis protocol
- * @param data input to validate (i.e. the shared secret)
+ * @param code always 0 (direct validation, backend does
+ * not generate a code in this mode)
+ * @param data truth for input to validate (i.e. the shared secret)
* @param data_length number of bytes in @a data
* @return state to track progress on the authorization operation, NULL on failure
*/
@@ -230,7 +230,9 @@ totp_start (void *cls,
const struct ANASTASIS_AuthorizationContext *ac = cls;
struct ANASTASIS_AUTHORIZATION_State *as;
uint64_t want;
+ unsigned int off = 0;
+ GNUNET_assert (0 == code);
as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
as->ac = ac;
as->truth_uuid = *truth_uuid;
@@ -241,8 +243,8 @@ totp_start (void *cls,
want = compute_totp (i,
data,
data_length);
- if (code == want)
- as->ok = true;
+ ANASTASIS_hash_answer (want,
+ &as->valid_replies[off++]);
}
return as;
}
@@ -264,9 +266,32 @@ totp_process (struct ANASTASIS_AUTHORIZATION_State *as,
MHD_RESULT mres;
const char *mime;
const char *lang;
+ const char *challenge_response_s;
+ struct GNUNET_HashCode challenge_response;
+
+ challenge_response_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "response");
+ if ( (NULL == challenge_response_s) ||
+ (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (challenge_response_s,
+ &challenge_response)) )
+ {
+ GNUNET_break_op (0);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "response");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
- if (as->ok)
- return ANASTASIS_AUTHORIZATION_RES_FINISHED;
+ }
+ for (unsigned int i = 0; i<=TIME_INTERVAL_RANGE * 2; i++)
+ if (0 ==
+ GNUNET_memcmp (&challenge_response,
+ &as->valid_replies[i]))
+ return ANASTASIS_AUTHORIZATION_RES_FINISHED;
mime = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT);
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,
diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h
index 7bf91a2..cf5a69a 100644
--- a/src/include/anastasis_database_plugin.h
+++ b/src/include/anastasis_database_plugin.h
@@ -633,7 +633,7 @@ struct ANASTASIS_DatabasePlugin
(*mark_challenge_code_satisfied)(
void *cls,
const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
- const uint64_t code);
+ uint64_t code);
/**
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index 897a6dd..8709cf9 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -1684,10 +1684,13 @@ select_challenge_cb (void *cls,
json_object_set_new (sctx->state,
"selected_challenge_uuid",
GNUNET_JSON_from_data_auto (&cd->uuid)));
- if (0 == strcmp ("question",
- cd->type))
+ if ( (0 == strcmp ("question",
+ cd->type)) ||
+ (0 == strcmp ("totp",
+ cd->type)) )
{
- /* security question, immediately request user to answer it */
+ /* security question or TOTP:
+ immediately request user to answer it */
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
sctx->cb (sctx->cb_cls,