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