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