LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_mfa.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 73.4 % 222 163
Test Date: 2026-09-11 17:45:24 Functions: 100.0 % 6 6

            Line data    Source code
       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              : 
      31              : 
      32              : /**
      33              :  * How many challenges do we allow at most per request?
      34              :  */
      35              : #define MAX_CHALLENGES 9
      36              : 
      37              : /**
      38              :  * How long are challenges valid?
      39              :  */
      40              : #define CHALLENGE_LIFETIME GNUNET_TIME_UNIT_DAYS
      41              : 
      42              : 
      43              : enum GNUNET_GenericReturnValue
      44           39 : TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
      45              :                             const char *challenge_id,
      46              :                             uint64_t *challenge_serial,
      47              :                             struct TALER_MERCHANT_MFA_BodyHash *h_body)
      48              : {
      49           39 :   const char *dash = strchr (challenge_id,
      50              :                              '-');
      51              :   unsigned long long ser;
      52              :   char min;
      53              : 
      54           39 :   if (NULL == dash)
      55              :   {
      56            0 :     GNUNET_break_op (0);
      57              :     return (MHD_NO ==
      58            0 :             TALER_MHD_reply_with_error (hc->connection,
      59              :                                         MHD_HTTP_BAD_REQUEST,
      60              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
      61              :                                         "'-' missing in challenge ID"))
      62              :       ? GNUNET_SYSERR
      63            0 :       : GNUNET_NO;
      64              :   }
      65           39 :   if ( (2 !=
      66           39 :         sscanf (challenge_id,
      67              :                 "%llu%c%*s",
      68              :                 &ser,
      69           39 :                 &min)) ||
      70           39 :        ('-' != min) )
      71              :   {
      72            0 :     GNUNET_break_op (0);
      73              :     return (MHD_NO ==
      74            0 :             TALER_MHD_reply_with_error (hc->connection,
      75              :                                         MHD_HTTP_BAD_REQUEST,
      76              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
      77              :                                         "Invalid number for challenge ID"))
      78              :       ? GNUNET_SYSERR
      79            0 :       : GNUNET_NO;
      80              :   }
      81           39 :   if (GNUNET_OK !=
      82           39 :       GNUNET_STRINGS_string_to_data (dash + 1,
      83              :                                      strlen (dash + 1),
      84              :                                      h_body,
      85              :                                      sizeof (*h_body)))
      86              :   {
      87            0 :     GNUNET_break_op (0);
      88              :     return (MHD_NO ==
      89            0 :             TALER_MHD_reply_with_error (hc->connection,
      90              :                                         MHD_HTTP_BAD_REQUEST,
      91              :                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
      92              :                                         "Malformed challenge ID"))
      93              :       ? GNUNET_SYSERR
      94            0 :       : GNUNET_NO;
      95              :   }
      96           39 :   *challenge_serial = (uint64_t) ser;
      97           39 :   return GNUNET_OK;
      98              : }
      99              : 
     100              : 
     101              : /**
     102              :  * Check if the given authentication check was already completed.
     103              :  *
     104              :  * @param[in,out] hc handler context of the connection to authorize
     105              :  * @param instance_name instance for which the challenge must have been issued
     106              :  * @param op operation for which we are requiring authorization
     107              :  * @param challenge_id ID of the challenge to check if it is done
     108              :  * @param[out] solved set to true if the challenge was solved,
     109              :  *             set to false if @a challenge_id was not found
     110              :  * @param[out] channel TAN channel that was used,
     111              :  *             set to #TALER_MERCHANT_MFA_CHANNEL_NONE if @a challenge_id
     112              :  *             was not found
     113              :  * @param[out] target_address address which was validated,
     114              :  *             set to NULL if @a challenge_id was not found
     115              :  * @param[out] retry_counter how many attempts are left on the challenge
     116              :  * @return #GNUNET_OK on success (challenge found)
     117              :  *         #GNUNET_NO if an error message was returned to the client
     118              :  *         #GNUNET_SYSERR to just close the connection
     119              :  */
     120              : static enum GNUNET_GenericReturnValue
     121           17 : mfa_challenge_check (
     122              :   struct TMH_HandlerContext *hc,
     123              :   const char *instance_name,
     124              :   enum TALER_MERCHANT_MFA_CriticalOperation op,
     125              :   const char *challenge_id,
     126              :   bool *solved,
     127              :   enum TALER_MERCHANT_MFA_Channel *channel,
     128              :   char **target_address,
     129              :   uint32_t *retry_counter)
     130              : {
     131              :   uint64_t challenge_serial;
     132              :   struct TALER_MERCHANT_MFA_BodyHash h_body;
     133              :   struct TALER_MERCHANT_MFA_BodyHash x_h_body;
     134              :   struct TALER_MERCHANT_MFA_BodySalt salt;
     135              :   struct GNUNET_TIME_Absolute retransmission_date;
     136              :   enum TALER_MERCHANT_MFA_CriticalOperation xop;
     137              :   enum GNUNET_DB_QueryStatus qs;
     138              :   struct GNUNET_TIME_Absolute confirmation_date;
     139              :   enum GNUNET_GenericReturnValue ret;
     140           17 :   char *instance_id = NULL;
     141              : 
     142           17 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     143              :               "Checking status of challenge %s\n",
     144              :               challenge_id);
     145           17 :   ret = TMH_mfa_parse_challenge_id (hc,
     146              :                                     challenge_id,
     147              :                                     &challenge_serial,
     148              :                                     &x_h_body);
     149           17 :   if (GNUNET_OK != ret)
     150            0 :     return ret;
     151           17 :   *target_address = NULL;
     152           17 :   *solved = false;
     153           17 :   *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
     154           17 :   *retry_counter = UINT_MAX;
     155           17 :   qs = TALER_MERCHANTDB_get_mfa_challenge (TMH_db,
     156              :                                            challenge_serial,
     157              :                                            &x_h_body,
     158              :                                            &salt,
     159              :                                            target_address,
     160              :                                            &xop,
     161              :                                            &confirmation_date,
     162              :                                            &retransmission_date,
     163              :                                            retry_counter,
     164              :                                            channel,
     165              :                                            &instance_id);
     166           17 :   switch (qs)
     167              :   {
     168            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     169            0 :     GNUNET_break (0);
     170              :     return (MHD_NO ==
     171            0 :             TALER_MHD_reply_with_error (hc->connection,
     172              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     173              :                                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
     174              :                                         NULL))
     175              :       ? GNUNET_SYSERR
     176            0 :       : GNUNET_NO;
     177            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     178            0 :     GNUNET_break (0);
     179              :     return (MHD_NO ==
     180            0 :             TALER_MHD_reply_with_error (hc->connection,
     181              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     182              :                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     183              :                                         NULL))
     184              :       ? GNUNET_SYSERR
     185            0 :       : GNUNET_NO;
     186            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     187            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     188              :                 "Challenge %s not found\n",
     189              :                 challenge_id);
     190            0 :     return GNUNET_OK;
     191           17 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     192           17 :     break;
     193              :   }
     194           17 :   if (0 != strcmp (instance_name,
     195              :                    instance_id))
     196              :   {
     197            1 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     198              :                 "Challenge was issued for a different instance (%s!=%s)!\n",
     199              :                 instance_id,
     200              :                 instance_name);
     201            1 :     GNUNET_free (instance_id);
     202            1 :     GNUNET_free (*target_address);
     203            1 :     *target_address = NULL;
     204            1 :     *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
     205            1 :     *retry_counter = UINT_MAX;
     206            1 :     return GNUNET_OK;
     207              :   }
     208           16 :   GNUNET_free (instance_id);
     209              : 
     210           16 :   if (xop != op)
     211              :   {
     212            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     213              :                 "Challenge was for a different operation (%d!=%d)!\n",
     214              :                 (int) op,
     215              :                 (int) xop);
     216            0 :     *solved = false;
     217            0 :     return GNUNET_OK;
     218              :   }
     219           16 :   TALER_MERCHANT_mfa_body_hash (hc->request_body,
     220              :                                 &salt,
     221              :                                 &h_body);
     222           16 :   if (0 !=
     223           16 :       GNUNET_memcmp (&h_body,
     224              :                      &x_h_body))
     225              :   {
     226            4 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     227              :                 "Challenge was for a different request body!\n");
     228            4 :     *solved = false;
     229            4 :     return GNUNET_OK;
     230              :   }
     231           12 :   *solved = (! GNUNET_TIME_absolute_is_future (confirmation_date));
     232           12 :   return GNUNET_OK;
     233              : }
     234              : 
     235              : 
     236              : /**
     237              :  * Multi-factor authentication check to see if for the given @a instance_id
     238              :  * and the @a op operation all the TAN channels given in @a required_tans have
     239              :  * been satisfied.  Note that we always satisfy @a required_tans in the order
     240              :  * given in the array, so if the last one is satisfied, all previous ones must
     241              :  * have been satisfied before.
     242              :  *
     243              :  * If the challenges has not been satisfied, an appropriate response
     244              :  * is returned to the client of @a hc.
     245              :  *
     246              :  * @param[in,out] hc handler context of the connection to authorize
     247              :  * @param instance_name instance name to use in the message to the customer
     248              :  * @param op operation for which we are performing
     249              :  * @param channel TAN channel to try
     250              :  * @param expiration_date when should the challenge expire
     251              :  * @param required_address addresses to use for
     252              :  *        the respective challenge
     253              :  * @param[out] challenge_id set to the challenge ID, to be freed by
     254              :  *   the caller
     255              :  * @return #GNUNET_OK on success,
     256              :  *         #GNUNET_NO if an error message was returned to the client
     257              :  *         #GNUNET_SYSERR to just close the connection
     258              :  */
     259              : static enum GNUNET_GenericReturnValue
     260           20 : mfa_challenge_start (
     261              :   struct TMH_HandlerContext *hc,
     262              :   const char *instance_name,
     263              :   enum TALER_MERCHANT_MFA_CriticalOperation op,
     264              :   enum TALER_MERCHANT_MFA_Channel channel,
     265              :   struct GNUNET_TIME_Absolute expiration_date,
     266              :   const char *required_address,
     267              :   char **challenge_id)
     268              : {
     269              :   enum GNUNET_DB_QueryStatus qs;
     270              :   struct TALER_MERCHANT_MFA_BodySalt salt;
     271              :   struct TALER_MERCHANT_MFA_BodyHash h_body;
     272              :   uint64_t challenge_serial;
     273              :   unsigned long long challenge_num;
     274              :   char *code;
     275              : 
     276           20 :   GNUNET_CRYPTO_random_block (&salt,
     277              :                               sizeof (salt));
     278           20 :   TALER_MERCHANT_mfa_body_hash (hc->request_body,
     279              :                                 &salt,
     280              :                                 &h_body);
     281              :   challenge_num = (unsigned long long)
     282           20 :                   GNUNET_CRYPTO_random_u64 (1000 * 1000 * 100);
     283              :   /* Note: if this is changed, the code in
     284              :      taler-merchant-httpd_post-challenge-ID.c and
     285              :      taler-merchant-httpd_post-challenge-ID-confirm.c must
     286              :      possibly also be updated! */
     287           20 :   GNUNET_asprintf (&code,
     288              :                    "%04llu-%04llu",
     289              :                    challenge_num / 10000,
     290              :                    challenge_num % 10000);
     291           20 :   qs = TALER_MERCHANTDB_insert_mfa_challenge (TMH_db,
     292              :                                               instance_name,
     293              :                                               op,
     294              :                                               &h_body,
     295              :                                               &salt,
     296              :                                               code,
     297              :                                               expiration_date,
     298           20 :                                               GNUNET_TIME_UNIT_ZERO_ABS,
     299              :                                               channel,
     300              :                                               required_address,
     301              :                                               &challenge_serial);
     302           20 :   GNUNET_free (code);
     303           20 :   switch (qs)
     304              :   {
     305            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     306            0 :     GNUNET_break (0);
     307              :     return (MHD_NO ==
     308            0 :             TALER_MHD_reply_with_error (hc->connection,
     309              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     310              :                                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
     311              :                                         NULL))
     312              :       ? GNUNET_SYSERR
     313            0 :       : GNUNET_NO;
     314            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     315            0 :     GNUNET_break (0);
     316              :     return (MHD_NO ==
     317            0 :             TALER_MHD_reply_with_error (hc->connection,
     318              :                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     319              :                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     320              :                                         NULL))
     321              :       ? GNUNET_SYSERR
     322            0 :       : GNUNET_NO;
     323            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     324            0 :     GNUNET_assert (0);
     325              :     break;
     326           20 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     327           20 :     break;
     328              :   }
     329              :   {
     330              :     char *h_body_s;
     331              : 
     332           20 :     h_body_s = GNUNET_STRINGS_data_to_string_alloc (&h_body,
     333              :                                                     sizeof (h_body));
     334           20 :     GNUNET_asprintf (challenge_id,
     335              :                      "%llu-%s",
     336              :                      (unsigned long long) challenge_serial,
     337              :                      h_body_s);
     338           20 :     GNUNET_free (h_body_s);
     339              :   }
     340           20 :   return GNUNET_OK;
     341              : }
     342              : 
     343              : 
     344              : /**
     345              :  * Internal book-keeping for #TMH_mfa_challenges_do().
     346              :  */
     347              : struct Challenge
     348              : {
     349              :   /**
     350              :    * Channel on which the challenge is transmitted.
     351              :    */
     352              :   enum TALER_MERCHANT_MFA_Channel channel;
     353              : 
     354              :   /**
     355              :    * Address to send the challenge to.
     356              :    */
     357              :   const char *required_address;
     358              : 
     359              :   /**
     360              :    * Internal challenge ID.
     361              :    */
     362              :   char *challenge_id;
     363              : 
     364              :   /**
     365              :    * True if the challenge was solved.
     366              :    */
     367              :   bool solved;
     368              : 
     369              :   /**
     370              :    * True if the challenge could still be solved.
     371              :    */
     372              :   bool solvable;
     373              : 
     374              : };
     375              : 
     376              : 
     377              : /**
     378              :  * Obtain hint about the @a target_address of type @a channel to
     379              :  * return to the client.
     380              :  *
     381              :  * @param channel type of challenge
     382              :  * @param target_address address we will sent the challenge to
     383              :  * @return hint for the user about the address
     384              :  */
     385              : static char *
     386           22 : get_hint (enum TALER_MERCHANT_MFA_Channel channel,
     387              :           const char *target_address)
     388              : {
     389           22 :   switch (channel)
     390              :   {
     391            0 :   case TALER_MERCHANT_MFA_CHANNEL_NONE:
     392            0 :     GNUNET_assert (0);
     393              :     return NULL;
     394           11 :   case TALER_MERCHANT_MFA_CHANNEL_SMS:
     395              :     {
     396           11 :       size_t slen = strlen (target_address);
     397              :       const char *end;
     398              : 
     399           11 :       if (slen > 4)
     400           11 :         end = &target_address[slen - 4];
     401              :       else
     402            0 :         end = &target_address[slen / 2];
     403           11 :       return GNUNET_strdup (end);
     404              :     }
     405           11 :   case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
     406              :     {
     407              :       const char *at;
     408              :       size_t len;
     409              : 
     410           11 :       at = strchr (target_address,
     411              :                    '@');
     412           11 :       if (NULL == at)
     413            0 :         len = 0;
     414              :       else
     415           11 :         len = at - target_address;
     416           11 :       return GNUNET_strndup (target_address,
     417              :                              len);
     418              :     }
     419            0 :   case TALER_MERCHANT_MFA_CHANNEL_TOTP:
     420            0 :     GNUNET_break (0);
     421            0 :     return GNUNET_strdup ("TOTP is not implemented: #10327");
     422              :   }
     423            0 :   GNUNET_break (0);
     424            0 :   return NULL;
     425              : }
     426              : 
     427              : 
     428              : /**
     429              :  * Check that a set of MFA challenges has been satisfied by the
     430              :  * client for the request in @a hc.
     431              :  *
     432              :  * @param[in,out] hc handler context with the connection to the client
     433              :  * @param instance_name instance name to use in the message to the customer
     434              :  * @param op operation for which we should check challenges for
     435              :  * @param combi_and true to tell the client to solve all challenges (AND),
     436              :  *       false means that any of the challenges will do (OR)
     437              :  * @param ... pairs of channel and address, terminated by
     438              :  *        #TALER_MERCHANT_MFA_CHANNEL_NONE
     439              :  * @return #GNUNET_OK on success (challenges satisfied)
     440              :  *         #GNUNET_NO if an error message was returned to the client
     441              :  *         #GNUNET_SYSERR to just close the connection
     442              :  */
     443              : enum GNUNET_GenericReturnValue
     444           18 : TMH_mfa_challenges_do (
     445              :   struct TMH_HandlerContext *hc,
     446              :   const char *instance_name,
     447              :   enum TALER_MERCHANT_MFA_CriticalOperation op,
     448              :   bool combi_and,
     449              :   ...)
     450              : {
     451              :   struct Challenge challenges[MAX_CHALLENGES];
     452              :   const char *challenge_ids[MAX_CHALLENGES];
     453              :   size_t num_challenges;
     454           18 :   char *challenge_ids_copy = NULL;
     455              :   size_t num_provided_challenges;
     456              :   enum GNUNET_GenericReturnValue ret;
     457              : 
     458              :   {
     459              :     va_list ap;
     460              : 
     461           18 :     va_start (ap,
     462              :               combi_and);
     463           18 :     num_challenges = 0;
     464           54 :     while (num_challenges < MAX_CHALLENGES)
     465              :     {
     466              :       enum TALER_MERCHANT_MFA_Channel channel;
     467              :       const char *address;
     468              : 
     469           54 :       channel = va_arg (ap,
     470              :                         enum TALER_MERCHANT_MFA_Channel);
     471           54 :       if (TALER_MERCHANT_MFA_CHANNEL_NONE == channel)
     472           18 :         break;
     473           36 :       address = va_arg (ap,
     474              :                         const char *);
     475           36 :       if (NULL == address)
     476            0 :         continue;
     477           36 :       challenges[num_challenges].channel = channel;
     478           36 :       challenges[num_challenges].required_address = address;
     479           36 :       challenges[num_challenges].challenge_id = NULL;
     480           36 :       challenges[num_challenges].solved = false;
     481           36 :       challenges[num_challenges].solvable = true;
     482           36 :       num_challenges++;
     483              :     }
     484           18 :     va_end (ap);
     485              :   }
     486              : 
     487           18 :   if (0 == num_challenges)
     488              :   {
     489              :     /* No challenges required. Strange... */
     490            0 :     return GNUNET_OK;
     491              :   }
     492              : 
     493              :   {
     494              :     const char *challenge_ids_header;
     495              : 
     496              :     challenge_ids_header
     497           18 :       = MHD_lookup_connection_value (hc->connection,
     498              :                                      MHD_HEADER_KIND,
     499              :                                      "Taler-Challenge-Ids");
     500           18 :     num_provided_challenges = 0;
     501           18 :     if (NULL != challenge_ids_header)
     502              :     {
     503           11 :       challenge_ids_copy = GNUNET_strdup (challenge_ids_header);
     504              : 
     505           11 :       for (char *token = strtok (challenge_ids_copy,
     506              :                                  ",");
     507           28 :            NULL != token;
     508           17 :            token = strtok (NULL,
     509              :                            ","))
     510              :       {
     511           17 :         if (num_provided_challenges >= MAX_CHALLENGES)
     512              :         {
     513            0 :           GNUNET_break_op (0);
     514            0 :           GNUNET_free (challenge_ids_copy);
     515              :           return (MHD_NO ==
     516            0 :                   TALER_MHD_reply_with_error (
     517              :                     hc->connection,
     518              :                     MHD_HTTP_BAD_REQUEST,
     519              :                     TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
     520              :                     "Taler-Challenge-Ids"))
     521              :           ? GNUNET_SYSERR
     522            0 :           : GNUNET_NO;
     523              :         }
     524           17 :         challenge_ids[num_provided_challenges] = token;
     525           17 :         num_provided_challenges++;
     526              :       }
     527              :     }
     528              :   }
     529              : 
     530              :   /* Check provided challenges against requirements */
     531           35 :   for (size_t i = 0; i < num_provided_challenges; i++)
     532              :   {
     533              :     bool solved;
     534              :     enum TALER_MERCHANT_MFA_Channel channel;
     535              :     char *target_address;
     536              :     uint32_t retry_counter;
     537              : 
     538           17 :     ret = mfa_challenge_check (hc,
     539              :                                instance_name,
     540              :                                op,
     541              :                                challenge_ids[i],
     542              :                                &solved,
     543              :                                &channel,
     544              :                                &target_address,
     545              :                                &retry_counter);
     546           17 :     if (GNUNET_OK != ret)
     547            0 :       goto cleanup;
     548           25 :     for (size_t j = 0; j < num_challenges; j++)
     549              :     {
     550           24 :       if ( (challenges[j].channel == channel) &&
     551           16 :            (NULL == challenges[j].challenge_id) &&
     552           16 :            (NULL != target_address /* just to be sure */) &&
     553           16 :            (0 == strcmp (target_address,
     554              :                          challenges[j].required_address) ) )
     555              :       {
     556              :         challenges[j].solved
     557           16 :           = solved;
     558              :         challenges[j].challenge_id
     559           16 :           = GNUNET_strdup (challenge_ids[i]);
     560           16 :         if ( (! solved) &&
     561            4 :              (0 == retry_counter) )
     562              :         {
     563              :           /* can't be solved anymore! */
     564            0 :           challenges[j].solvable = false;
     565              :         }
     566           16 :         break;
     567              :       }
     568              :     }
     569           17 :     GNUNET_free (target_address);
     570              :   }
     571              : 
     572              :   {
     573              :     struct GNUNET_TIME_Absolute expiration_date
     574           18 :       = GNUNET_TIME_relative_to_absolute (CHALLENGE_LIFETIME);
     575              : 
     576              :     /* Start new challenges for unsolved requirements */
     577           54 :     for (size_t i = 0; i < num_challenges; i++)
     578              :     {
     579           36 :       if (NULL == challenges[i].challenge_id)
     580              :       {
     581           20 :         GNUNET_assert (! challenges[i].solved);
     582           20 :         GNUNET_assert (challenges[i].solvable);
     583           20 :         ret = mfa_challenge_start (hc,
     584              :                                    instance_name,
     585              :                                    op,
     586              :                                    challenges[i].channel,
     587              :                                    expiration_date,
     588              :                                    challenges[i].required_address,
     589              :                                    &challenges[i].challenge_id);
     590           20 :         if (GNUNET_OK != ret)
     591            0 :           goto cleanup;
     592              :       }
     593              :     }
     594              :   }
     595              : 
     596              :   {
     597           18 :     bool all_solved = true;
     598           18 :     bool any_solved = false;
     599           18 :     bool solvable = true;
     600              : 
     601           54 :     for (size_t i = 0; i < num_challenges; i++)
     602              :     {
     603           36 :       if (challenges[i].solved)
     604              :       {
     605           12 :         any_solved = true;
     606              :       }
     607              :       else
     608              :       {
     609           24 :         all_solved = false;
     610           24 :         if (combi_and &&
     611           13 :             (! challenges[i].solvable) )
     612            0 :           solvable = false;
     613              :       }
     614              :     }
     615              : 
     616           18 :     if ( (combi_and && all_solved) ||
     617           14 :          (! combi_and && any_solved) )
     618              :     {
     619              :       /* Authorization successful */
     620            7 :       ret = GNUNET_OK;
     621            7 :       goto cleanup;
     622              :     }
     623           11 :     if (! solvable)
     624              :     {
     625            0 :       ret = (MHD_NO ==
     626            0 :              TALER_MHD_reply_with_error (
     627              :                hc->connection,
     628              :                MHD_HTTP_FORBIDDEN,
     629              :                TALER_EC_MERCHANT_MFA_FORBIDDEN,
     630              :                GNUNET_TIME_relative2s (CHALLENGE_LIFETIME,
     631              :                                        false)))
     632              :         ? GNUNET_SYSERR
     633            0 :         : GNUNET_NO;
     634            0 :       goto cleanup;
     635              :     }
     636              :   }
     637              : 
     638              :   /* Return challenges to client */
     639              :   {
     640              :     json_t *jchallenges;
     641              : 
     642           11 :     jchallenges = json_array ();
     643           11 :     GNUNET_assert (NULL != jchallenges);
     644           33 :     for (size_t i = 0; i<num_challenges; i++)
     645              :     {
     646           22 :       const struct Challenge *c = &challenges[i];
     647              :       json_t *jc;
     648              :       char *hint;
     649              : 
     650           22 :       hint = get_hint (c->channel,
     651           22 :                        c->required_address);
     652              : 
     653           22 :       jc = GNUNET_JSON_PACK (
     654              :         GNUNET_JSON_pack_string ("tan_info",
     655              :                                  hint),
     656              :         GNUNET_JSON_pack_string ("tan_channel",
     657              :                                  TALER_MERCHANT_MFA_channel_to_string (
     658              :                                    c->channel)),
     659              :         GNUNET_JSON_pack_string ("challenge_id",
     660              :                                  c->challenge_id));
     661           22 :       GNUNET_free (hint);
     662           22 :       GNUNET_assert (0 ==
     663              :                      json_array_append_new (
     664              :                        jchallenges,
     665              :                        jc));
     666              :     }
     667           11 :     ret = (MHD_NO ==
     668           11 :            TALER_MHD_REPLY_JSON_PACK (
     669              :              hc->connection,
     670              :              MHD_HTTP_ACCEPTED,
     671              :              GNUNET_JSON_pack_bool ("combi_and",
     672              :                                     combi_and),
     673              :              GNUNET_JSON_pack_array_steal ("challenges",
     674              :                                            jchallenges)))
     675              :       ? GNUNET_SYSERR
     676           11 :       : GNUNET_NO;
     677              :   }
     678              : 
     679           18 : cleanup:
     680           54 :   for (size_t i = 0; i < num_challenges; i++)
     681           36 :     GNUNET_free (challenges[i].challenge_id);
     682           18 :   GNUNET_free (challenge_ids_copy);
     683           18 :   return ret;
     684              : }
     685              : 
     686              : 
     687              : enum GNUNET_GenericReturnValue
     688           32 : TMH_mfa_check_simple (
     689              :   struct TMH_HandlerContext *hc,
     690              :   enum TALER_MERCHANT_MFA_CriticalOperation op,
     691              :   struct TMH_MerchantInstance *mi)
     692              : {
     693              :   enum GNUNET_GenericReturnValue ret;
     694           69 :   bool have_sms = (NULL != mi->settings.phone) &&
     695           37 :                   (NULL != TMH_helper_sms) &&
     696            5 :                   (mi->settings.phone_validated);
     697           69 :   bool have_email = (NULL != mi->settings.email) &&
     698           37 :                     (NULL != TMH_helper_email) &&
     699            5 :                     (mi->settings.email_validated);
     700              : 
     701              :   /* Note: we check for 'validated' above, but in theory
     702              :      we could also use unvalidated for this operation.
     703              :      That's a policy-decision we may want to revise,
     704              :      but probably need to look at the global threat model to
     705              :      make sure alternative configurations are still sane. */
     706           32 :   if (have_email)
     707              :   {
     708            5 :     ret = TMH_mfa_challenges_do (hc,
     709            5 :                                  mi->settings.id,
     710              :                                  op,
     711              :                                  false,
     712              :                                  TALER_MERCHANT_MFA_CHANNEL_EMAIL,
     713              :                                  mi->settings.email,
     714              :                                  have_sms
     715              :                                  ? TALER_MERCHANT_MFA_CHANNEL_SMS
     716              :                                  : TALER_MERCHANT_MFA_CHANNEL_NONE,
     717              :                                  mi->settings.phone,
     718              :                                  TALER_MERCHANT_MFA_CHANNEL_NONE);
     719              :   }
     720           27 :   else if (have_sms)
     721              :   {
     722            0 :     ret = TMH_mfa_challenges_do (hc,
     723            0 :                                  mi->settings.id,
     724              :                                  op,
     725              :                                  false,
     726              :                                  TALER_MERCHANT_MFA_CHANNEL_SMS,
     727              :                                  mi->settings.phone,
     728              :                                  TALER_MERCHANT_MFA_CHANNEL_NONE);
     729              :   }
     730              :   else
     731              :   {
     732           27 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     733              :                 "No MFA possible, skipping 2-FA\n");
     734           27 :     ret = GNUNET_OK;
     735              :   }
     736           32 :   return ret;
     737              : }
        

Generated by: LCOV version 2.0-1