challenger

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

commit 7e1fc8ae32812a6529c631650b5f7fa6c83ba5fd
parent 8e90d4f613c0347922dbe1eb73f5ef9287bf1743
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 27 Apr 2023 01:31:52 +0200

more work on challenger skeleton

Diffstat:
Msrc/challenger/challenger-httpd_challenge.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/challenger/challenger-httpd_login.c | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/include/challenger_database_plugin.h | 75+++++++++++++++++++++++++++++++--------------------------------------------
3 files changed, 287 insertions(+), 53 deletions(-)

diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c @@ -37,6 +37,16 @@ struct ChallengeContext * Handle for processing uploaded data. */ struct MHD_PostProcessor *pp; + + /** + * 0-terminated address information submitted to us. + */ + char *addressp; + + /** + * Number of bytes in @a address, excluding 0-terminator. + */ + size_t address_len; }; @@ -90,9 +100,23 @@ post_iter (void *cls, { struct ChallengeContext *bc = cls; - (void) bc; - GNUNET_break (0); - return MHD_NO; + (void) filename; + (void) content_type; + (void) transfer_encoding; + (void) off; + if (0 != strcmp (key, + "address")) + return MHD_YES; + if (MHD_POSTDATA_KIND != kind) + return MHD_YES; + bc->address = GNUNET_realloc (bc->address, + bc->address_len + size + 1); + memcpy (bc->address + bc->address_len, + data, + size); + bc->address_len += size; + bc->address[bc->address_len] = '\0'; + return MHD_YES; } @@ -131,6 +155,75 @@ CH_handler_challenge (struct CH_HandlerContext *hc, return MHD_NO; } - /* FIXME: generate proper response */ - return MHD_NO; + { + struct GNUNET_TIME_Absolute last_tx_time + = GNUNET_TIME_absolute_get (); + uint32_t last_pin + = GNUNET_CRYPTO_random_uint32 (GNUNET_CRYPTO_RANDOM_NONCE) % 100000000; + uint32_t pin_transmissions_left; + uint32_t pin_attempts_left = 3; /* if addr is new */ + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute next_tx_time; + + next_tx_time = GNUNET_TIME_absolute_subtract (last_tx_time, + get_duration); + qs = db->challenge_set_address_and_pin (db->cls, + &nonce, + address, + next_tx_time, + &last_tx, + &last_pin, + &pin_transmissions_left, + &pin_attempts_left); + switch (qs) + { + case GNUNET_DB_SUCCESS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "internal-server-error.must", + NULL); + case GNUNET_DB_SUCCESS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_SUCCESS_NO_RESULTS: + /* nonce unknown */ + return TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_NOT_FOUND, + "validation-unknown.must", + NULL); + case GNUNET_DB_SUCCESS_ONE_RESULT: + break; + } + if (0 == pin_attempts_left) + { + return TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_XXX, + "attempts-exhausted.must", + NULL); + } + if ( (GNUNET_TIME_relative_cmp (duration, + (>), + CH_pin_retransmission_frequency)) && + (pin_transmissions_left > 0) ) + { + /* Retransmit PIN */ + } + { + MHD_RESULT res; + json_t *args; + + args = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("attempts_left", + pin_attempts_left), + GNUNET_JSON_pack_absolute_time ("next_tx_time", + next_tx_time), + ); + res = TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_OK, + "enter-pin-form.must", + args); + return res; + } + } } diff --git a/src/challenger/challenger-httpd_login.c b/src/challenger/challenger-httpd_login.c @@ -29,8 +29,162 @@ CH_handler_login (struct CH_HandlerContext *hc, const char *upload_data, size_t *upload_data_size) { - return TALER_MHD_reply_with_error (hc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + const char *response_type; + unsigned long long client_id; + const char *redirect_uri; + const char *state; + const char *scope; + struct CHALLENGER_ValidationNonceP nonce; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (hc->path, + strlen (hc->path), + &nonce, + sizeof (nonce))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_PARAMETER_MISSING, + hc->path); + } + response_type + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "response_type"); + if (NULL == response_type) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "response_type"); + } + if (0 != strcmp (response_type, + "code")) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "response_type (mus be 'code')"); + } + + { + const char *client_id_str; + char dummy; + + client_id_str + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "client_id"); + if (NULL == client_id_str) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "client_id"); + } + if (1 != sscanf (client_id_str, + "%llu%c", + &client_id, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "client_id"); + } + } + redirect_uri + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "redirect_uri"); + if ( (NULL != redirect_uri) && + (0 != strncmp (redirect_uri, + "http://", + strlen ("http://"))) && + (0 != strncmp (redirect_uri, + "https://", + strlen ("https://"))) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "redirect_uri (has to start with 'http://' or 'https://')"); + } + state + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "state"); + if (NULL == state) + state = ""; + scope + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "scope"); + (void) scope; /* ignored */ + { + struct GNUNET_TIME_Absolute last_tx_time; + uint32_t pin_attempts_left; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute next_tx_time; + + qs = db->validate_login_start (db->cls, + &nonce, + client_scope, + client_state, + client_redirect_url, + &last_tx, + &pin_attempts_left); + switch (qs) + { + case GNUNET_DB_SUCCESS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "internal-server-error.must", + NULL); + case GNUNET_DB_SUCCESS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_SUCCESS_NO_RESULTS: + /* nonce unknown */ + return TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_NOT_FOUND, + "validation-unknown.must", + NULL); + case GNUNET_DB_SUCCESS_ONE_RESULT: + break; + } + if (0 == pin_attempts_left) + { + /* FIXME: return human-readable HTML saying that they tried to often */ + return TALER_MHD_reply_with_html (connection, + MHD_HTTP_FORBIDDEN, + "too-many-attempts.must", + NULL /* OK? or pass empty object? */); + } + next_tx_time = GNUNET_TIME_absolute_add (last_tx_time, + CH_pin_retransmission_frequency); + { + MHD_RESULT res; + json_t *args; + + args = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("attempts_left", + pin_attempts_left), + GNUNET_JSON_pack_absolute_time ("next_tx_time", + next_tx_time), + ); + res = TALER_MHD_reply_with_html (hc->connection, + MHD_HTTP_OK, + "enter-address-form.must", + args); + return res; + } + } } diff --git a/src/include/challenger_database_plugin.h b/src/include/challenger_database_plugin.h @@ -199,10 +199,10 @@ struct CHALLENGER_DatabasePlugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*validation_setup)(void *cls, - uint64_t client_id, - const struct CHALLENGER_ValidationNonceP *nonce, - struct GNUNET_TIME_Absolute expiration_time); + (*setup_nonce)(void *cls, + uint64_t client_id, + const struct CHALLENGER_ValidationNonceP *nonce, + struct GNUNET_TIME_Absolute expiration_time); /** @@ -213,66 +213,53 @@ struct CHALLENGER_DatabasePlugin * * @param cls * @param nonce unique nonce to use to identify the validation - * @param address the new address to validate * @param client_scope scope of the validation * @param client_state state of the client * @param client_redirect_url where to redirect at the end, NULL to use a unique one registered for the client * @param[out] last_tx_time set to the last time when we (presumably) send a PIN to @a address; 0 if never sent - * @param[out] last_pin set to the PIN last send to @a address, 0 if never sent - * @param[in,out] pin_attempts_left set to number of PIN transmission attempts left for this address; input is value to be used if address is new, output is possibly different if address was not new + * @param[out] pin_attempts_left set to number of PIN transmission attempts left for this address; input is value to be used if address is new, output is possibly different if address was not new * @return transaction status: * #GNUNET_DB_SUCCESS_ONE_RESULT if the address was changed * #GNUNET_DB_SUCCESS_NO_RESULTS if we do not permit further changes to the address (attempts exhausted) * #GNUNET_DB_SUCCESS_HARD_ERROR on failure */ enum GNUNET_DB_QueryStatus - (*validate_login_address)(void *cls, - const struct CHALLENGER_ValidationNonceP *nonce, - const char *address, - const char *client_scope, - const char *client_state, - const char *client_redirect_url, - struct GNUNET_TIME_Absolute *last_tx_time, - uint32_t *last_pin, - uint32_t *pin_attempts_left); - - - /** - * Store a new PIN to be used to validate an address. - * - * @param cls - * @param nonce unique nonce to use to identify the validation - * @param tx_time the current time - * @param new_pin the PIN we are sending - * @param auth_attempts_allowed how many attempts do we give to the user to enter the correct PIN - * @return transaction status: - * #GNUNET_DB_SUCCESS_ONE_RESULT if the pin was stored - * #GNUNET_DB_SUCCESS_NO_RESULTS if we do not know the @a nonce or if pin attempts left is zero - * #GNUNET_DB_SUCCESS_HARD_ERROR on failure - */ - enum GNUNET_DB_QueryStatus - (*validate_login_pin)(void *cls, - const struct CHALLENGER_ValidationNonceP *nonce, - struct GNUNET_TIME_Absolute tx_time, - uint32_t new_pin, - uint32_t auth_attempts_allowed); + (*login_start)(void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + const char *client_scope, + const char *client_state, + const char *client_redirect_url, + struct GNUNET_TIME_Absolute *last_tx_time, + uint32_t *pin_attempts_left); /** - * Check if challenge is pending to validate an address. + * Set the user-provided address in a validation process. Updates + * the address and decrements the "addresses left" counter. If the + * address did not change, the operation is successful even without + * the counter change. * * @param cls * @param nonce unique nonce to use to identify the validation - * @param[out] is_open set to true if a challenge was sent + * @param address the new address to validate + * @param next_tx_time tx time we must have sent earlier before to retransmit now + * @param[in,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[in,out] last_pin set to the PIN last send to @a address, input should be random PIN to use if address did not change + * @param[in,out] pin_attempts_left set to number of PIN transmission attempts left for this address; input is value to be used if address is new, output is possibly different if address was not new * @return transaction status: - * #GNUNET_DB_SUCCESS_ONE_RESULT if the nonce was found - * #GNUNET_DB_SUCCESS_NO_RESULTS if we do not know the nonce + * #GNUNET_DB_SUCCESS_ONE_RESULT if the address was changed + * #GNUNET_DB_SUCCESS_NO_RESULTS if we do not permit further changes to the address (attempts exhausted) * #GNUNET_DB_SUCCESS_HARD_ERROR on failure */ enum GNUNET_DB_QueryStatus - (*validate_challenge_open)(void *cls, - const struct CHALLENGER_ValidationNonceP *nonce, - bool *is_open); + (*challenge_set_address_and_pin)( + void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + const char *address, + struct GNUNET_TIME_Absolute next_tx_time, + struct GNUNET_TIME_Absolute *last_tx_time, + uint32_t *last_pin, + uint32_t *pin_attempts_left); /**