challenger

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

commit 82780cee92875a6278ae33f70b3f008de7a54ba1
parent e5f0aa0e4b92febfbeb29f639ce616bc51119ca3
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 26 Apr 2026 09:31:35 +0200

handle case where challenge is revisited after being solved correctly, add test for it

Diffstat:
Msrc/challenger/Makefile.am | 3++-
Msrc/challenger/challenger-httpd_challenge.c | 81+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/challenger/challenger-httpd_common.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/challenger/challenger-httpd_common.h | 17+++++++++++++++++
Msrc/challenger/challenger-httpd_solve.c | 169++++++++++++++++++-------------------------------------------------------------
Asrc/challenger/test-challenger-revisit.sh | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/challenger/test-challenger.sh | 2++
7 files changed, 411 insertions(+), 172 deletions(-)

diff --git a/src/challenger/Makefile.am b/src/challenger/Makefile.am @@ -29,7 +29,8 @@ bin_SCRIPTS = \ check_SCRIPTS = \ test-challenger.sh \ - test-challenger-pkce.sh + test-challenger-pkce.sh \ + test-challenger-revisit.sh TESTS = \ $(check_SCRIPTS) diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c @@ -337,11 +337,11 @@ send_tan (struct ChallengeContext *bc) enum MHD_Result mres; GNUNET_break (0); - mres = reply_error (bc, - "internal-error", - MHD_HTTP_BAD_GATEWAY, - TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, - "pipe"); + mres = TALER_MHD_reply_with_error ( + bc->hc->connection, + MHD_HTTP_BAD_GATEWAY, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "pipe"); bc->status = (MHD_YES == mres) ? GNUNET_NO : GNUNET_SYSERR; @@ -385,11 +385,11 @@ send_tan (struct ChallengeContext *bc) GNUNET_break (0); GNUNET_break (GNUNET_OK == GNUNET_DISK_pipe_close (p)); - mres = reply_error (bc, - "internal-error", - MHD_HTTP_BAD_GATEWAY, - TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, - "exec"); + mres = TALER_MHD_reply_with_error ( + bc->hc->connection, + MHD_HTTP_BAD_GATEWAY, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "exec"); bc->status = (MHD_YES == mres) ? GNUNET_NO : GNUNET_SYSERR; @@ -421,11 +421,11 @@ send_tan (struct ChallengeContext *bc) enum MHD_Result mres; GNUNET_break (0); - mres = reply_error (bc, - "internal-error", - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE, - NULL); + mres = TALER_MHD_reply_with_error ( + bc->hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE, + NULL); GNUNET_DISK_file_close (pipe_stdin); bc->status = (MHD_YES == mres) ? GNUNET_NO @@ -459,11 +459,11 @@ send_tan (struct ChallengeContext *bc) enum MHD_Result mres; GNUNET_break (0); - mres = reply_error (bc, - "internal-error", - MHD_HTTP_BAD_GATEWAY, - TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, - "write"); + mres = TALER_MHD_reply_with_error ( + bc->hc->connection, + MHD_HTTP_BAD_GATEWAY, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "write"); GNUNET_DISK_file_close (pipe_stdin); GNUNET_free (msg); bc->status = (MHD_YES == mres) @@ -640,11 +640,11 @@ CH_handler_challenge (struct CH_HandlerContext *hc, sizeof (bc->nonce))) { GNUNET_break_op (0); - return reply_error (bc, - "invalid-request", - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_PARAMETER_MISSING, - hc->path); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_PARAMETER_MISSING, + hc->path); } { const char *ct; @@ -698,11 +698,11 @@ CH_handler_challenge (struct CH_HandlerContext *hc, "Helper failed with status %d/%d\n", (int) bc->pst, (int) bc->exit_code); - return reply_error (bc, - "internal-error", - MHD_HTTP_BAD_GATEWAY, - TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, - es); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_BAD_GATEWAY, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + es); } /* handle upload */ if (bc->is_json) @@ -880,21 +880,24 @@ CH_handler_challenge (struct CH_HandlerContext *hc, bc->solved ? "solved" : "unsolved"); if (bc->solved) { + enum GNUNET_GenericReturnValue ret; + char *url; struct MHD_Response *response; - enum MHD_Result ret; - - // FIXME: this "redirect_url" is incomplete, we need to compute - // the full one with 'code' and possibly 'state' as is done - // in challenger-httpd_solve.c! - json_t *args = GNUNET_JSON_PACK ( + json_t *args; + + ret = CH_build_full_redirect_url (&bc->nonce, + hc->connection, + &url); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("type", "completed"), GNUNET_JSON_pack_string ("redirect_url", - bc->client_redirect_uri) + url) ); - + GNUNET_free (url); response = TALER_MHD_make_json (args); - ret = MHD_queue_response (hc->connection, MHD_HTTP_OK, response); diff --git a/src/challenger/challenger-httpd_common.c b/src/challenger/challenger-httpd_common.c @@ -20,6 +20,8 @@ */ #include "platform.h" #include "challenger-httpd_common.h" +#include <gnunet/gnunet_pq_lib.h> +#include "challenger-database/validation_get.h" /** @@ -277,3 +279,83 @@ TALER_MHD_redirect_with_oauth_status ( return ret; } } + + +enum GNUNET_GenericReturnValue +CH_build_full_redirect_url (const struct CHALLENGER_ValidationNonceP *nonce, + struct MHD_Connection *connection, + char **url) +{ + char *client_secret; + json_t *address; + char *client_scope; + char *client_state; + char *client_redirect_uri; + enum GNUNET_DB_QueryStatus qs; + enum MHD_Result ret; + + qs = CHALLENGERDB_validation_get (CH_context, + nonce, + &client_secret, + &address, + &client_scope, + &client_state, + &client_redirect_uri); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "validation_get"); + return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, + NULL); + return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + { + char *code; + + code = CH_compute_code (nonce, + client_secret, + client_scope, + address, + client_redirect_uri); + if (NULL == client_state) + { + GNUNET_asprintf (url, + "%s?code=%s", + client_redirect_uri, + code); + } + else + { + char *url_encoded; + + url_encoded = TALER_urlencode (client_state); + GNUNET_asprintf (url, + "%s?code=%s&state=%s", + client_redirect_uri, + code, + url_encoded); + GNUNET_free (url_encoded); + } + GNUNET_free (code); + } + json_decref (address); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_uri); + GNUNET_free (client_state); + return GNUNET_OK; +} diff --git a/src/challenger/challenger-httpd_common.h b/src/challenger/challenger-httpd_common.h @@ -78,6 +78,23 @@ CH_code_to_nonce (const char *code, /** + * The challenge of @a nonce was solved, build the full redirect URL + * for the client. + * + * @param nonce the nonce of the challenge + * @param connection connection being handled + * @param[out] url set to the redirect URL + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error was returned on @a connection (#MHD_YES) + * #GNUNET_SYSERR to just close @a connection (#MHD_NO) + */ +enum GNUNET_GenericReturnValue +CH_build_full_redirect_url (const struct CHALLENGER_ValidationNonceP *nonce, + struct MHD_Connection *connection, + char **url); + + +/** * Send a OAuth 2.0 response indicating an error following * section 5.2 of RFC 6749. * diff --git a/src/challenger/challenger-httpd_solve.c b/src/challenger/challenger-httpd_solve.c @@ -118,33 +118,6 @@ cleanup_ctx (void *cls) /** - * Generate error reply in the format requested by - * the client. - * - * @param bc our context - * @param template error template to use - * @param http_status HTTP status to return - * @param ec error code to return - * @param hint human-readable hint to give - */ -static enum MHD_Result -reply_error (struct SolveContext *bc, - const char *template, - unsigned int http_status, - enum TALER_ErrorCode ec, - const char *hint) -{ - struct CH_HandlerContext *hc = bc->hc; - - return TALER_MHD_reply_with_error ( - hc->connection, - http_status, - ec, - hint); -} - - -/** * Iterator over key-value pairs where the value may be made available * in increments and/or may not be zero-terminated. Used for * processing POST data. @@ -220,11 +193,11 @@ CH_handler_solve (struct CH_HandlerContext *hc, sizeof (bc->nonce))) { GNUNET_break_op (0); - return reply_error (bc, - "invalid-request", - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "nonce"); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "nonce"); } TALER_MHD_check_content_length (hc->connection, 1024); @@ -246,11 +219,11 @@ CH_handler_solve (struct CH_HandlerContext *hc, if (NULL == bc->pin) { GNUNET_break_op (0); - return reply_error (bc, - "invalid-request", - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "pin"); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "pin"); } { unsigned int pin; @@ -266,11 +239,11 @@ CH_handler_solve (struct CH_HandlerContext *hc, &dummy)) { GNUNET_break_op (0); - return reply_error (bc, - "invalid-request", - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "pin"); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "pin"); } for (unsigned int r = 0; r<MAX_RETRIES; r++) @@ -290,9 +263,8 @@ CH_handler_solve (struct CH_HandlerContext *hc, { case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); - return reply_error ( - bc, - "internal-error", + return TALER_MHD_reply_with_error ( + hc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "validate_solve_pin"); @@ -300,16 +272,14 @@ CH_handler_solve (struct CH_HandlerContext *hc, if (r < MAX_RETRIES - 1) continue; GNUNET_break (0); - return reply_error ( - bc, - "internal-error", + return TALER_MHD_reply_with_error ( + hc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "validate_solve_pin"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return reply_error ( - bc, - "validation-unknown", + return TALER_MHD_reply_with_error ( + hc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, NULL); @@ -329,11 +299,11 @@ CH_handler_solve (struct CH_HandlerContext *hc, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client exhausted all chances to satisfy challenge\n"); - return reply_error (bc, - "access_denied", - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS, - "users exhausted all possibilities of passing the check"); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS, + "users exhausted all possibilities of passing the check"); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -365,78 +335,13 @@ CH_handler_solve (struct CH_HandlerContext *hc, struct MHD_Response *response; char *url; unsigned int http_status; + enum GNUNET_GenericReturnValue ret; - { - char *client_secret; - json_t *address; - char *client_scope; - char *client_state; - char *client_redirect_uri; - enum GNUNET_DB_QueryStatus qs; - - qs = CHALLENGERDB_validation_get (CH_context, - &bc->nonce, - &client_secret, - &address, - &client_scope, - &client_state, - &client_redirect_uri); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - return reply_error (bc, - "internal-server-error", - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "validation_get"); - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return MHD_NO; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return reply_error (bc, - "validation-unknown", - MHD_HTTP_NOT_FOUND, - TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - { - char *code; - char *url_encoded; - - code = CH_compute_code (&bc->nonce, - client_secret, - client_scope, - address, - client_redirect_uri); - if (NULL == client_state) - { - GNUNET_asprintf (&url, - "%s?code=%s", - client_redirect_uri, - code); - } - else - { - url_encoded = TALER_urlencode (client_state); - GNUNET_asprintf (&url, - "%s?code=%s&state=%s", - client_redirect_uri, - code, - url_encoded); - GNUNET_free (url_encoded); - } - GNUNET_free (code); - } - json_decref (address); - GNUNET_free (client_scope); - GNUNET_free (client_secret); - GNUNET_free (client_redirect_uri); - GNUNET_free (client_state); - } - + ret = CH_build_full_redirect_url (&bc->nonce, + hc->connection, + &url); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; if (0 == CH_get_output_type (hc->connection)) { { @@ -487,13 +392,13 @@ CH_handler_solve (struct CH_HandlerContext *hc, } { - enum MHD_Result ret; + enum MHD_Result mret; - ret = MHD_queue_response (hc->connection, - http_status, - response); + mret = MHD_queue_response (hc->connection, + http_status, + response); MHD_destroy_response (response); - return ret; + return mret; } } } diff --git a/src/challenger/test-challenger-revisit.sh b/src/challenger/test-challenger-revisit.sh @@ -0,0 +1,229 @@ +#!/bin/bash +# This file is in the public domain. +# +# Tests that re-submitting to /challenge for an already-solved +# validation returns a redirect_url with both ``code`` and ``state`` +# query parameters, so the user agent can still be sent back to the +# OAuth client with a usable authorization grant. +# +# Regression test: previously the "already solved" branch returned the +# bare client redirect URI without code/state. + +set -eu + +function exit_skip() { + echo " SKIP: $1" + exit 77 +} + +function exit_fail() { + echo " FAIL: $@" + exit 1 +} + +function cleanup() +{ + for n in $(jobs -p) + do + kill $n 2> /dev/null || true + done + rm -f "$LAST_RESPONSE" "$FILENAME" + wait +} + +LAST_RESPONSE=$(mktemp responseXXXXXX.log) +FILENAME="test-challenger-revisit.txt" + +trap cleanup EXIT + +export PATH="$PATH:." + +echo -n "Testing for jq" +jq -h > /dev/null || exit_skip "jq required" +echo " FOUND" +echo -n "Testing for curl" +curl -h > /dev/null || exit_skip "curl required" +echo " FOUND" +echo -n "Testing for wget" +wget -h > /dev/null || exit_skip "wget required" +echo " FOUND" +echo -n "Testing for challenger-httpd ..." +challenger-httpd -h > /dev/null || exit_skip "challenger-httpd required" +echo " FOUND" + +CONF="test-challenger.conf" +BURL="http://localhost:9967" +REDIRECT_URI="http://client.example.com/" + +echo -n "Initialize challenger database ..." +challenger-dbinit -r -c "${CONF}" &> dbinit.log +echo " OK" + +echo -n "Add challenger client ..." +CLIENT_SECRET="secret-token:secret" +challenger-admin -c "${CONF}" -a "${CLIENT_SECRET}" "${REDIRECT_URI}" &> admin.log +echo " OK" +CLIENT_ID=1 + +echo -n "Start challenger-httpd ..." +challenger-httpd -L INFO -c "${CONF}" &> httpd.log & + +for n in $(seq 1 50) +do + echo -n "." + sleep 0.2 + OK=0 + wget --tries=1 --timeout=1 "${BURL}/config" -o /dev/null -O /dev/null >/dev/null || continue + OK=1 + break +done +if [ 1 != $OK ] +then + exit_skip "Failed to launch challenger service" +fi + + +echo -n "Setup new validation process..." +STATUS=$(curl "${BURL}/setup/${CLIENT_ID}" \ + -H "Authorization: Bearer ${CLIENT_SECRET}" \ + -d '' \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "200" ] +then + exit_fail "Expected 200 OK. Got: $STATUS" $(cat $LAST_RESPONSE) +fi +NONCE=$(jq -r .nonce < "$LAST_RESPONSE") +echo " OK" + +CLIENT_STATE="the-client-state" +CLIENT_SCOPE="the-client-scope" + +echo -n "Initiating user login..." +STATUS=$(curl "${BURL}/authorize/${NONCE}" \ + -G \ + -H "Accept: application/json" \ + --data-urlencode "response_type=code" \ + --data-urlencode "client_id=${CLIENT_ID}" \ + --data-urlencode "redirect_uri=${REDIRECT_URI}" \ + --data-urlencode "state=${CLIENT_STATE}" \ + --data-urlencode "scope=${CLIENT_SCOPE}" \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "200" ] +then + exit_fail "Expected 200 OK. Got: $STATUS" $(cat $LAST_RESPONSE) +fi +echo "OK" + + +echo -n "Initiating address submission..." +STATUS=$(curl "${BURL}/challenge/${NONCE}" \ + -X POST \ + -H "Accept: application/json" \ + --data-urlencode "filename=${FILENAME}" \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "200" ] +then + exit_fail "Expected 200 OK. Got: $STATUS" $(cat $LAST_RESPONSE) +fi +echo "OK" + +PIN=$(cat ${FILENAME} | awk '{print $5}') + +echo -n "Initiating PIN ${PIN} submission..." +RESULT=$(curl "${BURL}/solve/${NONCE}" \ + -X POST \ + -H "Accept: text/html" \ + --data-urlencode "pin=${PIN}" \ + -w "%{http_code} %{redirect_url}" -s -o $LAST_RESPONSE) +STATUS=$(echo "$RESULT" | awk '{print $1}') +TARGET=$(echo "$RESULT" | awk '{print $2}') + +if [ "$STATUS" != "302" ] +then + exit_fail "Expected 302. Got: $STATUS" $(cat $LAST_RESPONSE) +fi + +# This is where we start to diverge from test-challenger.sh: +# We track the origional (good) response and then check we get +# it again later. + +ORIG_CODE=$(echo "$TARGET" | sed -e "s/.*?code=//g" -e "s/&.*//g") +ORIG_STATE=$(echo "$TARGET" | sed -e "s/.*&state=//g") + +if [ -z "${ORIG_CODE}" ] +then + exit_fail "No code returned from /solve" +fi +echo "OK" + +# Now the actual regression test: re-submit the SAME address to +# /challenge. Since the validation is already solved, the server +# should reply with a JSON ChallengeRedirect whose redirect_url +# carries an OAuth ``code`` (and the original ``state``) so the user +# agent can still be returned to the client with a valid grant. +echo -n "Re-submitting address after solve..." +STATUS=$(curl "${BURL}/challenge/${NONCE}" \ + -X POST \ + -H "Accept: application/json" \ + --data-urlencode "filename=${FILENAME}" \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "200" ] +then + exit_fail "Expected 200 OK on re-submit. Got: $STATUS" $(cat $LAST_RESPONSE) +fi + +TYPE=$(jq -r .type < "$LAST_RESPONSE") +if [ "$TYPE" != "completed" ] +then + exit_fail "Expected type=completed on re-submit. Got: $TYPE" $(cat $LAST_RESPONSE) +fi + +REDIRECT_URL=$(jq -r .redirect_url < "$LAST_RESPONSE") +if [ -z "${REDIRECT_URL}" ] || [ "${REDIRECT_URL}" = "null" ] +then + exit_fail "Missing redirect_url in re-submit response" $(cat $LAST_RESPONSE) +fi + +# redirect_url must have a query string with code= and state=. +case "${REDIRECT_URL}" in + *\?*code=*) + # Good! + ;; + *) + exit_fail "redirect_url has no 'code' parameter: ${REDIRECT_URL}" + ;; +esac +case "${REDIRECT_URL}" in + *state=*) + # Good! + ;; + *) + exit_fail "redirect_url has no 'state' parameter: ${REDIRECT_URL}" + ;; +esac + +REVISIT_URL=$(echo "${REDIRECT_URL}" | sed -e "s/?.*//g") +REVISIT_CODE=$(echo "${REDIRECT_URL}" | sed -e "s/.*?code=//g" -e "s/&.*//g") +REVISIT_STATE=$(echo "${REDIRECT_URL}" | sed -e "s/.*&state=//g") + +if [ "${REVISIT_URL}" != "${REDIRECT_URI}" ] +then + exit_fail "Re-submit redirect URL ${REVISIT_URL} differs from registered ${REDIRECT_URI}" +fi +if [ "${REVISIT_STATE}" != "${CLIENT_STATE}" ] +then + exit_fail "Re-submit state ${REVISIT_STATE} differs from original ${CLIENT_STATE}" +fi +# The code is deterministic over (nonce, secret, scope, address, +# redirect_uri), so re-issuing must produce the same value as /solve did. +if [ "${REVISIT_CODE}" != "${ORIG_CODE}" ] +then + exit_fail "Re-submit code ${REVISIT_CODE} differs from /solve code ${ORIG_CODE}" +fi +echo "OK" + +exit 0 diff --git a/src/challenger/test-challenger.sh b/src/challenger/test-challenger.sh @@ -1,5 +1,7 @@ #!/bin/bash # This file is in the public domain. +# +# Tests happy path of challenger mostly. set -eu