challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

commit 9f66bf52f2a367ff6ab6be019229ba03dace5c9f
parent 827ab3c1f394e40b528a272ed2040e5a59551886
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Tue, 14 Jul 2026 18:58:51 +0200

fix retransmission logic

Diffstat:
Msrc/challenger/challenger-httpd.c | 13+++++++++++++
Msrc/challenger/challenger-httpd_authorize.c | 2+-
Msrc/challenger/challenger-httpd_challenge.c | 4++--
Msrc/challenger/challenger.conf | 6++++++
Msrc/challengerdb/challenge_set_address_and_pin.c | 46+++++++++++++++++++++++++++++++---------------
Msrc/challengerdb/challenger_do_challenge_set_address_and_pin.sql | 11++++++++---
Msrc/include/challenger-database/challenge_set_address_and_pin.h | 7++++---
7 files changed, 65 insertions(+), 24 deletions(-)

diff --git a/src/challenger/challenger-httpd.c b/src/challenger/challenger-httpd.c @@ -566,6 +566,19 @@ run (void *cls, CH_token_expiration = GNUNET_TIME_UNIT_HOURS; } if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (config, + "CHALLENGER", + "PIN_RETRANSMISSION_FREQUENCY", + &CH_pin_retransmission_frequency)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "CHALLENGER", + "PIN_RETRANSMISSION_FREQUENCY"); + CH_pin_retransmission_frequency + = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, + 5); + } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (config, "CHALLENGER", "AUTH_COMMAND", diff --git a/src/challenger/challenger-httpd_authorize.c b/src/challenger/challenger-httpd_authorize.c @@ -325,7 +325,7 @@ CH_handler_authorize (struct CH_HandlerContext *hc, GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add ( last_tx_time, - CH_validation_duration))), + CH_pin_retransmission_frequency))), GNUNET_JSON_pack_uint64 ("changes_left", address_attempts_left) ); diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c @@ -838,7 +838,7 @@ CH_handler_challenge (struct CH_HandlerContext *hc, qs = CHALLENGERDB_challenge_set_address_and_pin (CH_context, &bc->nonce, bc->address, - CH_validation_duration, + CH_pin_retransmission_frequency, &bc->tan, &bc->state, &bc->last_tx_time, @@ -966,7 +966,7 @@ CH_handler_challenge (struct CH_HandlerContext *hc, GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add ( bc->last_tx_time, - CH_validation_duration))) + CH_pin_retransmission_frequency))) ); http_status = MHD_HTTP_OK; resp = TALER_MHD_make_json_steal (args); diff --git a/src/challenger/challenger.conf b/src/challenger/challenger.conf @@ -30,6 +30,12 @@ VALIDATION_DURATION = 1d # How long is an validation valid? VALIDATION_EXPIRATION = 365d +# Minimum time between two (re)transmissions of the PIN/TAN to the +# same address. The user may request the challenge be re-sent, but +# not more often than this. Must be (much) smaller than +# VALIDATION_DURATION. +PIN_RETRANSMISSION_FREQUENCY = 5 min + # Base URL of our service. Must end with '/'. #BASE_URL = https://challenger.DOMAIN/ BASE_URL = http://localhost:9967/ diff --git a/src/challengerdb/challenge_set_address_and_pin.c b/src/challengerdb/challenge_set_address_and_pin.c @@ -27,28 +27,44 @@ enum GNUNET_DB_QueryStatus -CHALLENGERDB_challenge_set_address_and_pin (struct CHALLENGERDB_PostgresContext *ctx, - const struct CHALLENGER_ValidationNonceP *nonce, - const json_t *address, - struct GNUNET_TIME_Relative validation_duration, - uint32_t *tan, - char **state, - struct GNUNET_TIME_Absolute *last_tx_time, - uint32_t *auth_attempts_left, - bool *pin_transmit, - char **client_redirect_uri, - bool *address_refused, - bool *solved) +CHALLENGERDB_challenge_set_address_and_pin (struct + CHALLENGERDB_PostgresContext *ctx, + const struct + CHALLENGER_ValidationNonceP *nonce, + const json_t *address, + struct GNUNET_TIME_Relative + retransmission_frequency, + uint32_t *tan, + char **state, + struct GNUNET_TIME_Absolute * + last_tx_time, + uint32_t *auth_attempts_left, + bool *pin_transmit, + char **client_redirect_uri, + bool *address_refused, + bool *solved) { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_TIME_Absolute next_tx_time + /* We must gate retransmission on + last_tx_time + retransmission_frequency <= now + but 'last_tx_time' is only read (under FOR UPDATE) inside the stored + procedure, so we cannot form that sum here. We therefore pass the + equivalent, inverted form: the *newest* 'last_tx_time' for which a + retransmission is still due. The SQL then merely checks + last_tx_time <= retransmit_cutoff. + Note that this is deliberately a subtraction from 'now', not + 'now + retransmission_frequency': the value is compared against a + timestamp in the *past*. GNUNET_TIME_absolute_subtract() saturates at + zero rather than underflowing, so a FOREVER frequency correctly + degrades to "never retransmit". */ + struct GNUNET_TIME_Absolute retransmit_cutoff = GNUNET_TIME_absolute_subtract (now, - validation_duration); + retransmission_frequency); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (nonce), TALER_PQ_query_param_json (address), - GNUNET_PQ_query_param_absolute_time (&next_tx_time), + GNUNET_PQ_query_param_absolute_time (&retransmit_cutoff), GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_uint32 (tan), GNUNET_PQ_query_param_end diff --git a/src/challengerdb/challenger_do_challenge_set_address_and_pin.sql b/src/challengerdb/challenger_do_challenge_set_address_and_pin.sql @@ -15,10 +15,14 @@ -- -CREATE OR REPLACE FUNCTION challenger_do_challenge_set_address_and_pin ( +DROP FUNCTION IF EXISTS challenger_do_challenge_set_address_and_pin; +CREATE FUNCTION challenger_do_challenge_set_address_and_pin ( IN in_nonce BYTEA, IN in_address TEXT, - IN in_next_tx_time INT8, + -- Newest last_tx_time for which a (re)transmission is still due, that is + -- 'now - retransmission_frequency'. Computed by the caller because + -- last_tx_time is only known here; see challenge_set_address_and_pin.c. + IN in_retransmit_cutoff INT8, IN in_now INT8, IN in_tan INT4, OUT out_not_found BOOLEAN, @@ -103,8 +107,9 @@ THEN END IF; IF ( (my_status.pin_transmissions_left > 0) AND - (my_status.last_tx_time <= in_next_tx_time) ) + (my_status.last_tx_time <= in_retransmit_cutoff) ) THEN + -- enough time has passed since the last transmission, so -- we are changing the PIN, update counters my_status.pin_transmissions_left = my_status.pin_transmissions_left - 1; my_status.last_pin = in_tan; diff --git a/src/include/challenger-database/challenge_set_address_and_pin.h b/src/include/challenger-database/challenge_set_address_and_pin.h @@ -36,10 +36,11 @@ * @param cls * @param nonce unique nonce to use to identify the validation * @param address the new address to validate - * @param validation_duration minimum time between transmissions + * @param retransmission_frequency minimum time that must have passed since the + * last transmission before the PIN is (re)transmitted to @a address again * @param[in,out] tan set to the PIN/TAN last send to @a address, input should be random PIN/TAN to use if address did not change * @param[out] state set to client's OAuth2 state if available - * @param[out] last_tx_time set to the last time when we (presumably) send a PIN to @a address, input should be current time to use if the existing value for tx_time is past @a next_tx_time + * @param[out] last_tx_time set to the last time when we (presumably) send a PIN to @a address * @param[out] pin_transmit set to true if we should transmit the @a last_pin to the @a address * @param[out] auth_attempts_left set to number of attempts the user has left on this pin * @param[out] client_redirect_uri redirection URI of the client (for reporting failures) @@ -57,7 +58,7 @@ CHALLENGERDB_challenge_set_address_and_pin ( struct CHALLENGERDB_PostgresContext *ctx, const struct CHALLENGER_ValidationNonceP *nonce, const json_t *address, - struct GNUNET_TIME_Relative validation_duration, + struct GNUNET_TIME_Relative retransmission_frequency, uint32_t *tan, char **state, struct GNUNET_TIME_Absolute *last_tx_time,