challenger

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

commit f3550bf44f67735195a09c1a18500300d7a252d8
parent 9eb507be71bc2e8d53650868bfc7861522a70215
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  6 May 2023 15:33:33 +0200

-more basic work on challenger

Diffstat:
Acontrib/enter-address-form.must | 24++++++++++++++++++++++++
Acontrib/enter-tan-form.must | 29+++++++++++++++++++++++++++++
Msrc/challenger/challenger-httpd.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/challenger/challenger-httpd.h | 16++++++++++++++++
Msrc/challenger/challenger-httpd_auth.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/challenger/challenger-httpd_challenge.c | 399++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/challenger/challenger-httpd_challenge.h | 7+++++++
Msrc/challenger/challenger-httpd_common.c | 33+++++++++++++++++++++++++++++++++
Msrc/challenger/challenger-httpd_common.h | 32++++++++++++++++++++++++++++++++
Msrc/challenger/challenger-httpd_info.c | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/challenger/challenger-httpd_login.c | 5+++--
Msrc/challenger/challenger-httpd_solve.c | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/challengerdb/challenger-0001.sql | 9++++++---
Msrc/challengerdb/pg_challenge_set_address_and_pin.c | 94++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/challengerdb/pg_challenge_set_address_and_pin.h | 12+++++++-----
Msrc/include/challenger_database_plugin.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++------
16 files changed, 1297 insertions(+), 141 deletions(-)

diff --git a/contrib/enter-address-form.must b/contrib/enter-address-form.must @@ -0,0 +1,23 @@ +<html> +<head> +<title>Enter your address</title> +</head> +<body> +<form action="/challenge/{{nonce}}" method="POST"> + <div> + <label for="say">What is your address?</label> + <input + name="address" + id="address" + maxlength="512" + value="{{last_address}}" + {{#fixed_address}}readonly{{/fixed_address}} + /> + </div> + (You can change address another {{changes_left}} times.) + <div> + <button>Submit</button> + </div> +</form> +</body> +</html> +\ No newline at end of file diff --git a/contrib/enter-tan-form.must b/contrib/enter-tan-form.must @@ -0,0 +1,29 @@ +<html> +<head> +<title>Enter your TAN</title> +</head> +<body> + {{#transmitted}} + A TAN was sent to your address &quot;{{address}}&quot;. + {{transmitted}} + {{^transmitted}} + We recently already sent a TAN to your address &quot;{{address}}&quot;. + A new TAN will not be transmitted again before {{next_tx_time}}. + {{/transmitted}} + + <form action="/solve/{{nonce}}" method="POST"> + <div> + <label for="say">Please enter the TAN your received to authenticate.</label> + <input + name="pin" + id="pin" + maxlength="64" + /> + </div> + (You have {{attempts_left}} attempts left.) + <div> + <button>Submit</button> + </div> +</form> +</body> +</html> diff --git a/src/challenger/challenger-httpd.c b/src/challenger/challenger-httpd.c @@ -81,11 +81,28 @@ struct CHALLENGER_DatabasePlugin *db; struct GNUNET_TIME_Relative CH_validation_duration; /** + * How long validated data considered to be valid? + */ +struct GNUNET_TIME_Relative CH_validation_expiration; + +/** * How often do we retransmit the challenge. */ struct GNUNET_TIME_Relative CH_pin_retransmission_frequency; /** + * Type of addresses this challenger validates. + */ +char *CH_address_type; + +/** + * Helper command to run for transmission of + * challenge values. + */ +char *CH_auth_command; + + +/** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback @@ -293,6 +310,7 @@ static void do_shutdown (void *cls) { (void) cls; + CH_wakeup_challenge_on_shutdown (); if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); @@ -511,6 +529,39 @@ run (void *cls, "VALIDATION_DURATION"); return; } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (config, + "CHALLENGER", + "VALIDATION_EXPIRATION", + &CH_validation_expiration)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "CHALLENGER", + "VALIDATION_EXPIRATION"); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (config, + "CHALLENGER", + "AUTH_COMMAND", + &CH_auth_command)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "CHALLENGER", + "AUTH_COMMAND"); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (config, + "CHALLENGER", + "ADDRESS_TYPE", + &CH_address_type)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "CHALLENGER", + "ADDRESS_TYPE"); + return; + } TALER_MHD_setup (go); result = EXIT_NOTCONFIGURED; diff --git a/src/challenger/challenger-httpd.h b/src/challenger/challenger-httpd.h @@ -130,11 +130,27 @@ extern struct CHALLENGER_DatabasePlugin *db; extern struct GNUNET_CURL_Context *CH_ctx; /** + * Helper command to run for transmission of + * challenge values. + */ +extern char *CH_auth_command; + +/** + * Type of addresses this challenger validates. + */ +extern char *CH_address_type; + +/** * How long is an individual validation request valid? */ extern struct GNUNET_TIME_Relative CH_validation_duration; /** + * How long validated data considered to be valid? + */ +extern struct GNUNET_TIME_Relative CH_validation_expiration; + +/** * How often do we retransmit the challenge. */ extern struct GNUNET_TIME_Relative CH_pin_retransmission_frequency; diff --git a/src/challenger/challenger-httpd_auth.c b/src/challenger/challenger-httpd_auth.c @@ -34,9 +34,39 @@ struct AuthContext { /** + * Nonce of the validation process the request is about. + */ + struct CHALLENGER_ValidationNonceP nonce; + + /** * Handle for processing uploaded data. */ struct MHD_PostProcessor *pp; + + /** + * Uploaded 'client_id' field from POST data. + */ + char *client_id; + + /** + * Uploaded 'client_id' field from POST data. + */ + char *redirect_uri; + + /** + * Uploaded 'client_secret' field from POST data. + */ + char *client_secret; + + /** + * Uploaded 'code' field from POST data. + */ + char *code; + + /** + * Uploaded 'grant_type' field from POST data. + */ + char *grant_type; }; @@ -55,6 +85,11 @@ cleanup_ctx (void *cls) GNUNET_break_op (MHD_YES == MHD_destroy_post_processor (bc->pp)); } + GNUNET_free (bc->client_id); + GNUNET_free (bc->redirect_uri); + GNUNET_free (bc->client_secret); + GNUNET_free (bc->code); + GNUNET_free (bc->grant_type); GNUNET_free (bc); } @@ -89,10 +124,58 @@ post_iter (void *cls, size_t size) { struct AuthContext *bc = cls; + struct Map + { + const char *name; + char **ptr; + } map[] = { + { + .name = "client_id", + .ptr = &bc->client_id + }, + { + .name = "redirect_uri", + .ptr = &bc->redirect_uri + }, + { + .name = "client_secret", + .ptr = &bc->client_secret + }, + { + .name = "code", + .ptr = &bc->code + }, + { + .name = "grant_type", + .ptr = &bc->grant_type + }, + { + .name = NULL, + .ptr = NULL + }, + }; + char **ptr = NULL; + size_t slen; - (void) bc; - GNUNET_break (0); - return MHD_NO; + for (unsigned int i = 0; NULL != map[i].name; i++) + if (0 == strcmp (key, + map[i].name)) + ptr = map[i].ptr; + if (NULL == ptr) + return MHD_YES; /* ignore */ + if (NULL == *ptr) + slen = 0; + else + slen = strlen (*ptr); + if (NULL == *ptr) + *ptr = GNUNET_malloc (size + 1); + else + *ptr = GNUNET_realloc (*ptr, + slen + size + 1); + memcpy ((*ptr)[slen], + data, + size); + return MHD_YES; } @@ -102,6 +185,7 @@ CH_handler_auth (struct CH_HandlerContext *hc, size_t *upload_data_size) { struct AuthContext *bc = hc->ctx; + char *access_token; if (NULL == bc) { @@ -127,10 +211,211 @@ CH_handler_auth (struct CH_HandlerContext *hc, *upload_data_size = 0; if (MHD_YES == res) return MHD_YES; - /* FIXME: return more specific error if possible... */ return MHD_NO; } + if ( (NULL == bc->grant_type) || + (0 != strcmp (bc->grant_type, + "authorization_code")) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_INVALID, + "authorization_code"); + } + + if (NULL == bc->code) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "code"); + } + if (NULL == bc->client_secret) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "client_secret"); + } + if (NULL == bc->client_id) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "client_id"); + } + if (NULL == bc->redirect_uri) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "redirect_uri"); + } + + /* Check this client is authorized to access the service */ + { + enum GNUNET_DB_QueryStatus qs; + char *client_url = NULL; + + qs = CH_db->client_check (CH_db->cls, + bc->client_id, + bc->client_secret, + 0, /* do not increment */ + &client_url); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_XX, + "client_check"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_CHALLENGER_XXX, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + if ( (NULL != client_url) && + (0 != strcmp (client_url, + bc->redirect_uri)) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_CHALLENGER_XXX, + NULL); + } + GNUNET_free (client_url); + } - /* FIXME: generate proper response */ - return MHD_NO; + if (GNUNET_OK != + CH_code_to_nonce (bc->code, + &bc->nonce)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_CHALLENGER_XXX, + NULL); + } + + /* Check code is valid */ + { + char *client_secret; + char *address; + char *client_scope; + char *client_state; + char *client_redirect_url; + enum GNUNET_DB_QueryStatus qs; + char *code; + + qs = CH_db->validation_get (CH_db->cls, + &bc->nonce, + &client_secret, + &address, + &client_scope, + &client_state, + &client_redirect_url); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_XX, + "validation_get"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_CHALLENGER_XXX, + "validation_get"); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + code = CH_compute_code (&bc->nonce, + client_secret, + client_scope, + address, + client_redirect_url); + GNUNET_free (address); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_url); + GNUNET_free (client_state); + if (0 != strcmp (code, + bc->code)) + { + GNUNET_break_op (0); + GNUNET_free (code); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_CHALLENGER_XXX, + "code"); + } + GNUNET_free (code); + } + + { + struct CHALLENGER_AccessTokenP grant; + enum GNUNET_DB_QueryStatus qs; + /* FIXME: do not hard-code 1h? */ + struct GNUNET_TIME_Relative expiration + = GNUNET_TIME_UNIT_HOURS; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &grant, + sizeof (grant)); + qs = CH_db->auth_add_grant (CH_db->cls, + &bc->nonce, + &grant, + expiration); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_XX, + "add_grant"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_CHALLENGER_XXX, + "add_grant"); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + + + return TALER_MHD_REPLY_JSON_PACK ( + hc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ("access_token", + &grant), + GNUNET_JSON_pack_string ("token_type", + "Bearer"), + GNUNET_JSON_pack_uint64 ("expires_in", + expiration.rel_time_us + / GNUNET_TIME_UNIT_SECONDS.rel_time_us)); + } } diff --git a/src/challenger/challenger-httpd_challenge.c b/src/challenger/challenger-httpd_challenge.c @@ -35,6 +35,36 @@ struct ChallengeContext { /** + * Nonce of the operation. + */ + struct CHALLENGER_ValidationNonceP nonce; + + /** + * Kept in a DLL. + */ + struct ChallengeContext *next; + + /** + * Kept in a DLL. + */ + struct ChallengeContext *prev; + + /** + * Our handler context. + */ + struct CH_HandlerContext *hc; + + /** + * Handle to the helper process. + */ + struct GNUNET_OS_Process *child; + + /** + * Handle to wait for @e child + */ + struct GNUNET_ChildWaitHandle *cwh; + + /** * Handle for processing uploaded data. */ struct MHD_PostProcessor *pp; @@ -45,13 +75,90 @@ struct ChallengeContext char *address; /** + * When did we transmit last? + */ + struct GNUNET_TIME_Absolute last_tx_time; + + /** + * Exit code from helper. + */ + unsigned long int exit_code; + + /** * Number of bytes in @a address, excluding 0-terminator. */ size_t address_len; + + /** + * Our tan. + */ + uint32_t tan; + + /** + * How many attempts does the user have left? + */ + uint32_t pin_attempts_left; + + /** + * How did the helper die? + */ + enum GNUNET_OS_ProcessStatusType pst; + + /** + * Connection status. #GNUNET_OK to continue + * normally, #GNUNET_NO if an error was already + * returned, #GNUNET_SYSERR if we failed to + * return an error and should just return #MHD_NO. + */ + enum GNUNET_GenericReturnValue status; + + /** + * #GNUNET_YES if we are suspended in #bc_head, + * #GNUNET_NO if operating normally, + * #GNUNET_SYSERR if resumed by shutdown (end with #MHD_NO) + */ + enum GNUNET_GenericReturnValue suspended; + + /** + * Should we retransmit the PIN? + */ + bool retransmit; + + /** + * Did we do the DB interaction? + */ + bool db_finished; }; /** + * Head of suspended challenger contexts. + */ +struct ChallengeContext *bc_head; + +/** + * Tail of suspended challenger contexts. + */ +struct ChallengeContext *bc_tail; + + +void +CH_wakeup_challenge_on_shutdown () +{ + struct ChallengeContext *bc; + + while (NULL != (bc = bc_head)) + { + GNUNET_CONTAINER_DLL_remove (bc_head, + bc_tail, + bc); + MHD_resume_connection (bs->connection); + bc->suspended = GNUNET_SYSERR; + } +} + + +/** * Function called to clean up a backup context. * * @param hc a `struct ChallengeContext` @@ -66,12 +173,154 @@ cleanup_ctx (void *cls) GNUNET_break_op (MHD_YES == MHD_destroy_post_processor (bc->pp)); } + if (NULL != bc->cwh) + { + GNUNET_wait_child_cancel (bc->cwh); + bc->cwh = NULL; + } + if (NULL != bc->child) + { + (void) GNUNET_OS_process_kill (bc->child, + SIGKILL); + GNUNET_break (GNUNET_OK == + GNUNET_OS_process_wait (bc->child)); + bc->child = NULL; + } GNUNET_free (bc->address); GNUNET_free (bc); } /** + * Function called when our PIN transmission helper has terminated. + * + * @param cls our `struct ChallengeContext *` + * @param type type of the process + * @param exit_code status code of the process + */ +static void +child_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct ChallengeContext *bc = cls; + + bc->child = NULL; + bc->cwh = NULL; + bc->pst = type; + bc->exit_code = exit_code; + MHD_resume_connection (bs->connection); + GNUNET_CONTAINER_DLL_remove (bc_head, + bc_tail, + bc); + CH_trigger_daemon (); +} + + +/** + * Transmit the TAN to the given address. + * + * @param[in,out] bc context to submit TAN for + * @param tan TAN value to submit + */ +static void +send_tan (struct ChallengeContext *bc, + uint32_t tan) +{ + struct GNUNET_DISK_PipeHandle *p; + struct GNUNET_DISK_FileHandle *pipe_stdin; + char *msg; + + p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW); + if (NULL == p) + { + MHD_STATUS mres; + + // FIXME: generate HTML error instead... + mres = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "pipe"); + bc->status = (MHD_YES == mres) + ? GNUNET_NO + : GNUNET_SYSERR; + return; + } + bc->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + p, + NULL, + NULL, + CH_auth_command, + CH_auth_command, + bc->address, + NULL); + if (NULL == child) + { + MHD_RESULT mres; + + GNUNET_DISK_pipe_close (p); + // FIXME: generate HTML error instead... + mres = TALER_MHD_reply_with_error (bc->hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "exec"); + bc->status = (MHD_YES == mres) + ? GNUNET_NO + : GNUNET_SYSERR; + return; + } + pipe_stdin = GNUNET_DISK_pipe_detach_end (p, + GNUNET_DISK_PIPE_END_WRITE); + GNUNET_assert (NULL != pipe_stdin); + GNUNET_DISK_pipe_close (p); + + GNUNET_asprintf (&msg, + "PIN: %u", + (unsigned int) pin); + { + const char *off = msg; + size_t left = strlen (off); + + while (0 != left) + { + ssize_t ret; + + if (0 == left) + break; + ret = GNUNET_DISK_file_write (pipe_stdin, + off, + left); + if (ret <= 0) + { + MHD_RESULT mres; + + // FIXME: generate HTML error instead... + mres = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + "write"); + bc->status = (MHD_YES == mres) + ? GNUNET_NO + : GNUNET_SYSERR; + return; + } + msg_off += ret; + off += ret; + left -= ret; + } + GNUNET_DISK_file_close (pipe_stdin); + } + bc->cwh = GNUNET_wait_child (bc->child, + &child_done_cb, + bc); + MHD_suspend_connection (connection); + GNUNET_CONTAINER_DLL_insert (bc_head, + bc_tail, + bc); +} + + +/** * 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. @@ -128,33 +377,60 @@ CH_handler_challenge (struct CH_HandlerContext *hc, size_t *upload_data_size) { struct ChallengeContext *bc = hc->ctx; - 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 (hc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_PARAMETER_MISSING, - hc->path); - } if (NULL == bc) { /* first call, setup internals */ bc = GNUNET_new (struct ChallengeContext); + bc->status = GNUNET_OK; + bc->hc = hc; hc->cc = &cleanup_ctx; hc->ctx = bc; + bc->pst = GNUNET_OS_PROCESS_UNKNOWN; + bc->tan + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, + 100000000); bc->pp = MHD_create_post_processor (hc->connection, 1024, &post_iter, bc); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (hc->path, + strlen (hc->path), + &bc->nonce, + sizeof (bc->nonce))) + { + GNUNET_break_op (0); + // FIXME: generate HTML error instead... + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_PARAMETER_MISSING, + hc->path); + } /* FIXME: check content-length is low-enough */ return MHD_YES; } + GNUNET_assert (GNUNET_YES != bc->suspended); + if (GNUNET_SYSERR == bc->suspended) + return MHD_NO; + /* Handle case where helper process failed */ + if ( ( (GNUNET_OS_PROCESS_UNKNOWN != bc->pst) && + (GNUNET_OS_PROCESS_EXITED != bc->pst) ) || + (0 != bc->exit_code) ) + { + char es[32]; + + GNUNET_snprintf (es, + sizeof (es), + "%u/%d", + (unsigned int) bc->exit_code, + bc->pst); + // FIXME: generate HTML error instead... + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_CHALLENGER_HELPER_EXEC_FAILED, + es); + } /* handle upload */ if (0 != *upload_data_size) { @@ -166,30 +442,23 @@ CH_handler_challenge (struct CH_HandlerContext *hc, *upload_data_size = 0; if (MHD_YES == res) return MHD_YES; - /* FIXME: return more specific error if possible... */ return MHD_NO; } - + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Submitted address is `%s'\n", + bc->address); + if (! bc->db_finished) { - struct GNUNET_TIME_Absolute last_tx_time - = GNUNET_TIME_absolute_get (); - uint32_t last_pin - = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, - 100000000); - uint32_t pin_attempts_left = 3; /* if addr is new */ enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Absolute next_tx_time; - bool retransmit; - next_tx_time = GNUNET_TIME_absolute_subtract (last_tx_time, - CH_validation_duration); qs = db->challenge_set_address_and_pin (db->cls, &nonce, bc->address, - next_tx_time, - &last_tx_time, - &last_pin, - &retransmit); + CH_validation_duration, + &bc->tan, + &bc->last_tx_time, + &bc->pin_attempts_left, + &bc->retransmit); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -241,7 +510,8 @@ CH_handler_challenge (struct CH_HandlerContext *hc, case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } - if (0 == pin_attempts_left) + bc->db_finished = true; + if (0 == bc->pin_attempts_left) { enum GNUNET_GenericReturnValue ret; json_t *root = json_object (); @@ -263,35 +533,52 @@ CH_handler_challenge (struct CH_HandlerContext *hc, return MHD_YES; } - if (retransmit) + if (bc->retransmit) { - /* Retransmit PIN */ + /* (Re)transmit PIN/TAN */ + send_tan (bc, + tan); + if (GNUNET_YES == bc->suspended) + return MHD_YES; + /* Did we already try to generate a response? */ + if (GNUNET_OK != bc->status) + return (GNUNET_NO == bc->status) + ? MHD_YES + : MHD_NO; } - { - json_t *args; - enum GNUNET_GenericReturnValue ret; + } - args = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("attempts_left", - pin_attempts_left), - GNUNET_JSON_pack_timestamp ("next_tx_time", - GNUNET_TIME_absolute_to_timestamp ( - next_tx_time)) - ); - ret = TALER_TEMPLATING_reply (hc->connection, - MHD_HTTP_OK, - "enter-pin-form.must", - NULL, - NULL, - args); - json_decref (args); - if (GNUNET_SYSERR == ret) - { - GNUNET_break (0); - return MHD_NO; - } - GNUNET_break (GNUNET_OK == ret); - return MHD_YES; + { + json_t *args; + enum GNUNET_GenericReturnValue ret; + + args = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("attempts_left", + bc->pin_attempts_left), + GNUNET_JSON_pack_string ("nonce", + hc->path), + GNUNET_JSON_pack_string ("address", + bc->address), + GNUNET_JSON_pack_bool ("transmitted", + bc->retransmit), + GNUNET_JSON_pack_string ("next_tx_time", + GNUNET_TIME_absolute2s ( + GNUNET_TIME_absolute_to_timestamp ( + bc->next_tx_time))) + ); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_OK, + "enter-tan-form.must", + NULL, + NULL, + args); + json_decref (args); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; } } diff --git a/src/challenger/challenger-httpd_challenge.h b/src/challenger/challenger-httpd_challenge.h @@ -25,6 +25,13 @@ /** + * Wake up suspended connections during shutdown. + */ +void +CH_wakeup_challenge_on_shutdown (void); + + +/** * Handle a client POSTing a /challenge request * * @param hc context of the connection diff --git a/src/challenger/challenger-httpd_common.c b/src/challenger/challenger-httpd_common.c @@ -53,3 +53,36 @@ CH_get_client_secret (struct MHD_Connection *connection) } return tok; } + + +char * +CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce, + const char *client_secret, + const char *client_scope, + const char *address, + const char *client_redirect_url) +{ + // FIXME: compute HKDF over inputs here!!! + GNUNET_break (0); // FIXME: insecure! + return "access-granted"; +} + + +enum GNUNET_GenericReturnValue +CH_code_to_nonce (const char *code, + struct CHALLENGER_ValidationNonceP *nonce) +{ + GNUNET_break (0); // FIXME: not implemented + return GNUNET_SYSERR; +} + + +char * +CH_compute_token (const struct CHALLENGER_ValidationNonceP *nonce, + const char *client_secret, + const char *client_redirect_url) +{ + // FIXME: compute HKDF over inputs here!!! + GNUNET_break (0); // FIXME: insecure! + return "grant-token"; +} diff --git a/src/challenger/challenger-httpd_common.h b/src/challenger/challenger-httpd_common.h @@ -34,4 +34,36 @@ const char * CH_get_client_secret (struct MHD_Connection *connection); +/** + * Compute code that would authorize access to the + * given challenge address. NOTE: We may not want + * to include all of these when hashing... + * + * @param nonce nonce of the challenge process + * @param client_secret secret of the client that should receive access + * @param client_scope scope of the grant + * @param address address that access is being granted to + * @param client_redirect_url redirect URL of the client + * @return code that grants access + */ +char * +CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce, + const char *client_secret, + const char *client_scope, + const char *address, + const char *client_redirect_url); + + +/** + * Extracts a @a nonce from the given @a code. + * + * @param code access code computed via #CH_compute_code() + * @param[out] nonce set to nonce used in #CH_compute_code() + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +CH_code_to_nonce (const char *code, + struct CHALLENGER_ValidationNonceP *nonce); + + #endif diff --git a/src/challenger/challenger-httpd_info.c b/src/challenger/challenger-httpd_info.c @@ -23,14 +23,97 @@ #include <gnunet/gnunet_util_lib.h> #include "challenger-httpd_info.h" +/** + * Prefix of a 'Bearer' token in an 'Authorization' HTTP header. + */ +#define BEARER_PREFIX "Bearer " + MHD_RESULT CH_handler_info (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 *auth; + const char *token; + struct CHALLENGER_AccessTokenP grant; + + auth = MHD_lookup_connection_value (hc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_AUTHORIZATION); + if (NULL == auth) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_GENERIC_PARAMETER_MISSING, + MHD_HTTP_HEADER_AUTHORIZATION); + } + if (0 != strncmp (auth, + BEARER_PREFIX, + strlen (BEARER_PREFIX))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + MHD_HTTP_HEADER_AUTHORIZATION); + } + token = auth + strlen (BEARER_PREFIX); + + if (GNUNET_OK != + CH_token_to_grant (token, + &grant)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + MHD_HTTP_HEADER_AUTHORIZATION); + } + + /* Check token is valid */ + { + char *address; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp address_expiration; + MHD_RESULT mret; + + qs = CH_db->info_get_grant (CH_db->cls, + &bc->grant, + &address, + &address_expiration); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_XX, + "info_get_grant"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_CHALLENGER_XXX, + "info_get_grant"); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + + mret = TALER_MHD_REPLY_JSON_PACK ( + hc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("address", + address), + GNUNET_JSON_pack_string ("address_type", + CH_address_type), + GNUNET_JSON_pack_timestamp ("expires", + address_expiration)); + GNUNET_free (address); + return mret; + } } diff --git a/src/challenger/challenger-httpd_login.c b/src/challenger/challenger-httpd_login.c @@ -68,7 +68,7 @@ CH_handler_login (struct CH_HandlerContext *hc, return TALER_MHD_reply_with_error (hc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "response_type (mus be 'code')"); + "response_type (must be 'code')"); } { @@ -127,7 +127,6 @@ CH_handler_login (struct CH_HandlerContext *hc, = MHD_lookup_connection_value (hc->connection, MHD_GET_ARGUMENT_KIND, "scope"); - (void) scope; /* ignored */ { char *last_address; uint32_t address_attempts_left; @@ -199,6 +198,8 @@ CH_handler_login (struct CH_HandlerContext *hc, args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_bool ("fix_address", 0 == address_attempts_left), + GNUNET_JSON_pack_string ("nonce", + hc->path), GNUNET_JSON_pack_string ("last_address", last_address), GNUNET_JSON_pack_uint64 ("changes_left", diff --git a/src/challenger/challenger-httpd_solve.c b/src/challenger/challenger-httpd_solve.c @@ -34,6 +34,11 @@ struct SolveContext { /** + * Nonce of the operation. + */ + struct CHALLENGER_ValidationNonceP nonce; + + /** * Handle for processing uploaded data. */ struct MHD_PostProcessor *pp; @@ -127,20 +132,7 @@ CH_handler_solve (struct CH_HandlerContext *hc, size_t *upload_data_size) { struct SolveContext *bc = hc->ctx; - 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 (hc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_PARAMETER_MISSING, - hc->path); - } if (NULL == bc) { /* first call, setup internals */ @@ -151,6 +143,18 @@ CH_handler_solve (struct CH_HandlerContext *hc, 1024, &post_iter, bc); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (hc->path, + strlen (hc->path), + &bc->nonce, + sizeof (bc->nonce))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_PARAMETER_MISSING, + hc->path); + } /* FIXME: check content-length is low-enough */ return MHD_YES; } @@ -165,12 +169,251 @@ CH_handler_solve (struct CH_HandlerContext *hc, *upload_data_size = 0; if (MHD_YES == res) return MHD_YES; - /* FIXME: return more specific error if possible... */ return MHD_NO; } - /* FIXME: convert pin string to number */ - /* FIXME: check with DB ... */ + { + unsigned int pin; + char dummy; + enum GNUNET_DB_QueryStatus qs; + bool solved; + + if (1 != sscanf (bc->pin, + "%u%c", + &pin, + &dummy)) + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); + + GNUNET_assert (NULL != root); + GNUNET_break_op (0); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_BAD_REQUEST, + "pin-must-be-number.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + + qs = CH_db->validate_solve_pin (CH_db->cls, + &bc->nonce, + pin, + &solved); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); + + GNUNET_assert (NULL != root); + GNUNET_break (0); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "internal-server-error.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); + + GNUNET_assert (NULL != root); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_NOT_FOUND, + "validation-unknown.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + if (! solved) + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); + + GNUNET_assert (NULL != root); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_FORBIDDEN, + "invalid-pin.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + } + + { + struct MHD_Response *response; + char *url; + + { + char *client_secret; + char *address; + char *client_scope; + char *client_state; + char *client_redirect_url; + enum GNUNET_DB_QueryStatus qs; + + qs = CH_db->validation_get (CH_db->cls, + &bc->nonce, + &client_secret, + &address, + &client_scope, + &client_state, + &client_redirect_url); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); - /* FIXME: generate proper response */ - return MHD_NO; + GNUNET_assert (NULL != root); + GNUNET_break (0); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "internal-server-error.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + { + enum GNUNET_GenericReturnValue ret; + json_t *root = json_object (); + + GNUNET_assert (NULL != root); + ret = TALER_TEMPLATING_reply (hc->connection, + MHD_HTTP_NOT_FOUND, + "validation-unknown.must", + NULL, + NULL, + root); + json_decref (root); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (GNUNET_OK == ret); + return MHD_YES; + } + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + { + char *code; + char *ue; + + code = CH_compute_code (&bc->nonce, + client_secret, + client_scope, + address, + client_redirect_url); + ue = TALER_urlencode (client_state); + GNUNET_asprintf (&url, + "%s?code=%s&state=%s", + client_redirect_url, + code, + ue); + GNUNET_free (ue); + GNUNET_free (code); + } + GNUNET_free (address); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_url); + GNUNET_free (client_state); + } + + { + const char *ok = "Ok!"; + + response = MHD_create_response_from_buffer (strlen (ok), + (void *) ok, + MHD_RESPMEM_PERSISTENT); + } + if (NULL == response) + { + GNUNET_break (0); + GNUNET_free (url); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + url)) + { + GNUNET_break (0); + MHD_destroy_response (response); + GNUNET_free (url); + return MHD_NO; + } + GNUNET_free (url); + + { + MHD_RESULT ret; + + ret = MHD_queue_response (connection, + MHD_HTTP_FOUND, + response); + MHD_destroy_response (response); + return ret; + } + } } diff --git a/src/challengerdb/challenger-0001.sql b/src/challengerdb/challenger-0001.sql @@ -98,7 +98,8 @@ CREATE TABLE IF NOT EXISTS grants (grant_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY ,access_token BYTEA PRIMARY KEY CHECK (length(access_token)=32) ,address VARCHAR NOT NULL - ,expiration_time INT8 NOT NULL + ,address_expiration_time INT8 NOT NULL + ,grant_expiration_time INT8 NOT NULL ); COMMENT ON TABLE grants @@ -107,8 +108,10 @@ COMMENT ON COLUMN grants.access_token IS 'Token that grants access to the resource (the address)'; COMMENT ON COLUMN grants.address IS 'Address of the user (the resource protected by the token)'; -COMMENT ON COLUMN grants.expiration_time - IS 'When will the grant expire'; +COMMENT ON COLUMN grants.address_expiration_time + IS 'Timestamp until when we consider the address to be valid'; +COMMENT ON COLUMN grants.grant_expiration_time + IS 'Time until when we consider the grnat to be valid'; -- Complete transaction COMMIT; diff --git a/src/challengerdb/pg_challenge_set_address_and_pin.c b/src/challengerdb/pg_challenge_set_address_and_pin.c @@ -31,70 +31,82 @@ CH_PG_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_Relative validation_duration, + uint32_t *tan, struct GNUNET_TIME_Absolute *last_tx_time, - uint32_t *last_pin, + uint32_t *pin_attempts_left, bool *pin_transmit) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now + = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Absolute next_tx_time + = GNUNET_TIME_absolute_subtract (last_tx_time, + validation_duration), struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (nonce), GNUNET_PQ_query_param_string (address), GNUNET_PQ_query_param_absolute_time (&next_tx_time), - GNUNET_PQ_query_param_absolute_time (last_tx_time), - GNUNET_PQ_query_param_uint32 (last_pin), + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_uint32 (tan), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("last_tx_time", last_tx_time), GNUNET_PQ_result_spec_uint32 ("last_pin", - last_pin), + tan), GNUNET_PQ_result_spec_bool ("pin_transmit", pin_transmit), + GNUNET_PQ_result_spec_uint32 ("pin_attempts_left", + pin_attempts_left), GNUNET_PQ_result_spec_end }; - // FIXME: review!!! PREPARE (pg, "challenge_set_address_and_pin", + "WITH decisions AS (" + " SELECT " + " ,(address != $2) AND" + " (address_attempts_left > 0)" + " AS addr_changed" + " ,(pin_transmissions_left > 0) AND" + " ( (address != $2) OR" + " (last_tx_time < $3) ) AS send_pin" + " FROM validations" + " WHERE nonce=$1" + ")" "UPDATE validations SET" " address_attempts_left=CASE" - " WHEN address != $2" - " THEN address_attempts_left - 1" - " ELSE address_attempts_left" - " END" - " ,last_pin=CASE" - " WHEN address != $2" - " THEN $5" - " ELSE last_pin" - " ,END" - " ,pin_transmissions_left=CASE" - " WHEN address != $2" - " THEN 3" - " ELSE WHEN last_tx_time < 3" - " THEN pin_transmissions_left - 1" - " ELSE pin_transmissions_left" - " END" - " ,END" - " ,last_tx_time=CASE" - " WHEN last_tx_time < $3" - " THEN $4" - " ELSE last_tx_time" - " END" - " ,address=$2" - " WHERE nonce=$1" - " AND ( (address_attempts_left > 0)" - " OR ( (address == $2) AND" - " ( (pin_transmissions_left > 0) OR" - " (last_tx_time >= $3) ) ) )" - " RETURNING" - " last_tx_time" - " ,(address != $2) OR" - " ( (pin_transmissions_left > 0) AND" - " (last_tx_time < $3) ) AS pin_transmit" - " ,last_pin" - " ,pin_attempts_left;"); + " WHEN decisions.addr_changed + " THEN address_attempts_left - 1 " + " ELSE address_attempts_left " + " END " + ",last_pin = CASE " + " WHEN decisions.addr_changed + " THEN $5" + " ELSE last_pin" + " ,END" + " ,pin_transmissions_left=CASE" + " WHEN decisions.addr_changed + " THEN 3 " + " ELSE WHEN decisions.send_pin + " THEN pin_transmissions_left - 1" + " ELSE pin_transmissions_left" + " END" + " ,END" + " ,last_tx_time=CASE" + " WHEN decisions.send_pin" + " THEN $4" + " ELSE last_tx_time" + " END" + " ,address=$2" + " WHERE nonce=$1" + " RETURNING" + " last_tx_time" + " ,decisions.send_pin AS pin_transmit" + " ,last_pin" + " ,pin_attempts_left;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "challenge_set_address_and_pin", params, diff --git a/src/challengerdb/pg_challenge_set_address_and_pin.h b/src/challengerdb/pg_challenge_set_address_and_pin.h @@ -35,10 +35,11 @@ * @param cls * @param nonce unique nonce to use to identify the validation * @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 validation_duration minimum time between transmissions + * @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] 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] pin_transmit set to true if we should transmit the @a last_pin to the @a address + * @param[out] pin_attempts_left set to number of attempts the user has left on this pin * @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) @@ -49,9 +50,10 @@ CH_PG_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_Relative validation_duration, + uint32_t *tan, struct GNUNET_TIME_Absolute *last_tx_time, - uint32_t *last_pin, + uint32_t *pin_attempts_left, bool *pin_transmit); diff --git a/src/include/challenger_database_plugin.h b/src/include/challenger_database_plugin.h @@ -40,6 +40,18 @@ struct CHALLENGER_ValidationNonceP /** + * Nonce to uniquely (and unpredictably) identify grants. + */ +struct CHALLENGER_AccessTokenP +{ + /** + * 256-bit nonce used to identify grants. + */ + uint32_t value[256 / 32]; +}; + + +/** * Handle to interact with the database. * * Functions ending with "_TR" run their OWN transaction scope @@ -244,10 +256,11 @@ struct CHALLENGER_DatabasePlugin * @param cls closure * @param nonce unique nonce to use to identify the validation * @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[out] pin_transmit set to true if we should transmit @a last_pin to the @a address + * @param validation_duration minimum time between transmissions + * @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] 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] pin_transmit set to true if we should transmit the @a last_pin to the @a address + * @param[out] pin_attempts_left set to number of attempts the user has left on this pin * @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) @@ -258,9 +271,10 @@ struct CHALLENGER_DatabasePlugin void *cls, const struct CHALLENGER_ValidationNonceP *nonce, const char *address, - struct GNUNET_TIME_Absolute next_tx_time, + struct GNUNET_TIME_Relative validation_duration, + uint32_t *tan, struct GNUNET_TIME_Absolute *last_tx_time, - uint32_t *last_pin, + uint32_t *pin_attempts_left, bool *pin_transmit); @@ -310,5 +324,39 @@ struct CHALLENGER_DatabasePlugin char **client_redirect_url); + /** + * Add access @a grant to address under @a nonce. + * + * @param cls closure + * @param nonce validation process to grant access to + * @param grant grant token that grants access + * @param grant_expiration for how long should the grant be valid + * @param address_expiration for how long after validation do we consider addresses to be valid + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*auth_add_grant)(void *cls, + const struct CHALLENGER_ValidationNonceP *nonce, + const struct CHALLENGER_AccessTokenP *grant, + struct GNUNET_TIME_Relative grant_expiration, + struct GNUNET_TIME_Relative address_expiration); + + + /** + * Return @a address which @a grant gives access to. + * + * @param cls closure + * @param grant grant token that grants access + * @param[out] address set to the address under @a grant + * @param[out] address_expiration set to how long we consider @a address to be valid + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*info_get_grant)(void *cls, + const struct CHALLENGER_AccessTokenP *grant, + char **address, + struct GNUNET_TIME_Timestamp *address_expiration); + + }; #endif