merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_mfa.c (27852B)


      1 /*
      2   This file is part of TALER
      3   (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_mfa.c
     22  * @brief internal APIs for multi-factor authentication (MFA)
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd.h"
     27 #include "taler-merchant-httpd_mfa.h"
     28 #include "merchant-database/insert_mfa_challenge.h"
     29 #include "merchant-database/get_mfa_challenge.h"
     30 #include "merchant-database/update_instance_password_reset.h"
     31 
     32 
     33 /**
     34  * How many challenges do we allow at most per request?
     35  */
     36 #define MAX_CHALLENGES 9
     37 
     38 /**
     39  * How long are challenges valid?
     40  */
     41 #define CHALLENGE_LIFETIME GNUNET_TIME_UNIT_DAYS
     42 
     43 /**
     44  * How long after a password reset of an instance was started can
     45  * the next one be started at the earliest?
     46  */
     47 #define PASSWORD_RESET_DELAY \
     48         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
     49 
     50 
     51 enum GNUNET_GenericReturnValue
     52 TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
     53                             const char *challenge_id,
     54                             uint64_t *challenge_serial,
     55                             struct TALER_MERCHANT_MFA_BodyHash *h_body)
     56 {
     57   const char *dash = strchr (challenge_id,
     58                              '-');
     59   unsigned long long ser;
     60   char min;
     61 
     62   if (NULL == dash)
     63   {
     64     GNUNET_break_op (0);
     65     return (MHD_NO ==
     66             TALER_MHD_reply_with_error (hc->connection,
     67                                         MHD_HTTP_BAD_REQUEST,
     68                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     69                                         "'-' missing in challenge ID"))
     70       ? GNUNET_SYSERR
     71       : GNUNET_NO;
     72   }
     73   if ( (2 !=
     74         sscanf (challenge_id,
     75                 "%llu%c%*s",
     76                 &ser,
     77                 &min)) ||
     78        ('-' != min) )
     79   {
     80     GNUNET_break_op (0);
     81     return (MHD_NO ==
     82             TALER_MHD_reply_with_error (hc->connection,
     83                                         MHD_HTTP_BAD_REQUEST,
     84                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     85                                         "Invalid number for challenge ID"))
     86       ? GNUNET_SYSERR
     87       : GNUNET_NO;
     88   }
     89   if (GNUNET_OK !=
     90       GNUNET_STRINGS_string_to_data (dash + 1,
     91                                      strlen (dash + 1),
     92                                      h_body,
     93                                      sizeof (*h_body)))
     94   {
     95     GNUNET_break_op (0);
     96     return (MHD_NO ==
     97             TALER_MHD_reply_with_error (hc->connection,
     98                                         MHD_HTTP_BAD_REQUEST,
     99                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    100                                         "Malformed challenge ID"))
    101       ? GNUNET_SYSERR
    102       : GNUNET_NO;
    103   }
    104   *challenge_serial = (uint64_t) ser;
    105   return GNUNET_OK;
    106 }
    107 
    108 
    109 /**
    110  * Check if the given authentication check was already completed.
    111  *
    112  * @param[in,out] hc handler context of the connection to authorize
    113  * @param instance_name instance for which the challenge must have been issued
    114  * @param op operation for which we are requiring authorization
    115  * @param challenge_id ID of the challenge to check if it is done
    116  * @param[out] solved set to true if the challenge was solved,
    117  *             set to false if @a challenge_id was not found
    118  * @param[out] channel TAN channel that was used,
    119  *             set to #TALER_MERCHANT_MFA_CHANNEL_NONE if @a challenge_id
    120  *             was not found
    121  * @param[out] target_address address which was validated,
    122  *             set to NULL if @a challenge_id was not found
    123  * @param[out] retry_counter how many attempts are left on the challenge
    124  * @return #GNUNET_OK on success (challenge found)
    125  *         #GNUNET_NO if an error message was returned to the client
    126  *         #GNUNET_SYSERR to just close the connection
    127  */
    128 static enum GNUNET_GenericReturnValue
    129 mfa_challenge_check (
    130   struct TMH_HandlerContext *hc,
    131   const char *instance_name,
    132   enum TALER_MERCHANT_MFA_CriticalOperation op,
    133   const char *challenge_id,
    134   bool *solved,
    135   enum TALER_MERCHANT_MFA_Channel *channel,
    136   char **target_address,
    137   uint32_t *retry_counter)
    138 {
    139   uint64_t challenge_serial;
    140   struct TALER_MERCHANT_MFA_BodyHash h_body;
    141   struct TALER_MERCHANT_MFA_BodyHash x_h_body;
    142   struct TALER_MERCHANT_MFA_BodySalt salt;
    143   struct GNUNET_TIME_Absolute retransmission_date;
    144   enum TALER_MERCHANT_MFA_CriticalOperation xop;
    145   enum GNUNET_DB_QueryStatus qs;
    146   struct GNUNET_TIME_Absolute confirmation_date;
    147   enum GNUNET_GenericReturnValue ret;
    148   char *instance_id = NULL;
    149 
    150   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    151               "Checking status of challenge %s\n",
    152               challenge_id);
    153   ret = TMH_mfa_parse_challenge_id (hc,
    154                                     challenge_id,
    155                                     &challenge_serial,
    156                                     &x_h_body);
    157   if (GNUNET_OK != ret)
    158     return ret;
    159   *target_address = NULL;
    160   *solved = false;
    161   *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
    162   *retry_counter = UINT_MAX;
    163   qs = TALER_MERCHANTDB_get_mfa_challenge (TMH_db,
    164                                            challenge_serial,
    165                                            &x_h_body,
    166                                            &salt,
    167                                            target_address,
    168                                            &xop,
    169                                            &confirmation_date,
    170                                            &retransmission_date,
    171                                            retry_counter,
    172                                            channel,
    173                                            &instance_id);
    174   switch (qs)
    175   {
    176   case GNUNET_DB_STATUS_HARD_ERROR:
    177     GNUNET_break (0);
    178     return (MHD_NO ==
    179             TALER_MHD_reply_with_error (hc->connection,
    180                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    181                                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
    182                                         NULL))
    183       ? GNUNET_SYSERR
    184       : GNUNET_NO;
    185   case GNUNET_DB_STATUS_SOFT_ERROR:
    186     GNUNET_break (0);
    187     return (MHD_NO ==
    188             TALER_MHD_reply_with_error (hc->connection,
    189                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    190                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
    191                                         NULL))
    192       ? GNUNET_SYSERR
    193       : GNUNET_NO;
    194   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    195     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    196                 "Challenge %s not found\n",
    197                 challenge_id);
    198     return GNUNET_OK;
    199   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    200     break;
    201   }
    202   if (0 != strcmp (instance_name,
    203                    instance_id))
    204   {
    205     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    206                 "Challenge was issued for a different instance (%s!=%s)!\n",
    207                 instance_id,
    208                 instance_name);
    209     GNUNET_free (instance_id);
    210     GNUNET_free (*target_address);
    211     *target_address = NULL;
    212     *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
    213     *retry_counter = UINT_MAX;
    214     return GNUNET_OK;
    215   }
    216   GNUNET_free (instance_id);
    217 
    218   if (xop != op)
    219   {
    220     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    221                 "Challenge was for a different operation (%d!=%d)!\n",
    222                 (int) op,
    223                 (int) xop);
    224     *solved = false;
    225     return GNUNET_OK;
    226   }
    227   TALER_MERCHANT_mfa_body_hash (hc->request_body,
    228                                 &salt,
    229                                 &h_body);
    230   if (0 !=
    231       GNUNET_memcmp (&h_body,
    232                      &x_h_body))
    233   {
    234     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    235                 "Challenge was for a different request body!\n");
    236     *solved = false;
    237     return GNUNET_OK;
    238   }
    239   *solved = (! GNUNET_TIME_absolute_is_future (confirmation_date));
    240   return GNUNET_OK;
    241 }
    242 
    243 
    244 /**
    245  * Multi-factor authentication check to see if for the given @a instance_id
    246  * and the @a op operation all the TAN channels given in @a required_tans have
    247  * been satisfied.  Note that we always satisfy @a required_tans in the order
    248  * given in the array, so if the last one is satisfied, all previous ones must
    249  * have been satisfied before.
    250  *
    251  * If the challenges has not been satisfied, an appropriate response
    252  * is returned to the client of @a hc.
    253  *
    254  * @param[in,out] hc handler context of the connection to authorize
    255  * @param instance_name instance name to use in the message to the customer
    256  * @param op operation for which we are performing
    257  * @param channel TAN channel to try
    258  * @param expiration_date when should the challenge expire
    259  * @param required_address addresses to use for
    260  *        the respective challenge
    261  * @param[out] challenge_id set to the challenge ID, to be freed by
    262  *   the caller
    263  * @return #GNUNET_OK on success,
    264  *         #GNUNET_NO if an error message was returned to the client
    265  *         #GNUNET_SYSERR to just close the connection
    266  */
    267 static enum GNUNET_GenericReturnValue
    268 mfa_challenge_start (
    269   struct TMH_HandlerContext *hc,
    270   const char *instance_name,
    271   enum TALER_MERCHANT_MFA_CriticalOperation op,
    272   enum TALER_MERCHANT_MFA_Channel channel,
    273   struct GNUNET_TIME_Absolute expiration_date,
    274   const char *required_address,
    275   char **challenge_id)
    276 {
    277   enum GNUNET_DB_QueryStatus qs;
    278   struct TALER_MERCHANT_MFA_BodySalt salt;
    279   struct TALER_MERCHANT_MFA_BodyHash h_body;
    280   uint64_t challenge_serial;
    281 
    282   GNUNET_CRYPTO_random_block (&salt,
    283                               sizeof (salt));
    284   TALER_MERCHANT_mfa_body_hash (hc->request_body,
    285                                 &salt,
    286                                 &h_body);
    287   /* The challenge is stored without a code: the TAN is only generated
    288      when it is sent (taler-merchant-httpd_post-challenge-ID.c), and
    289      until then the challenge cannot be solved. */
    290   qs = TALER_MERCHANTDB_insert_mfa_challenge (TMH_db,
    291                                               instance_name,
    292                                               op,
    293                                               &h_body,
    294                                               &salt,
    295                                               expiration_date,
    296                                               GNUNET_TIME_UNIT_ZERO_ABS,
    297                                               channel,
    298                                               required_address,
    299                                               &challenge_serial);
    300   switch (qs)
    301   {
    302   case GNUNET_DB_STATUS_HARD_ERROR:
    303     GNUNET_break (0);
    304     return (MHD_NO ==
    305             TALER_MHD_reply_with_error (hc->connection,
    306                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    307                                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
    308                                         NULL))
    309       ? GNUNET_SYSERR
    310       : GNUNET_NO;
    311   case GNUNET_DB_STATUS_SOFT_ERROR:
    312     GNUNET_break (0);
    313     return (MHD_NO ==
    314             TALER_MHD_reply_with_error (hc->connection,
    315                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    316                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
    317                                         NULL))
    318       ? GNUNET_SYSERR
    319       : GNUNET_NO;
    320   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    321     GNUNET_assert (0);
    322     break;
    323   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    324     break;
    325   }
    326   {
    327     char *h_body_s;
    328 
    329     h_body_s = GNUNET_STRINGS_data_to_string_alloc (&h_body,
    330                                                     sizeof (h_body));
    331     GNUNET_asprintf (challenge_id,
    332                      "%llu-%s",
    333                      (unsigned long long) challenge_serial,
    334                      h_body_s);
    335     GNUNET_free (h_body_s);
    336   }
    337   return GNUNET_OK;
    338 }
    339 
    340 
    341 /**
    342  * Internal book-keeping for #TMH_mfa_challenges_do().
    343  */
    344 struct Challenge
    345 {
    346   /**
    347    * Channel on which the challenge is transmitted.
    348    */
    349   enum TALER_MERCHANT_MFA_Channel channel;
    350 
    351   /**
    352    * Address to send the challenge to.
    353    */
    354   const char *required_address;
    355 
    356   /**
    357    * Internal challenge ID.
    358    */
    359   char *challenge_id;
    360 
    361   /**
    362    * True if the challenge was solved.
    363    */
    364   bool solved;
    365 
    366   /**
    367    * True if the challenge could still be solved.
    368    */
    369   bool solvable;
    370 
    371 };
    372 
    373 
    374 /**
    375  * Obtain hint about the @a target_address of type @a channel to
    376  * return to the client.
    377  *
    378  * @param channel type of challenge
    379  * @param target_address address we will sent the challenge to
    380  * @return hint for the user about the address
    381  */
    382 static char *
    383 get_hint (enum TALER_MERCHANT_MFA_Channel channel,
    384           const char *target_address)
    385 {
    386   switch (channel)
    387   {
    388   case TALER_MERCHANT_MFA_CHANNEL_NONE:
    389     GNUNET_assert (0);
    390     return NULL;
    391   case TALER_MERCHANT_MFA_CHANNEL_SMS:
    392     {
    393       size_t slen = strlen (target_address);
    394       const char *end;
    395 
    396       if (slen > 4)
    397         end = &target_address[slen - 4];
    398       else
    399         end = &target_address[slen / 2];
    400       return GNUNET_strdup (end);
    401     }
    402   case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
    403     {
    404       const char *at;
    405       size_t len;
    406 
    407       at = strchr (target_address,
    408                    '@');
    409       if (NULL == at)
    410         len = 0;
    411       else
    412         len = at - target_address;
    413       return GNUNET_strndup (target_address,
    414                              len);
    415     }
    416   case TALER_MERCHANT_MFA_CHANNEL_TOTP:
    417     GNUNET_break (0);
    418     return GNUNET_strdup ("TOTP is not implemented: #10327");
    419   }
    420   GNUNET_break (0);
    421   return NULL;
    422 }
    423 
    424 
    425 /**
    426  * New challenges are about to be started for a password reset of
    427  * @a instance_name.  A password reset is unauthenticated, so without a
    428  * limit anyone could start one reset after the other and guess the TAN
    429  * sent for each.  Allow it only if the last password reset of the
    430  * instance was started at least #PASSWORD_RESET_DELAY ago, and record
    431  * this one.
    432  *
    433  * @param[in,out] hc handler context with the connection to the client
    434  * @param instance_name instance whose password is being reset
    435  * @return #GNUNET_OK if new challenges may be started,
    436  *         #GNUNET_NO if an error message was returned to the client
    437  *         #GNUNET_SYSERR to just close the connection
    438  */
    439 static enum GNUNET_GenericReturnValue
    440 password_reset_start (struct TMH_HandlerContext *hc,
    441                       const char *instance_name)
    442 {
    443   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
    444   enum GNUNET_DB_QueryStatus qs;
    445 
    446   qs = TALER_MERCHANTDB_update_instance_password_reset (
    447     TMH_db,
    448     instance_name,
    449     now,
    450     GNUNET_TIME_absolute_add (now,
    451                               PASSWORD_RESET_DELAY));
    452   switch (qs)
    453   {
    454   case GNUNET_DB_STATUS_HARD_ERROR:
    455     GNUNET_break (0);
    456     return (MHD_NO ==
    457             TALER_MHD_reply_with_error (hc->connection,
    458                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    459                                         TALER_EC_GENERIC_DB_STORE_FAILED,
    460                                         "update_instance_password_reset"))
    461       ? GNUNET_SYSERR
    462       : GNUNET_NO;
    463   case GNUNET_DB_STATUS_SOFT_ERROR:
    464     GNUNET_break (0);
    465     return (MHD_NO ==
    466             TALER_MHD_reply_with_error (hc->connection,
    467                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    468                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
    469                                         "update_instance_password_reset"))
    470       ? GNUNET_SYSERR
    471       : GNUNET_NO;
    472   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    473     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    474                 "Refusing password reset of instance `%s': the last one was started less than %s ago\n",
    475                 instance_name,
    476                 GNUNET_TIME_relative2s (PASSWORD_RESET_DELAY,
    477                                         false));
    478     return (MHD_NO ==
    479             TALER_MHD_reply_with_error (hc->connection,
    480                                         MHD_HTTP_TOO_MANY_REQUESTS,
    481                                         TALER_EC_MERCHANT_TAN_TOO_EARLY,
    482                                         GNUNET_TIME_relative2s (
    483                                           PASSWORD_RESET_DELAY,
    484                                           false)))
    485       ? GNUNET_SYSERR
    486       : GNUNET_NO;
    487   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    488     break;
    489   }
    490   return GNUNET_OK;
    491 }
    492 
    493 
    494 /**
    495  * Check that a set of MFA challenges has been satisfied by the
    496  * client for the request in @a hc.
    497  *
    498  * @param[in,out] hc handler context with the connection to the client
    499  * @param instance_name instance name to use in the message to the customer
    500  * @param op operation for which we should check challenges for
    501  * @param password_reset true if @a op is an unauthenticated password
    502  *       reset of @a instance_name, see #password_reset_start()
    503  * @param combi_and true to tell the client to solve all challenges (AND),
    504  *       false means that any of the challenges will do (OR)
    505  * @param ap pairs of channel and address, terminated by
    506  *        #TALER_MERCHANT_MFA_CHANNEL_NONE
    507  * @return #GNUNET_OK on success (challenges satisfied)
    508  *         #GNUNET_NO if an error message was returned to the client
    509  *         #GNUNET_SYSERR to just close the connection
    510  */
    511 static enum GNUNET_GenericReturnValue
    512 mfa_challenges_do (
    513   struct TMH_HandlerContext *hc,
    514   const char *instance_name,
    515   enum TALER_MERCHANT_MFA_CriticalOperation op,
    516   bool password_reset,
    517   bool combi_and,
    518   va_list ap)
    519 {
    520   struct Challenge challenges[MAX_CHALLENGES];
    521   const char *challenge_ids[MAX_CHALLENGES];
    522   size_t num_challenges;
    523   char *challenge_ids_copy = NULL;
    524   size_t num_provided_challenges;
    525   enum GNUNET_GenericReturnValue ret;
    526 
    527   {
    528     num_challenges = 0;
    529     while (num_challenges < MAX_CHALLENGES)
    530     {
    531       enum TALER_MERCHANT_MFA_Channel channel;
    532       const char *address;
    533 
    534       channel = va_arg (ap,
    535                         enum TALER_MERCHANT_MFA_Channel);
    536       if (TALER_MERCHANT_MFA_CHANNEL_NONE == channel)
    537         break;
    538       address = va_arg (ap,
    539                         const char *);
    540       if (NULL == address)
    541         continue;
    542       challenges[num_challenges].channel = channel;
    543       challenges[num_challenges].required_address = address;
    544       challenges[num_challenges].challenge_id = NULL;
    545       challenges[num_challenges].solved = false;
    546       challenges[num_challenges].solvable = true;
    547       num_challenges++;
    548     }
    549   }
    550 
    551   if (0 == num_challenges)
    552   {
    553     /* No challenges required. Strange... */
    554     return GNUNET_OK;
    555   }
    556 
    557   {
    558     const char *challenge_ids_header;
    559 
    560     challenge_ids_header
    561       = MHD_lookup_connection_value (hc->connection,
    562                                      MHD_HEADER_KIND,
    563                                      "Taler-Challenge-Ids");
    564     num_provided_challenges = 0;
    565     if (NULL != challenge_ids_header)
    566     {
    567       challenge_ids_copy = GNUNET_strdup (challenge_ids_header);
    568 
    569       for (char *token = strtok (challenge_ids_copy,
    570                                  ",");
    571            NULL != token;
    572            token = strtok (NULL,
    573                            ","))
    574       {
    575         if (num_provided_challenges >= MAX_CHALLENGES)
    576         {
    577           GNUNET_break_op (0);
    578           GNUNET_free (challenge_ids_copy);
    579           return (MHD_NO ==
    580                   TALER_MHD_reply_with_error (
    581                     hc->connection,
    582                     MHD_HTTP_BAD_REQUEST,
    583                     TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
    584                     "Taler-Challenge-Ids"))
    585           ? GNUNET_SYSERR
    586           : GNUNET_NO;
    587         }
    588         challenge_ids[num_provided_challenges] = token;
    589         num_provided_challenges++;
    590       }
    591     }
    592   }
    593 
    594   /* Check provided challenges against requirements */
    595   for (size_t i = 0; i < num_provided_challenges; i++)
    596   {
    597     bool solved;
    598     enum TALER_MERCHANT_MFA_Channel channel;
    599     char *target_address;
    600     uint32_t retry_counter;
    601 
    602     ret = mfa_challenge_check (hc,
    603                                instance_name,
    604                                op,
    605                                challenge_ids[i],
    606                                &solved,
    607                                &channel,
    608                                &target_address,
    609                                &retry_counter);
    610     if (GNUNET_OK != ret)
    611       goto cleanup;
    612     for (size_t j = 0; j < num_challenges; j++)
    613     {
    614       if ( (challenges[j].channel == channel) &&
    615            (NULL == challenges[j].challenge_id) &&
    616            (NULL != target_address /* just to be sure */) &&
    617            (0 == strcmp (target_address,
    618                          challenges[j].required_address) ) )
    619       {
    620         challenges[j].solved
    621           = solved;
    622         challenges[j].challenge_id
    623           = GNUNET_strdup (challenge_ids[i]);
    624         if ( (! solved) &&
    625              (0 == retry_counter) )
    626         {
    627           /* can't be solved anymore! */
    628           challenges[j].solvable = false;
    629         }
    630         break;
    631       }
    632     }
    633     GNUNET_free (target_address);
    634   }
    635 
    636   if (password_reset)
    637   {
    638     bool start_new = false;
    639 
    640     for (size_t i = 0; i < num_challenges; i++)
    641       if (NULL == challenges[i].challenge_id)
    642         start_new = true;
    643     if (start_new)
    644     {
    645       ret = password_reset_start (hc,
    646                                   instance_name);
    647       if (GNUNET_OK != ret)
    648         goto cleanup;
    649     }
    650   }
    651 
    652   {
    653     struct GNUNET_TIME_Absolute expiration_date
    654       = GNUNET_TIME_relative_to_absolute (CHALLENGE_LIFETIME);
    655 
    656     /* Start new challenges for unsolved requirements */
    657     for (size_t i = 0; i < num_challenges; i++)
    658     {
    659       if (NULL == challenges[i].challenge_id)
    660       {
    661         GNUNET_assert (! challenges[i].solved);
    662         GNUNET_assert (challenges[i].solvable);
    663         ret = mfa_challenge_start (hc,
    664                                    instance_name,
    665                                    op,
    666                                    challenges[i].channel,
    667                                    expiration_date,
    668                                    challenges[i].required_address,
    669                                    &challenges[i].challenge_id);
    670         if (GNUNET_OK != ret)
    671           goto cleanup;
    672       }
    673     }
    674   }
    675 
    676   {
    677     bool all_solved = true;
    678     bool any_solved = false;
    679     bool solvable = true;
    680 
    681     for (size_t i = 0; i < num_challenges; i++)
    682     {
    683       if (challenges[i].solved)
    684       {
    685         any_solved = true;
    686       }
    687       else
    688       {
    689         all_solved = false;
    690         if (combi_and &&
    691             (! challenges[i].solvable) )
    692           solvable = false;
    693       }
    694     }
    695 
    696     if ( (combi_and && all_solved) ||
    697          (! combi_and && any_solved) )
    698     {
    699       /* Authorization successful */
    700       ret = GNUNET_OK;
    701       goto cleanup;
    702     }
    703     if (! solvable)
    704     {
    705       ret = (MHD_NO ==
    706              TALER_MHD_reply_with_error (
    707                hc->connection,
    708                MHD_HTTP_FORBIDDEN,
    709                TALER_EC_MERCHANT_MFA_FORBIDDEN,
    710                GNUNET_TIME_relative2s (CHALLENGE_LIFETIME,
    711                                        false)))
    712         ? GNUNET_SYSERR
    713         : GNUNET_NO;
    714       goto cleanup;
    715     }
    716   }
    717 
    718   /* Return challenges to client */
    719   {
    720     json_t *jchallenges;
    721 
    722     jchallenges = json_array ();
    723     GNUNET_assert (NULL != jchallenges);
    724     for (size_t i = 0; i<num_challenges; i++)
    725     {
    726       const struct Challenge *c = &challenges[i];
    727       json_t *jc;
    728       char *hint;
    729 
    730       hint = get_hint (c->channel,
    731                        c->required_address);
    732 
    733       jc = GNUNET_JSON_PACK (
    734         GNUNET_JSON_pack_string ("tan_info",
    735                                  hint),
    736         GNUNET_JSON_pack_string ("tan_channel",
    737                                  TALER_MERCHANT_MFA_channel_to_string (
    738                                    c->channel)),
    739         GNUNET_JSON_pack_string ("challenge_id",
    740                                  c->challenge_id));
    741       GNUNET_free (hint);
    742       GNUNET_assert (0 ==
    743                      json_array_append_new (
    744                        jchallenges,
    745                        jc));
    746     }
    747     ret = (MHD_NO ==
    748            TALER_MHD_REPLY_JSON_PACK (
    749              hc->connection,
    750              MHD_HTTP_ACCEPTED,
    751              GNUNET_JSON_pack_bool ("combi_and",
    752                                     combi_and),
    753              GNUNET_JSON_pack_array_steal ("challenges",
    754                                            jchallenges)))
    755       ? GNUNET_SYSERR
    756       : GNUNET_NO;
    757   }
    758 
    759 cleanup:
    760   for (size_t i = 0; i < num_challenges; i++)
    761     GNUNET_free (challenges[i].challenge_id);
    762   GNUNET_free (challenge_ids_copy);
    763   return ret;
    764 }
    765 
    766 
    767 enum GNUNET_GenericReturnValue
    768 TMH_mfa_challenges_do (
    769   struct TMH_HandlerContext *hc,
    770   const char *instance_name,
    771   enum TALER_MERCHANT_MFA_CriticalOperation op,
    772   bool combi_and,
    773   ...)
    774 {
    775   va_list ap;
    776   enum GNUNET_GenericReturnValue ret;
    777 
    778   va_start (ap,
    779             combi_and);
    780   ret = mfa_challenges_do (hc,
    781                            instance_name,
    782                            op,
    783                            false,
    784                            combi_and,
    785                            ap);
    786   va_end (ap);
    787   return ret;
    788 }
    789 
    790 
    791 enum GNUNET_GenericReturnValue
    792 TMH_mfa_auth_challenges_do (
    793   struct TMH_HandlerContext *hc,
    794   const char *instance_name,
    795   bool password_reset,
    796   bool combi_and,
    797   ...)
    798 {
    799   va_list ap;
    800   enum GNUNET_GenericReturnValue ret;
    801 
    802   va_start (ap,
    803             combi_and);
    804   ret = mfa_challenges_do (hc,
    805                            instance_name,
    806                            TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
    807                            password_reset,
    808                            combi_and,
    809                            ap);
    810   va_end (ap);
    811   return ret;
    812 }
    813 
    814 
    815 enum GNUNET_GenericReturnValue
    816 TMH_mfa_check_simple (
    817   struct TMH_HandlerContext *hc,
    818   enum TALER_MERCHANT_MFA_CriticalOperation op,
    819   struct TMH_MerchantInstance *mi)
    820 {
    821   enum GNUNET_GenericReturnValue ret;
    822   bool have_sms = (NULL != mi->settings.phone) &&
    823                   (NULL != TMH_helper_sms) &&
    824                   (mi->settings.phone_validated);
    825   bool have_email = (NULL != mi->settings.email) &&
    826                     (NULL != TMH_helper_email) &&
    827                     (mi->settings.email_validated);
    828 
    829   /* Note: we check for 'validated' above, but in theory
    830      we could also use unvalidated for this operation.
    831      That's a policy-decision we may want to revise,
    832      but probably need to look at the global threat model to
    833      make sure alternative configurations are still sane. */
    834   if (have_email)
    835   {
    836     ret = TMH_mfa_challenges_do (hc,
    837                                  mi->settings.id,
    838                                  op,
    839                                  false,
    840                                  TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    841                                  mi->settings.email,
    842                                  have_sms
    843                                  ? TALER_MERCHANT_MFA_CHANNEL_SMS
    844                                  : TALER_MERCHANT_MFA_CHANNEL_NONE,
    845                                  mi->settings.phone,
    846                                  TALER_MERCHANT_MFA_CHANNEL_NONE);
    847   }
    848   else if (have_sms)
    849   {
    850     ret = TMH_mfa_challenges_do (hc,
    851                                  mi->settings.id,
    852                                  op,
    853                                  false,
    854                                  TALER_MERCHANT_MFA_CHANNEL_SMS,
    855                                  mi->settings.phone,
    856                                  TALER_MERCHANT_MFA_CHANNEL_NONE);
    857   }
    858   else
    859   {
    860     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    861                 "No MFA possible, skipping 2-FA\n");
    862     ret = GNUNET_OK;
    863   }
    864   return ret;
    865 }