challenger

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

commit f857c916e86f2ff4ff12f8fed51646ebb2f59082
parent cd01a7898a49f86ee470cfe6233de0a9209944a9
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 14:44:55 +0200

verify /token code MAC first, ensure all failures result in 400 invalid_grant to minimize information disclosure

Diffstat:
Msrc/challenger/challenger-httpd_token.c | 138++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/challenger/meson.build | 1+
2 files changed, 57 insertions(+), 82 deletions(-)

diff --git a/src/challenger/challenger-httpd_token.c b/src/challenger/challenger-httpd_token.c @@ -468,13 +468,18 @@ CH_handler_token (struct CH_HandlerContext *hc, case GNUNET_DB_STATUS_SOFT_ERROR: continue; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Unknown, expired, foreign or unsolved validation. RFC 6749 5.2 + has exactly one error for all of these, and we must not say which + one it was: the nonce is recoverable from any code, so a + distinguishable answer would let anybody track the progress of a + user's validation. */ GNUNET_break_op (0); return CH_reply_with_oauth_error ( hc->connection, MHD_HTTP_BAD_REQUEST, "invalid_grant", - TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, - "get_validation_pkce"); + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, + NULL); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } @@ -508,17 +513,61 @@ CH_handler_token (struct CH_HandlerContext *hc, "Invalid code_challenge_method"); } + /* Authenticate the authorization code *before* looking at anything else + about the validation. CH_code_to_nonce() only parses the nonce out of + the code, so every check that runs before the MAC comparison answers + questions about a validation the caller may not hold a code for. All + failures below therefore collapse into one indistinguishable + 'invalid_grant' (RFC 6749 5.2). */ + if (NULL == address) + { + /* Cannot happen: only solved validations are returned, and those + always carry an address. Answered like a bad code regardless. */ + GNUNET_break_op (0); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_uri); + GNUNET_free (client_state); + GNUNET_free (code_challenge); + return CH_reply_with_oauth_error ( + hc->connection, + MHD_HTTP_BAD_REQUEST, + "invalid_grant", + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, + NULL); + } + code = CH_compute_code (&bc->nonce, + client_secret, + client_scope, + address, + client_redirect_uri); + json_decref (address); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_uri); + GNUNET_free (client_state); + if (0 != + TALER_strcmp_ct (code, + bc->code)) + { + GNUNET_break_op (0); + GNUNET_free (code); + GNUNET_free (code_challenge); + return CH_reply_with_oauth_error ( + hc->connection, + MHD_HTTP_BAD_REQUEST, + "invalid_grant", + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, + NULL); + } + GNUNET_free (code); + /* Verify the code_challenge if present*/ if (NULL != code_challenge) { if (NULL == bc->code_verifier) { GNUNET_break_op (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -536,11 +585,6 @@ CH_handler_token (struct CH_HandlerContext *hc, (cv_len > 128) ) { GNUNET_break_op (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -556,11 +600,6 @@ CH_handler_token (struct CH_HandlerContext *hc, if (! TALER_url_is_reserved (c)) continue; GNUNET_break_op (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -587,11 +626,6 @@ CH_handler_token (struct CH_HandlerContext *hc, 0)) { GNUNET_break (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -620,11 +654,6 @@ CH_handler_token (struct CH_HandlerContext *hc, (NULL == encoded_hash) ) { GNUNET_break (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); GNUNET_free (encoded_hash); return CH_reply_with_oauth_error ( @@ -640,11 +669,6 @@ CH_handler_token (struct CH_HandlerContext *hc, code_challenge)) { GNUNET_break_op (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); GNUNET_free (encoded_hash); return CH_reply_with_oauth_error ( @@ -663,11 +687,6 @@ CH_handler_token (struct CH_HandlerContext *hc, code_challenge)) { GNUNET_break_op (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -680,11 +699,6 @@ CH_handler_token (struct CH_HandlerContext *hc, case CHALLENGER_CM_UNKNOWN: case CHALLENGER_CM_EMPTY: GNUNET_break (0); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); return CH_reply_with_oauth_error ( hc->connection, @@ -694,47 +708,7 @@ CH_handler_token (struct CH_HandlerContext *hc, "Database has empty or unknown challenge mode but with code_challenge"); } } - - if (NULL == address) - { - GNUNET_break_op (0); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); - GNUNET_free (code_challenge); - return CH_reply_with_oauth_error ( - hc->connection, - MHD_HTTP_CONFLICT, - "invalid_request", - TALER_EC_CHALLENGER_MISSING_ADDRESS, - "code"); - } - code = CH_compute_code (&bc->nonce, - client_secret, - client_scope, - address, - client_redirect_uri); - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); GNUNET_free (code_challenge); - if (0 != - TALER_strcmp_ct (code, - bc->code)) - { - GNUNET_break_op (0); - GNUNET_free (code); - return CH_reply_with_oauth_error ( - hc->connection, - MHD_HTTP_BAD_REQUEST, - "invalid_grant", - TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, - "code"); - } - GNUNET_free (code); } { diff --git a/src/challenger/meson.build b/src/challenger/meson.build @@ -15,6 +15,7 @@ check_SCRIPTS = [ 'test-challenger-badutf8', 'test-challenger-pinlimit', 'test-challenger-db-retry', + 'test-challenger-token-errors', ] test_helper_cat = configure_file(input: 'cat.sh', output: 'cat.sh', copy: true)