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 : }
|