commit bfa7d7574be58fa401dc8a7e2602c8237a2299fc
parent 1c71613d16918ab9d97e75f336a393d79892485e
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 2 Sep 2025 23:00:14 +0200
finish (untested) background work for MFA logic (#9816/#9817)
Diffstat:
13 files changed, 654 insertions(+), 818 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -196,6 +196,10 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-post-transfers.h \
taler-merchant-httpd_private-post-webhooks.c \
taler-merchant-httpd_private-post-webhooks.h \
+ taler-merchant-httpd_post-challenge-ID.c \
+ taler-merchant-httpd_post-challenge-ID.h \
+ taler-merchant-httpd_post-challenge-ID-confirm.c \
+ taler-merchant-httpd_post-challenge-ID-confirm.h \
taler-merchant-httpd_post-orders-ID-abort.c \
taler-merchant-httpd_post-orders-ID-abort.h \
taler-merchant-httpd_post-orders-ID-claim.c \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -95,6 +95,8 @@
#include "taler-merchant-httpd_private-post-token-families.h"
#include "taler-merchant-httpd_private-post-transfers.h"
#include "taler-merchant-httpd_private-post-webhooks.h"
+#include "taler-merchant-httpd_post-challenge-ID.h"
+#include "taler-merchant-httpd_post-challenge-ID-confirm.h"
#include "taler-merchant-httpd_post-orders-ID-abort.h"
#include "taler-merchant-httpd_post-orders-ID-claim.h"
#include "taler-merchant-httpd_post-orders-ID-paid.h"
@@ -125,6 +127,10 @@ char *TMH_currency;
char *TMH_base_url;
+char *TMH_helper_email;
+
+char *TMH_helper_sms;
+
int TMH_force_audit;
struct TALER_MERCHANTDB_Plugin *TMH_db;
@@ -635,7 +641,7 @@ do_shutdown (void *cls)
TMH_force_gorc_resume ();
TMH_force_wallet_get_order_resume ();
TMH_force_wallet_refund_order_resume ();
- TMH_mfa_done ();
+ TMH_challenge_done ();
TALER_MHD_daemons_destroy ();
if (NULL != instance_eh)
{
@@ -1011,6 +1017,7 @@ full_url_track_callback (void *cls,
struct TMH_HandlerContext *hc;
hc = GNUNET_new (struct TMH_HandlerContext);
+ hc->connection = con;
GNUNET_async_scope_fresh (&hc->async_scope_id);
GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
hc->full_url = GNUNET_strdup (full_url);
@@ -1069,7 +1076,8 @@ process_basic_auth (struct TMH_HandlerContext *hc,
* @return TALER_EC_NONE on success.
*/
static enum TALER_ErrorCode
-process_bearer_auth (struct TMH_HandlerContext *hc, const char*authn_s)
+process_bearer_auth (struct TMH_HandlerContext *hc,
+ const char *authn_s)
{
if (NULL == hc->instance)
{
@@ -1959,6 +1967,23 @@ url_handler (void *cls,
.handler = &TMH_post_using_templates_ID,
.max_upload = 1024 * 1024
},
+ /* POST /challenge/$ID: */
+ {
+ .url_prefix = "/challenge/",
+ .method = MHD_HTTP_METHOD_POST,
+ .have_id_segment = true,
+ .handler = &TMH_post_challenge_ID,
+ .max_upload = 1024
+ },
+ /* POST /challenge/$ID/confirm: */
+ {
+ .url_prefix = "/challenge/",
+ .method = MHD_HTTP_METHOD_POST,
+ .have_id_segment = true,
+ .url_suffix = "confirm",
+ .handler = &TMH_post_challenge_ID_confirm,
+ .max_upload = 1024
+ },
/* POST /instances */
{
.url_prefix = "/instances",
@@ -2859,6 +2884,31 @@ run (void *cls,
return;
}
}
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant",
+ "HELPER_SMS",
+ &TMH_helper_sms))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
+ "merchant",
+ "HELPER_SMS",
+ "no helper specified");
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant",
+ "HELPER_EMAIL",
+ &TMH_helper_email))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
+ "merchant",
+ "HELPER_EMAIL",
+ "no helper specified");
+ }
+
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (cfg,
"merchant",
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
@@ -648,6 +648,11 @@ struct TMH_HandlerContext
char *infix;
/**
+ * Which connection was suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
* JSON body that was uploaded, NULL if @e has_body is false.
*/
json_t *request_body;
@@ -725,6 +730,16 @@ extern char *TMH_currency;
extern char *TMH_base_url;
/**
+ * Name of helper program to send e-mail.
+ */
+extern char *TMH_helper_email;
+
+/**
+ * Name of helper program to send SMS.
+ */
+extern char *TMH_helper_sms;
+
+/**
* Length of the TMH_cspecs array.
*/
extern unsigned int TMH_num_cspecs;
diff --git a/src/backend/taler-merchant-httpd_mfa.c b/src/backend/taler-merchant-httpd_mfa.c
@@ -23,607 +23,550 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_mfa.h"
-/**
- * How many attempts do we allow per solution at most? Note that
- * this is just for the API, the value must also match the
- * database logic in create_mfa_challenge.
- */
-#define MAX_SOLUTIONS 3
-
/**
- * How long is an OTP code valid?
+ * How many challenges do we allow at most per request?
*/
-#define OTP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
-
+#define MAX_CHALLENGES 4
/**
- * Internal state for MFA processing.
+ * How long are challenges valid?
*/
-struct MfaState
-{
-
- /**
- * Kept in a DLL.
- */
- struct MfaState *next;
+#define CHALLENGE_LIFETIME GNUNET_TIME_UNIT_DAYS
- /**
- * Kept in a DLL.
- */
- struct MfaState *prev;
-
- /**
- * HTTP request we are handling.
- */
- struct TMH_HandlerContext *hc;
-
- /**
- * Challenge code.
- */
- char *code;
-
- /**
- * When does @e code expire?
- */
- struct GNUNET_TIME_Absolute expiration_date;
-
- /**
- * When may we transmit a new code?
- */
- struct GNUNET_TIME_Absolute retransmission_date;
-
- /**
- * Handle to the helper process.
- */
- struct GNUNET_OS_Process *child;
-
- /**
- * Handle to wait for @e child
- */
- struct GNUNET_ChildWaitHandle *cwh;
- /**
- * Function to call with the result.
- */
- TMH_MFA_ResultCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Address hint to return to the client.
- */
- char *address_hint;
-
- /**
- * Message to send.
- */
- char *msg;
-
- /**
- * Offset of transmission in msg.
- */
- size_t msg_off;
-
- /**
- * ID of our challenge.
- */
- uint64_t challenge_id;
-
- /**
- * Salt used when hashing the request body.
- */
- struct TALER_MERCHANT_MFA_BodySalt salt;
-
- /**
- * Salted hash over the request body.
- */
- struct TALER_MERCHANT_MFA_BodyHash h_body;
-
- /**
- * Type of the next challenge.
- */
- enum TALER_MERCHANT_MFA_Channel next_challenge;
-
- /**
- * Operation this is about.
- */
- enum TALER_MERCHANT_MFA_CriticalOperation op;
-};
-
-
-/**
- * Kept in a DLL.
- */
-static struct MfaState *mfa_head;
-
-/**
- * Kept in a DLL.
- */
-static struct MfaState *mfa_tail;
-
-
-/**
- * Conclude @a mfa process with status @a ret and response
- * @a response
- *
- * @param[in] mfa MFA process to conclude and release resources for
- * @param ret final result of the MFA process
- * @param http_status HTTP status to give to the client if @ret is #GNUNET_NO
- * @param[in] response HTTP response to return if @a ret is #GNUNET_NO
- */
-static void
-finish_mfa (struct MfaState *mfa,
- enum GNUNET_GenericReturnValue ret,
- unsigned int http_status,
- struct MHD_Response *response)
+enum GNUNET_GenericReturnValue
+TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
+ const char *challenge_id,
+ uint64_t *challenge_serial,
+ struct TALER_MERCHANT_MFA_BodyHash *h_body)
{
- GNUNET_CONTAINER_DLL_remove (mfa_head,
- mfa_tail,
- mfa);
- mfa->cb (mfa->cb_cls,
- ret,
- http_status,
- response);
- if (NULL != mfa->cwh)
+ const char *dash = strchr (challenge_id,
+ '-');
+ unsigned long long ser;
+ char min;
+
+ if (NULL == dash)
{
- GNUNET_wait_child_cancel (mfa->cwh);
- mfa->cwh = NULL;
+ GNUNET_break_op (0);
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'-' missing in challenge ID"))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
}
- if (NULL != mfa->child)
+ if ( (2 !=
+ sscanf (challenge_id,
+ "%llu%c%*s",
+ &ser,
+ &min)) ||
+ ('-' != min) )
{
- (void) GNUNET_OS_process_kill (mfa->child,
- SIGKILL);
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (mfa->child));
- mfa->child = NULL;
+ GNUNET_break_op (0);
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Invalid number for challenge ID"))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
}
- GNUNET_free (mfa->msg);
- GNUNET_free (mfa->code);
- GNUNET_free (mfa->address_hint);
- GNUNET_free (mfa);
-}
-
-
-void
-TMH_mfa_done ()
-{
- struct MfaState *mfa;
-
- while (NULL != (mfa = mfa_head))
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (dash + 1,
+ strlen (dash) + 1,
+ h_body,
+ sizeof (*h_body)))
{
- finish_mfa (mfa,
- GNUNET_SYSERR,
- 0,
- NULL);
+ GNUNET_break_op (0);
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Malformed challenge ID"))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
}
+ *challenge_serial = (uint64_t) ser;
+ return GNUNET_OK;
}
/**
- * Obtain hint about the @a target_address of type @a channel to
- * return to the client.
+ * Check if the given authentication check was already completed.
*
- * @param channel type of challenge
- * @param target_address address we will sent the challenge to
- * @return hint for the user about the address
+ * @param[in,out] hc handler context of the connection to authorize
+ * @param op operation for which we are requiring authorization
+ * @param challenge_id ID of the challenge to check if it is done
+ * @param[out] solved set to true if the challenge was solved,
+ * set to false if @a challenge_id was not found
+ * @param[out] channel TAN channel that was used,
+ * set to #TALER_MERCHANT_MFA_CHANNEL_NONE if @a challenge_id
+ * was not found
+ * @param[out] target_address address which was validated,
+ * set to NULL if @a challenge_id was not found
+ * @param[out] retry_counter how many attempts are left on the challenge
+ * @return #GNUNET_OK on success (challenge found)
+ * #GNUNET_NO if an error message was returned to the client
+ * #GNUNET_SYSERR to just close the connection
*/
-static char *
-get_hint (enum TALER_MERCHANT_MFA_Channel channel,
- const char *target_address)
+static enum GNUNET_GenericReturnValue
+mfa_challenge_check (
+ struct TMH_HandlerContext *hc,
+ enum TALER_MERCHANT_MFA_CriticalOperation op,
+ const char *challenge_id,
+ bool *solved,
+ enum TALER_MERCHANT_MFA_Channel *channel,
+ char **target_address,
+ uint32_t *retry_counter)
{
- switch (channel)
+ uint64_t challenge_serial;
+ struct TALER_MERCHANT_MFA_BodyHash h_body;
+ struct TALER_MERCHANT_MFA_BodyHash x_h_body;
+ struct TALER_MERCHANT_MFA_BodySalt salt;
+ struct GNUNET_TIME_Absolute retransmission_date;
+ enum TALER_MERCHANT_MFA_CriticalOperation xop;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute confirmation_date;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TMH_mfa_parse_challenge_id (hc,
+ challenge_id,
+ &challenge_serial,
+ &h_body);
+ if (GNUNET_OK != ret)
+ return ret;
+ *target_address = NULL;
+ *solved = false;
+ *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
+ *retry_counter = UINT_MAX;
+ qs = TMH_db->lookup_mfa_challenge (TMH_db->cls,
+ hc->instance->settings.id,
+ challenge_serial,
+ &x_h_body,
+ &salt,
+ target_address,
+ &xop,
+ &confirmation_date,
+ &retransmission_date,
+ retry_counter,
+ channel);
+ switch (qs)
{
- case TALER_MERCHANT_MFA_CHANNEL_NONE:
- GNUNET_assert (0);
- return NULL;
- case TALER_MERCHANT_MFA_CHANNEL_SMS:
- {
- size_t slen = strlen (target_address);
- const char *end;
-
- if (slen > 4)
- end = &target_address[slen - 4];
- else
- end = &target_address[slen / 2];
- return GNUNET_strdup (end);
- }
- case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
- {
- const char *at;
- size_t len;
-
- at = strchr (target_address,
- '@');
- if (NULL == at)
- len = 0;
- else
- len = at - target_address;
- return GNUNET_strndup (target_address,
- len);
- }
- case TALER_MERCHANT_MFA_CHANNEL_TOTP:
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- return GNUNET_strdup ("TOTP is not implemented: #10327");
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return GNUNET_OK;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
- GNUNET_break (0);
- return NULL;
-}
-
-/**
- * Generate an error for @a mfa.
- *
- * @param[in] mfa process to generate an error response for
- * @param http_status HTTP status of the response
- * @param ec Taler error code to return
- * @param hint hint to return, can be NULL
- */
-static void
-respond_with_error (struct MfaState *mfa,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const char *hint)
-{
- finish_mfa (mfa,
- GNUNET_NO,
- http_status,
- TALER_MHD_make_error (ec,
- hint));
+ if (xop != op)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Challenge was for a different operation (%d!=%d)!\n",
+ (int) op,
+ (int) xop);
+ *solved = false;
+ return GNUNET_OK;
+ }
+ TALER_MERCHANT_mfa_body_hash (hc->request_body,
+ &salt,
+ &h_body);
+ if (0 !=
+ GNUNET_memcmp (&h_body,
+ &x_h_body))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Challenge was for a different request body!\n");
+ *solved = false;
+ return GNUNET_OK;
+ }
+ *solved = (! GNUNET_TIME_absolute_is_future (confirmation_date));
+ return GNUNET_OK;
}
/**
- * Generate a response to the client pointing them to the challenge
- * that was sent.
+ * Multi-factor authentication check to see if for the given @a instance_id
+ * and the @a op operation all the TAN channels given in @a required_tans have
+ * been satisfied. Note that we always satisfy @a required_tans in the order
+ * given in the array, so if the last one is satisfied, all previous ones must
+ * have been satisfied before.
*
- * @param[in] mfa process to generate response for
- */
-static void
-respond_with_challenge (struct MfaState *mfa)
-{
- finish_mfa (
- mfa,
- GNUNET_NO,
- MHD_HTTP_FORBIDDEN, /* FIXME: check HTTP status code! Maybe use ACCEPTED? */
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_string ("address_hint",
- mfa->address_hint),
- GNUNET_JSON_pack_string ("challenge_type",
- TALER_MERCHANT_MFA_channel_to_string (
- mfa->next_challenge)),
- GNUNET_JSON_pack_uint64 ("challenge_id",
- mfa->challenge_id),
- GNUNET_JSON_pack_data_auto ("h_body",
- &mfa->h_body)));
-}
-
-
-/**
- * Function called when our SMS helper has terminated.
+ * If the challenges has not been satisfied, an appropriate response
+ * is returned to the client of @a hc.
*
- * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
- * @param type type of the process
- * @param exit_code status code of the process
+ * @param[in,out] hc handler context of the connection to authorize
+ * @param op operation for which we are performing
+ * @param channel TAN channel to try
+ * @param expiration_date when should the challenge expire
+ * @param required_address addresses to use for
+ * the respective challenge
+ * @param[out] challenge_id set to the challenge ID, to be freed by
+ * the caller
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error message was returned to the client
+ * #GNUNET_SYSERR to just close the connection
*/
-static void
-transmission_done_cb (void *cls,
- enum GNUNET_OS_ProcessStatusType type,
- long unsigned int exit_code)
+static enum GNUNET_GenericReturnValue
+mfa_challenge_start (
+ struct TMH_HandlerContext *hc,
+ enum TALER_MERCHANT_MFA_CriticalOperation op,
+ enum TALER_MERCHANT_MFA_Channel channel,
+ struct GNUNET_TIME_Absolute expiration_date,
+ const char *required_address,
+ char **challenge_id)
{
- struct MfaState *mfa = cls;
enum GNUNET_DB_QueryStatus qs;
-
- mfa->cwh = NULL;
- if (NULL != mfa->child)
- {
- GNUNET_OS_process_destroy (mfa->child);
- mfa->child = NULL;
- }
- if (0 == mfa->challenge_id)
- {
- /* 0: fresh challenge, must create one */
- qs = TMH_db->create_mfa_challenge (TMH_db->cls,
- mfa->hc->instance->settings.id,
- mfa->op,
- &mfa->h_body,
- &mfa->salt,
- mfa->code,
- mfa->expiration_date,
- mfa->retransmission_date,
- mfa->next_challenge,
- mfa->address_hint,
- &mfa->challenge_id);
- }
- else
- {
- qs = TMH_db->update_mfa_challenge (TMH_db->cls,
- mfa->challenge_id,
- mfa->code,
- MAX_SOLUTIONS,
- mfa->expiration_date,
- mfa->retransmission_date);
- }
+ struct TALER_MERCHANT_MFA_BodySalt salt;
+ struct TALER_MERCHANT_MFA_BodyHash h_body;
+ uint64_t challenge_serial;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &salt,
+ sizeof (salt));
+ TALER_MERCHANT_mfa_body_hash (hc->request_body,
+ &salt,
+ &h_body);
+ qs = TMH_db->create_mfa_challenge (TMH_db->cls,
+ hc->instance->settings.id,
+ op,
+ &h_body,
+ &salt,
+ NULL,
+ expiration_date,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ channel,
+ required_address,
+ &challenge_serial);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- return;
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
- return;
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "no results on INSERT, but success?");
- return;
+ GNUNET_assert (0);
+ break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- respond_with_challenge (mfa);
+ {
+ char *h_body_s;
+
+ h_body_s = GNUNET_STRINGS_data_to_string_alloc (&h_body,
+ sizeof (h_body));
+ GNUNET_asprintf (challenge_id,
+ "%llu-%s",
+ (unsigned long long) challenge_serial,
+ h_body_s);
+ GNUNET_free (h_body_s);
+ }
+ return GNUNET_OK;
}
/**
- * Setup challenge code for @a mfa and send it to the
- * @a required_address; on success.
+ * Internal book-keeping for #TMH_mfa_challenges_do().
+ */
+struct Challenge
+{
+ /**
+ * Channel on which the challenge is transmitted.
+ */
+ enum TALER_MERCHANT_MFA_Channel channel;
+
+ /**
+ * Address to send the challenge to.
+ */
+ const char *required_address;
+
+ /**
+ * Internal challenge ID.
+ */
+ char *challenge_id;
+
+ /**
+ * True if the challenge was solved.
+ */
+ bool solved;
+
+ /**
+ * True if the challenge could still be solved.
+ */
+ bool solvable;
+
+};
+
+
+/**
+ * Check that a set of MFA challenges has been satisfied by the
+ * client for the request in @a hc.
*
- * @param[in,out] mfa process to send the challenge for
- * @param required_address where to send the challenge
+ * @param[in,out] hc handler context with the connection to the client
+ * @param op operation for which we should check challenges for
+ * @param combi_and true to tell the client to solve all challenges (AND),
+ * false means that any of the challenges will do (OR)
+ * @param ... pairs of channel and address, terminated by
+ * #TALER_MERCHANT_MFA_CHANNEL_NONE
+ * @return #GNUNET_OK on success (challenges satisfied)
+ * #GNUNET_NO if an error message was returned to the client
+ * #GNUNET_SYSERR to just close the connection
*/
-static void
-send_challenge (struct MfaState *mfa,
- const char *required_address)
+enum GNUNET_GenericReturnValue
+TMH_mfa_challenges_do (
+ struct TMH_HandlerContext *hc,
+ enum TALER_MERCHANT_MFA_CriticalOperation op,
+ bool combi_and,
+ ...)
{
- const char *prog;
+ struct Challenge challenges[MAX_CHALLENGES];
+ const char *challenge_ids[MAX_CHALLENGES];
+ size_t num_challenges;
+ char *challenge_ids_copy = NULL;
+ size_t num_provided_challenges;
+ enum GNUNET_GenericReturnValue ret;
- switch (mfa->next_challenge)
- {
- case TALER_MERCHANT_MFA_CHANNEL_NONE:
- GNUNET_assert (0);
- break;
- case TALER_MERCHANT_MFA_CHANNEL_SMS:
- mfa->expiration_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- mfa->retransmission_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- GNUNET_asprintf (&mfa->code,
- "%llu",
- (unsigned long long)
- GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
- 100000000));
- prog = "taler-merchant-helper-send-sms";
- break;
- case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
- mfa->expiration_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- mfa->retransmission_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- GNUNET_asprintf (&mfa->code,
- "%llu",
- (unsigned long long)
- GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
- 100000000));
- prog = "taler-merchant-helper-send-email";
- break;
- case TALER_MERCHANT_MFA_CHANNEL_TOTP:
- mfa->expiration_date
- = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
- mfa->retransmission_date
- = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
- respond_with_error (mfa,
- MHD_HTTP_NOT_IMPLEMENTED,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "#10327");
- return;
- }
{
- /* Start child process and feed pipe */
- struct GNUNET_DISK_PipeHandle *p;
- struct GNUNET_DISK_FileHandle *pipe_stdin;
+ va_list ap;
- p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
- if (NULL == p)
- {
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "pipe");
- return;
- }
- mfa->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
- p,
- NULL,
- NULL,
- prog,
- prog,
- required_address,
- NULL);
- if (NULL == mfa->child)
+ va_start (ap,
+ combi_and);
+ for (num_challenges = 0;
+ num_challenges < MAX_CHALLENGES;
+ num_challenges++)
{
- GNUNET_DISK_pipe_close (p);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
- "exec");
- return;
+ enum TALER_MERCHANT_MFA_Channel channel;
+ const char *address;
+
+ channel = va_arg (ap,
+ enum TALER_MERCHANT_MFA_Channel);
+ if (TALER_MERCHANT_MFA_CHANNEL_NONE == channel)
+ break;
+ address = va_arg (ap,
+ const char *);
+ GNUNET_assert (NULL != address);
+ challenges[num_challenges].channel = channel;
+ challenges[num_challenges].required_address = address;
+ challenges[num_challenges].challenge_id = NULL;
+ challenges[num_challenges].solved = false;
+ challenges[num_challenges].solvable = true;
}
+ va_end (ap);
+ }
+
+ if (0 == num_challenges)
+ {
+ /* No challenges required. Strange... */
+ return GNUNET_OK;
+ }
- 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 (&mfa->msg,
- "%s\nTaler-Merchant:\n%s",
- mfa->code,
- mfa->hc->instance->settings.id);
+ {
+ const char *challenge_ids_header;
+
+ challenge_ids_header
+ = MHD_lookup_connection_value (hc->connection,
+ MHD_HEADER_KIND,
+ "Taler-Challenge-Ids");
+ num_provided_challenges = 0;
+ if (NULL != challenge_ids_header)
{
- const char *off = mfa->msg;
- size_t left = strlen (off);
+ challenge_ids_copy = GNUNET_strdup (challenge_ids_header);
- while (0 != left)
+ for (char *token = strtok (challenge_ids_copy,
+ ",");
+ NULL != token;
+ token = strtok (NULL,
+ ","))
{
- ssize_t ret;
-
- ret = GNUNET_DISK_file_write (pipe_stdin,
- off,
- left);
- if (ret <= 0)
+ if (num_provided_challenges > MAX_CHALLENGES)
{
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
- "write");
- return;
+ GNUNET_break_op (0);
+ GNUNET_free (challenge_ids_copy);
+ return (MHD_NO ==
+ TALER_MHD_reply_with_error (
+ hc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ "Taler-Challenge-Ids"))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
}
- mfa->msg_off += ret;
- off += ret;
- left -= ret;
+ challenge_ids[num_provided_challenges] = token;
+ num_provided_challenges++;
}
- GNUNET_DISK_file_close (pipe_stdin);
}
}
- mfa->cwh = GNUNET_wait_child (mfa->child,
- &transmission_done_cb,
- mfa);
-}
-
-
-void
-TMH_mfa_challenge_check (
- struct TMH_HandlerContext *hc,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
- enum TALER_MERCHANT_MFA_Channel required_tans[],
- const char *required_addresses[],
- TMH_MFA_ResultCallback cb,
- void *cb_cls)
-{
- struct MfaState *mfa;
- enum TALER_MERCHANT_MFA_Channel last_channel;
- uint32_t retry_counter;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute confirmation_date;
- mfa = GNUNET_new (struct MfaState);
- mfa->hc = hc;
- mfa->cb = cb;
- mfa->cb_cls = cb_cls;
- mfa->op = op;
- GNUNET_CONTAINER_DLL_insert (mfa_head,
- mfa_tail,
- mfa);
- if (TALER_MERCHANT_MFA_CHANNEL_NONE == required_tans[0])
+ /* Check provided challenges against requirements */
+ for (size_t i = 0; i < num_provided_challenges; i++)
{
- finish_mfa (mfa,
- GNUNET_OK,
- 0,
- NULL);
- return;
+ bool solved;
+ enum TALER_MERCHANT_MFA_Channel channel;
+ char *target_address;
+ uint32_t retry_counter;
+
+ ret = mfa_challenge_check (hc,
+ op,
+ challenge_ids[i],
+ &solved,
+ &channel,
+ &target_address,
+ &retry_counter);
+ if (GNUNET_OK != ret)
+ goto cleanup;
+ for (size_t j = 0; j < num_challenges; j++)
+ {
+ if ( (challenges[j].channel == channel) &&
+ (NULL == challenges[j].challenge_id) &&
+ (NULL != target_address /* just to be sure */) &&
+ (0 == strcmp (target_address,
+ challenges[j].required_address) ) )
+ {
+ challenges[j].solved
+ = solved;
+ challenges[j].challenge_id
+ = GNUNET_strdup (challenge_ids[i]);
+ if ( (! solved) &&
+ (0 == retry_counter) )
+ {
+ /* can't be solved anymore! */
+ challenges[i].solvable = false;
+ }
+ break;
+ }
+ }
+ GNUNET_free (target_address);
}
- qs = TMH_db->lookup_mfa_challenge (TMH_db->cls,
- hc->instance->settings.id,
- op,
- hc->request_body,
- &mfa->h_body,
- &mfa->salt,
- &confirmation_date,
- &mfa->retransmission_date,
- &retry_counter,
- &last_channel,
- &mfa->challenge_id);
- switch (qs)
+
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &mfa->salt,
- sizeof (mfa->salt));
- TALER_MERCHANT_mfa_body_hash (hc->request_body,
- &mfa->salt,
- &mfa->h_body);
- mfa->next_challenge = required_tans[0];
- mfa->address_hint = get_hint (mfa->next_challenge,
- required_addresses[0]);
- send_challenge (mfa,
- required_addresses[0]);
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
+ struct GNUNET_TIME_Absolute expiration_date
+ = GNUNET_TIME_relative_to_absolute (CHALLENGE_LIFETIME);
+
+ /* Start new challenges for unsolved requirements */
+ for (size_t i = 0; i < num_challenges; i++)
+ {
+ if (NULL == challenges[i].challenge_id)
+ {
+ GNUNET_assert (! challenges[i].solved);
+ GNUNET_assert (challenges[i].solvable);
+ ret = mfa_challenge_start (hc,
+ op,
+ challenges[i].channel,
+ expiration_date,
+ challenges[i].required_address,
+ &challenges[i].challenge_id);
+ if (GNUNET_OK != ret)
+ goto cleanup;
+ }
+ }
}
- for (unsigned int i = 0;
- TALER_MERCHANT_MFA_CHANNEL_NONE != required_tans[i];
- i++)
+
{
- if (last_channel == required_tans[i])
+ bool all_solved = true;
+ bool any_solved = false;
+ bool solvable = true;
+
+ for (size_t i = 0; i < num_challenges; i++)
{
- if (GNUNET_TIME_absolute_is_past (confirmation_date))
+ if (challenges[i].solved)
{
- /* Last challenge WAS solved, move on! */
- mfa->next_challenge = required_tans[i + 1];
- if (TALER_MERCHANT_MFA_CHANNEL_NONE == mfa->next_challenge)
- {
- finish_mfa (mfa,
- GNUNET_OK,
- 0,
- NULL);
- return;
- }
- mfa->address_hint = get_hint (mfa->next_challenge,
- required_addresses[i + 1]);
- send_challenge (mfa,
- required_addresses[i + 1]);
- return;
+ any_solved = true;
}
else
{
- /* challenge was not yet solved, possibly re-transmit
- a new one */
- mfa->next_challenge = last_channel;
- mfa->address_hint = get_hint (mfa->next_challenge,
- required_addresses[i]);
- if (GNUNET_TIME_absolute_is_past (mfa->retransmission_date))
- {
- send_challenge (mfa,
- required_addresses[i]);
- return;
- }
- respond_with_challenge (mfa);
- return;
+ all_solved = false;
+ if (combi_and &&
+ (! challenges[i].solvable) )
+ solvable = false;
}
}
+
+ if ( (combi_and && all_solved) ||
+ (! combi_and && any_solved) )
+ {
+ /* Authorization successful */
+ ret = GNUNET_OK;
+ goto cleanup;
+ }
+ if (! solvable)
+ {
+ ret = (MHD_NO ==
+ TALER_MHD_reply_with_error (
+ hc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_MFA_FORBIDDEN,
+ GNUNET_TIME_relative2s (CHALLENGE_LIFETIME,
+ false)))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
+ goto cleanup;
+ }
}
- /* challenge was satisfied that is not in our list! */
- GNUNET_break (0);
- respond_with_error (mfa,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "unexpected challenge already satisfied");
+
+ /* Return challenges to client */
+ {
+ json_t *jchallenges;
+
+ jchallenges = json_array ();
+ GNUNET_assert (NULL != jchallenges);
+ for (size_t i = 0; i<num_challenges; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ jchallenges,
+ json_string (challenges[i].challenge_id)));
+ }
+ ret = (MHD_NO ==
+ TALER_MHD_REPLY_JSON_PACK (
+ hc->connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_bool ("combi_and",
+ combi_and),
+ GNUNET_JSON_pack_array_steal ("challenges",
+ jchallenges)))
+ ? GNUNET_SYSERR
+ : GNUNET_NO;
+ }
+
+cleanup:
+ for (size_t i = 0; i < num_challenges; i++)
+ GNUNET_free (challenges[i].challenge_id);
+ GNUNET_free (challenge_ids_copy);
+ return ret;
}
diff --git a/src/backend/taler-merchant-httpd_mfa.h b/src/backend/taler-merchant-httpd_mfa.h
@@ -30,59 +30,43 @@
/**
- * Function called with the result of an MFA operation.
+ * Parse the given @a challenge_id into its parts.
*
- * @param cls closure
- * @param res #GNUNET_OK if all challenges have been satisfied
- * #GNUNET_NO if a challenge response was returned to the client
- * #GNUNET_SYSERR to just close the connection without response
- * @param http_status HTTP status code of @a response, non-zero only if
- * @a res is #GNUNET_NO
- * @param response HTTP response to queue, only if @a res is #GNUNET_NO
+ * @param[in,out] hc handler context with the connection to the client
+ * @param challenge_id challenge of "$NUMBER-$H_BODY"
+ * @param[out] challenge_serial set to $NUMBER
+ * @param[out] h_body set to $H_BODY (decoded)
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error message was returned to the client
+ * #GNUNET_SYSERR to just close the connection
*/
-typedef void
-(*TMH_MFA_ResultCallback)(void *cls,
- enum GNUNET_GenericReturnValue res,
- unsigned int http_status,
- struct MHD_Response *response);
+enum GNUNET_GenericReturnValue
+TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
+ const char *challenge_id,
+ uint64_t *challenge_serial,
+ struct TALER_MERCHANT_MFA_BodyHash *h_body);
/**
- * Multi-factor authentication check to see if for the given @a instance_id
- * and the @a op operation all the TAN channels given in @a required_tans have
- * been satisfied. Note that we always satisfy @a required_tans in the order
- * given in the array, so if the last one is satisfied, all previous ones must
- * have been satisfied before.
+ * Check that a set of MFA challenges has been satisfied by the
+ * client for the request in @a hc.
*
- * If the challenges has not been satisfied, an appropriate response
- * is returned to the client of @a hc.
- *
- * @param[in,out] hc handler context of the connection to authorize
- * @param op operation for which we are performing
- * @param required_tans array of TAN channels to try,
- * terminated with #TALER_MERCHANT_MFA_CHANNEL_NONE
- * @param required_addresses array of addresses to use for
- * the respective challenge, NULL-terminated, same length
- * as @a required_tans
- * @param cb continuation to call with the result
- * @param cb_cls closure for @a cb
+ * @param[in,out] hc handler context with the connection to the client
+ * @param op operation for which we should check challenges for
+ * @param combi_and true to tell the client to solve all challenges (AND),
+ * false means that any of the challenges will do (OR)
+ * @param ... pairs of channel and address, terminated by
+ * #TALER_MERCHANT_MFA_CHANNEL_NONE
+ * @return #GNUNET_OK on success (challenges satisfied)
+ * #GNUNET_NO if an error message was returned to the client
+ * #GNUNET_SYSERR to just close the connection
*/
-void
-TMH_mfa_challenge_check (
+enum GNUNET_GenericReturnValue
+TMH_mfa_challenges_do (
struct TMH_HandlerContext *hc,
enum TALER_MERCHANT_MFA_CriticalOperation op,
- enum TALER_MERCHANT_MFA_Channel required_tans[],
- const char *required_addresses[],
- TMH_MFA_ResultCallback cb,
- void *cb_cls);
-
-
-/**
- * Function to call when the HTTP server is shutting down to
- * clean up all ongoing MFA challenge processes.
- */
-void
-TMH_mfa_done (void);
+ bool combi_and,
+ ...);
#endif
diff --git a/src/backenddb/pg_create_mfa_challenge.c b/src/backenddb/pg_create_mfa_challenge.c
@@ -37,7 +37,7 @@ TMH_PG_create_mfa_challenge (
struct GNUNET_TIME_Absolute expiration_date,
struct GNUNET_TIME_Absolute retransmission_date,
enum TALER_MERCHANT_MFA_Channel tan_channel,
- const char *tan_info,
+ const char *required_address,
uint64_t *challenge_id)
{
struct PostgresClosure *pg = cls;
@@ -54,9 +54,7 @@ TMH_PG_create_mfa_challenge (
GNUNET_PQ_query_param_absolute_time (&retransmission_date),
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (channel_str),
- (NULL == tan_info)
- ? GNUNET_PQ_query_param_null ()
- : GNUNET_PQ_query_param_string (tan_info), /* $10 */
+ GNUNET_PQ_query_param_string (required_address), /* $10 */
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
@@ -78,7 +76,7 @@ TMH_PG_create_mfa_challenge (
" ,retry_counter"
" ,merchant_serial"
" ,tan_channel"
- " ,tan_info)"
+ " ,required_address)"
" SELECT"
" $1, $2, $3, $4, $5, $6, $7, 3, merchant_serial, $9, $10"
" FROM merchant_instances"
diff --git a/src/backenddb/pg_create_mfa_challenge.h b/src/backenddb/pg_create_mfa_challenge.h
@@ -39,8 +39,8 @@
* @param retansmission_date when do we next allow retransmission
* of the challenge
* @param tan_channel which channel was used
- * @param tan_info information message to give to the user
- * (about where the challenge was sent), can be NULL
+ * @param required_address address
+ * where the challenge is to be sent
* @param[out] challenge_id set to the ID of the new challenge
* @return database result code
*/
@@ -55,7 +55,7 @@ TMH_PG_create_mfa_challenge (
struct GNUNET_TIME_Absolute expiration_date,
struct GNUNET_TIME_Absolute retransmission_date,
enum TALER_MERCHANT_MFA_Channel tan_channel,
- const char *tan_info,
+ const char *required_address,
uint64_t *challenge_id);
#endif
diff --git a/src/backenddb/pg_lookup_mfa_challenge.c b/src/backenddb/pg_lookup_mfa_challenge.c
@@ -25,204 +25,82 @@
#include "pg_lookup_mfa_challenge.h"
#include "pg_helper.h"
-/**
- * Closure for #handle_mfa_challenge().
- */
-struct LookupChallengeContext
-{
- /**
- * Provides the body to check against.
- */
- const json_t *body;
-
- /**
- * Set to the resulting salted hash of @e body.
- */
- struct TALER_MERCHANT_MFA_BodyHash *h_body;
-
- /**
- * Set to the salt used to hash @e body.
- */
- struct TALER_MERCHANT_MFA_BodySalt *salt;
-
- /**
- * Set to when the challenge we completed.
- */
- struct GNUNET_TIME_Absolute *confirmation_date;
-
- /**
- * Set to the next possible retransmission date for the challenge.
- */
- struct GNUNET_TIME_Absolute *retransmission_date;
-
- /**
- * Set to the remaining retry counter for this challenge.
- */
- uint32_t *retry_counter;
-
- /**
- * Set to the channel used to transmit this challenge.
- */
- enum TALER_MERCHANT_MFA_Channel *tan_channel;
-
- /**
- * Set to the unique ID of the challenge in our backend.
- */
- uint64_t *challenge_id;
-
- /**
- * Set to true if a body matched and any of the other fields were
- * initialized.
- */
- bool body_matched;
-
- /**
- * Set to true on errors.
- */
- bool extract_failed;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results about products.
- *
- * @param[in,out] cls of type `struct LookupChallengeContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-handle_mfa_challenge (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupChallengeContext *ctx = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MERCHANT_MFA_BodyHash h_body;
- struct TALER_MERCHANT_MFA_BodySalt salt;
- char *channel_str = NULL;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_body",
- &h_body),
- GNUNET_PQ_result_spec_auto_from_type ("salt",
- &salt),
- GNUNET_PQ_result_spec_absolute_time ("confirmation_date",
- ctx->confirmation_date),
- GNUNET_PQ_result_spec_absolute_time ("retransmission_date",
- ctx->retransmission_date),
- GNUNET_PQ_result_spec_uint32 ("retry_counter",
- ctx->retry_counter),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("tan_channel",
- &channel_str),
- NULL),
- GNUNET_PQ_result_spec_uint64 ("challenge_id",
- ctx->challenge_id),
- GNUNET_PQ_result_spec_end
- };
- struct TALER_MERCHANT_MFA_BodyHash h2;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->extract_failed = true;
- return;
- }
- /* Note: missed optimization: only serialize and
- hash body once, only add salt each time... */
- TALER_MERCHANT_mfa_body_hash (ctx->body,
- &salt,
- &h2);
- if (0 == GNUNET_memcmp (&h2,
- &h_body))
- {
- ctx->body_matched = true;
- *ctx->tan_channel = TALER_MERCHANT_MFA_channel_from_string (channel_str);
- *ctx->h_body = h2;
- *ctx->salt = salt;
- }
- GNUNET_PQ_cleanup_result (rs);
- if (ctx->body_matched)
- break;
- }
-}
-
enum GNUNET_DB_QueryStatus
TMH_PG_lookup_mfa_challenge (
void *cls,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
- const json_t *body,
- struct TALER_MERCHANT_MFA_BodyHash *h_body,
+ uint64_t challenge_id,
+ const struct TALER_MERCHANT_MFA_BodyHash *h_body,
struct TALER_MERCHANT_MFA_BodySalt *salt,
+ char **required_address,
+ enum TALER_MERCHANT_MFA_CriticalOperation *op,
struct GNUNET_TIME_Absolute *confirmation_date,
struct GNUNET_TIME_Absolute *retransmission_date,
uint32_t *retry_counter,
- enum TALER_MERCHANT_MFA_Channel *tan_channel,
- uint64_t *challenge_id)
+ enum TALER_MERCHANT_MFA_Channel *tan_channel)
{
struct PostgresClosure *pg = cls;
- struct LookupChallengeContext ctx = {
- .body = body,
- .h_body = h_body,
- .salt = salt,
- .confirmation_date = confirmation_date,
- .retransmission_date = retransmission_date,
- .retry_counter = retry_counter,
- .tan_channel = tan_channel,
- .challenge_id = challenge_id
- };
- const char *op_str = TALER_MERCHANT_MFA_co_to_string (op);
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_string (op_str),
+ GNUNET_PQ_query_param_uint64 (&challenge_id),
+ GNUNET_PQ_query_param_auto_from_type (h_body),
GNUNET_PQ_query_param_end
};
+ char *op_str;
+ char *chan_str;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("op",
+ &op_str),
+ GNUNET_PQ_result_spec_auto_from_type ("salt",
+ salt),
+ GNUNET_PQ_result_spec_absolute_time ("confirmation_date",
+ confirmation_date),
+ GNUNET_PQ_result_spec_absolute_time ("retransmission_date",
+ retransmission_date),
+ GNUNET_PQ_result_spec_uint32 ("retry_counter",
+ retry_counter),
+ GNUNET_PQ_result_spec_string ("tan_channel",
+ &chan_str),
+ GNUNET_PQ_result_spec_string ("required_address",
+ required_address),
+ GNUNET_PQ_result_spec_end
+ };
enum GNUNET_DB_QueryStatus qs;
PREPARE (pg,
"lookup_mfa_challenge",
"SELECT "
- " h_body"
+ " op::TEXT"
" ,salt"
" ,confirmation_date"
" ,retransmission_date"
" ,retry_counter"
- " ,tan_channel"
- " ,challenge_id"
+ " ,required_address"
+ " ,tan_channel::TEXT"
" FROM tan_challenges"
" WHERE merchant_serial IN"
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id = $1)"
- " AND (tc.op = $2)"
+ " AND (tc.challenge_id = $2)"
+ " AND (tc.h_body = $3)"
" AND (expiration_date / 1000 / 1000"
" > EXTRACT(EPOCH FROM NOW()"
- " AT TIME ZONE 'UTC')::bigint)"
- " ORDER BY creation_date DESC");
+ " AT TIME ZONE 'UTC')::bigint)");
/* Initialize to conservative values in case qs ends up <= 0 */
*tan_channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
+ *op = TALER_MERCHANT_MFA_CO_NONE;
*retry_counter = 0;
- *challenge_id = 0;
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_mfa_challenge",
- params,
- &handle_mfa_challenge,
- &ctx);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_mfa_challenge",
+ params,
+ rs);
if (qs <= 0)
return qs;
- if (ctx.extract_failed)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! ctx.body_matched)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ *tan_channel = TALER_MERCHANT_MFA_channel_from_string (chan_str);
+ *op = TALER_MERCHANT_MFA_co_from_string (op_str);
+ GNUNET_free (chan_str);
+ GNUNET_free (op_str);
return qs;
}
diff --git a/src/backenddb/pg_lookup_mfa_challenge.h b/src/backenddb/pg_lookup_mfa_challenge.h
@@ -32,10 +32,11 @@
*
* @param cls closure
* @param instance_id instance for which the challenge is being looked up
- * @param op operation that triggered the MFA request
- * @param body body to match
- * @param[out] h_body hash of the request body
+ * @param challenge_id set to the ID of the challenge
+ * @param h_body hash of the request body
* @param[out] salt salt used to compute @a h_body
+ * @param[out] required address set to where the challenge is to be send
+ * @param[out] op operation that triggered the MFA request
* @param[out] confirmation_date when was the challenge solved,
* set to "GNUNET_TIME_ABSOLUTE_NEVER" if unsolved
* @param[out] retransmission_date set to when a fresh challenge
@@ -43,22 +44,21 @@
* @param[out] retry_counter set to the number of attempts that remain
* for solving the challenge (after this time)
* @param[out] tan_channel which channel was used
- * @param[out] challenge_id set to the ID of the new challenge
* @return database result code
*/
enum GNUNET_DB_QueryStatus
TMH_PG_lookup_mfa_challenge (
void *cls,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
- const json_t *body,
- struct TALER_MERCHANT_MFA_BodyHash *h_body,
+ uint64_t challenge_id,
+ const struct TALER_MERCHANT_MFA_BodyHash *h_body,
struct TALER_MERCHANT_MFA_BodySalt *salt,
+ char **required_address,
+ enum TALER_MERCHANT_MFA_CriticalOperation *op,
struct GNUNET_TIME_Absolute *confirmation_date,
struct GNUNET_TIME_Absolute *retransmission_date,
uint32_t *retry_counter,
- enum TALER_MERCHANT_MFA_Channel *tan_channel,
- uint64_t *challenge_id);
+ enum TALER_MERCHANT_MFA_Channel *tan_channel);
#endif
diff --git a/src/backenddb/pg_solve_mfa_challenge.c b/src/backenddb/pg_solve_mfa_challenge.c
@@ -31,19 +31,13 @@ TMH_PG_solve_mfa_challenge (
void *cls,
uint64_t challenge_id,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
const char *solution,
bool *solved,
- uint32_t *retry_counter,
- struct GNUNET_TIME_Absolute *retransmission_date,
- enum TALER_MERCHANT_MFA_Channel *tan_channel)
+ uint32_t *retry_counter)
{
struct PostgresClosure *pg = cls;
- bool no_date;
bool no_match;
- char *channel_str;
- struct GNUNET_TIME_Absolute confirmation_date;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&challenge_id),
GNUNET_PQ_query_param_string (instance_id),
@@ -55,21 +49,9 @@ TMH_PG_solve_mfa_challenge (
GNUNET_PQ_result_spec_bool ("out_solved",
solved),
GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_absolute_time ("out_confirmation_date",
- &confirmation_date),
- &no_date),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_absolute_time ("out_retransmission_date",
- retransmission_date),
- &no_match),
- GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint32 ("out_retry_counter",
retry_counter),
&no_match),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_string ("out_tan_channel",
- &channel_str),
- &no_match),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -82,10 +64,7 @@ TMH_PG_solve_mfa_challenge (
"solve_mfa_challenge",
"SELECT"
" out_solved"
- " ,out_confirmation_date"
- " ,out_retransmission_date"
" ,out_retry_counter"
- " ,out_tan_channel"
" FROM merchant_do_insert_issued_token"
" ($1, $2, $3, $4);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
@@ -99,11 +78,6 @@ TMH_PG_solve_mfa_challenge (
GNUNET_PQ_cleanup_result (rs);
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
- GNUNET_break (NULL != channel_str);
- GNUNET_break ( (! solved) ||
- ( (! no_date) &&
- GNUNET_TIME_absolute_is_past (confirmation_date) ) );
- *tan_channel = TALER_MERCHANT_MFA_channel_from_string (channel_str);
GNUNET_PQ_cleanup_result (rs);
return qs;
}
diff --git a/src/backenddb/pg_solve_mfa_challenge.h b/src/backenddb/pg_solve_mfa_challenge.h
@@ -34,16 +34,12 @@
* @param cls closure
* @param challenge_id challenge ID to be solved
* @param instance_id instance for which the challenge is being solved
- * @param op operation which the challenge authorizes
* @param h_body body of the operation the challenge authorizes
* @param solution proposed solution to be checked against the actual code
* @param[out] solved set to true if the challenge was solved by
* @a solution
* @param[out] retry_counter set to the number of attempts that remain
* for solving the challenge (after this time)
- * @param[out] retransmission_date set to when a fresh challenge
- * may be transmitted
- * @param[out] tan_channel set to the channel the challenge was sent on
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -51,12 +47,9 @@ TMH_PG_solve_mfa_challenge (
void *cls,
uint64_t challenge_id,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
const char *solution,
bool *solved,
- uint32_t *retry_counter,
- struct GNUNET_TIME_Absolute *retransmission_date,
- enum TALER_MERCHANT_MFA_Channel *tan_channel);
+ uint32_t *retry_counter);
#endif
diff --git a/src/backenddb/pg_solve_mfa_challenge.sql b/src/backenddb/pg_solve_mfa_challenge.sql
@@ -14,21 +14,20 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
-CREATE OR REPLACE FUNCTION merchant_do_solve_mfa_challenge (
+DROP FUNCTION IF EXISTS merchant_do_solve_mfa_challenge;
+CREATE FUNCTION merchant_do_solve_mfa_challenge (
IN in_challenge_id INT8,
IN in_instance_id TEXT,
IN in_h_body BYTEA,
IN in_solution TEXT,
OUT out_solved BOOLEAN,
- OUT out_confirmation_date INT8,
- OUT out_retransmission_date INT8,
- OUT out_retry_counter INT4,
- OUT out_tan_channel TEXT -- tan_enum
+ OUT out_retry_counter INT4
)
LANGUAGE plpgsql
AS $$
DECLARE
my_instance_id INT8;
+ my_confirmation_date INT8;
DECLARE
my_rec RECORD;
BEGIN
@@ -40,9 +39,7 @@ BEGIN
-- Check if challenge exists and matches
SELECT
tc.confirmation_date
- ,tc.retransmission_date
,tc.retry_counter
- ,tc.tan_channel
,(tc.code = in_solution) AS solved
INTO
my_rec
@@ -50,7 +47,10 @@ BEGIN
WHERE tc.challenge_id = in_challenge_id
AND tc.merchant_serial = my_instance_id
AND tc.h_body = in_h_body
- AND tc.expiration_date > EXTRACT(EPOCH FROM NOW()) * 1000000;
+ AND tc.expiration_date >
+ EXTRACT(EPOCH FROM NOW()
+ AT TIME ZONE 'UTC')::bigint
+ * 1000 * 1000;
IF NOT FOUND
THEN
@@ -58,27 +58,31 @@ BEGIN
RETURN;
END IF;
- out_confirmation_date = my_rec.confirmation_date;
- out_retransmission_date = my_rec.retransmission_date;
+ my_confirmation_date = my_rec.confirmation_date;
out_retry_counter = my_rec.retry_counter;
- out_tan_channel = my_rec.tan_channel;
out_solved = my_rec.solved;
-- Check if already solved before
- IF out_confirmation_date IS NOT NULL
+ IF my_confirmation_date IS NOT NULL
THEN
out_solved := TRUE;
RETURN;
END IF;
+ IF (0 == out_retry_counter)
+ THEN
+ out_solved = FALSE;
+ RETURN;
+ END IF;
+
IF out_solved
THEN
-- Newly solved, update DB!
- out_confirmation_date = EXTRACT(EPOCH FROM NOW()
- AT TIME ZONE 'UTC')::bigint
- * 1000 * 1000;
+ my_confirmation_date = EXTRACT(EPOCH FROM NOW()
+ AT TIME ZONE 'UTC')::bigint
+ * 1000 * 1000;
UPDATE tan_challenges
- SET confirmation_date = out_confirmation_date
+ SET confirmation_date = my_confirmation_date
WHERE challenge_id = in_challenge_id;
ELSE
-- Failed to solve, decrement retry counter
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -4173,10 +4173,11 @@ struct TALER_MERCHANTDB_Plugin
*
* @param cls closure
* @param instance_id instance for which the challenge is being looked up
- * @param op operation that triggered the MFA request
- * @param body request body to match against
- * @param[out] h_body hash of the request body
+ * @param challenge_id set to the ID of the challenge
+ * @param h_body hash of the request body
* @param[out] salt salt used to compute @a h_body
+ * @param[out] required address set to where the challenge is to be send
+ * @param[out] op operation that triggered the MFA request
* @param[out] confirmation_date when was the challenge solved,
* set to "GNUNET_TIME_ABSOLUTE_NEVER" if unsolved
* @param[out] retransmission_date set to when a fresh challenge
@@ -4184,22 +4185,21 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] retry_counter set to the number of attempts that remain
* for solving the challenge (after this time)
* @param[out] tan_channel which channel was used
- * @param[out] challenge_id set to the ID of the new challenge
* @return database result code
*/
enum GNUNET_DB_QueryStatus
(*lookup_mfa_challenge)(
void *cls,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
- const json_t *body,
- struct TALER_MERCHANT_MFA_BodyHash *h_body,
+ uint64_t challenge_id,
+ const struct TALER_MERCHANT_MFA_BodyHash *h_body,
struct TALER_MERCHANT_MFA_BodySalt *salt,
+ char **required_address,
+ enum TALER_MERCHANT_MFA_CriticalOperation *op,
struct GNUNET_TIME_Absolute *confirmation_date,
struct GNUNET_TIME_Absolute *retransmission_date,
uint32_t *retry_counter,
- enum TALER_MERCHANT_MFA_Channel *tan_channel,
- uint64_t *challenge_id);
+ enum TALER_MERCHANT_MFA_Channel *tan_channel);
/**
@@ -4211,16 +4211,12 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param challenge_id challenge ID to be solved
* @param instance_id instance for which the challenge is being solved
- * @param op operation which the challenge authorizes
* @param h_body body of the operation the challenge authorizes
* @param solution proposed solution to be checked against the actual code
* @param[out] solved set to true if the challenge was solved by
* @a solution
- * @param[out] retransmission_date set to when a fresh challenge
- * may be transmitted
* @param[out] retry_counter set to the number of attempts that remain
* for solving the challenge (after this time)
- * @param[out] tan_channel set to the channel the challenge was sent on
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -4228,13 +4224,10 @@ struct TALER_MERCHANTDB_Plugin
void *cls,
uint64_t challenge_id,
const char *instance_id,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
const char *solution,
bool *solved,
- uint32_t *retry_counter,
- struct GNUNET_TIME_Absolute *retransmission_date,
- enum TALER_MERCHANT_MFA_Channel *tan_channel);
+ uint32_t *retry_counter);
/**
@@ -4274,8 +4267,8 @@ struct TALER_MERCHANTDB_Plugin
* @param retansmission_date when do we next allow retransmission
* of the challenge
* @param tan_channel which channel was used
- * @param tan_info information message to give to the user
- * (about where the challenge was sent), can be NULL
+ * @param required_address address
+ * where the challenge is to be sent
* @param[out] challenge_id set to the ID of the new challenge
* @return database result code
*/
@@ -4290,7 +4283,7 @@ struct TALER_MERCHANTDB_Plugin
struct GNUNET_TIME_Absolute expiration_date,
struct GNUNET_TIME_Absolute retransmission_date,
enum TALER_MERCHANT_MFA_Channel tan_channel,
- const char *tan_info,
+ const char *required_address,
uint64_t *challenge_id);