1: %%==============================================================================
    2: %% Copyright 2010 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: 
   17: -module(login_SUITE).
   18: -compile([export_all, nowarn_export_all]).
   19: 
   20: -include_lib("exml/include/exml.hrl").
   21: -include_lib("stdlib/include/assert.hrl").
   22: 
   23: -import(distributed_helper, [mim/0,
   24:                              require_rpc_nodes/1,
   25:                              rpc/4]).
   26: 
   27: -import(domain_helper, [host_type/0, domain/0]).
   28: 
   29: %%--------------------------------------------------------------------
   30: %% Suite configuration
   31: %%--------------------------------------------------------------------
   32: 
   33: all() ->
   34:     [
   35:      {group, login},
   36:      {group, login_digest},
   37:      {group, login_scram},
   38:      {group, login_scram_store_plain},
   39:      {group, login_specific_scram},
   40:      {group, login_scram_tls},
   41:      {group, messages},
   42:      {group, access}
   43:     ].
   44: 
   45: groups() ->
   46:     [{login, [parallel], all_tests()},
   47:      {login_digest, [sequence], digest_tests()},
   48:      {login_scram, [parallel], scram_tests()},
   49:      {login_scram_store_plain, [parallel], scram_tests()},
   50:      {login_scram_tls, [parallel], scram_tests()},
   51:      {login_specific_scram, [sequence], configure_specific_scram_test()},
   52:      {messages, [sequence], [messages_story]},
   53:      {access, [], access_tests()}].
   54: 
   55: scram_tests() ->
   56:     [scram_failed_with_non_authorized,
   57:      log_one,
   58:      log_one_scram_sha1,
   59:      log_one_scram_sha224,
   60:      log_one_scram_sha256,
   61:      log_one_scram_sha384,
   62:      log_one_scram_sha512,
   63:      log_one_scram_sha1_plus,
   64:      log_one_scram_sha224_plus,
   65:      log_one_scram_sha256_plus,
   66:      log_one_scram_sha384_plus,
   67:      log_one_scram_sha512_plus].
   68: 
   69: configure_specific_scram_test() ->
   70:     [configure_sha1_log_with_sha1,
   71:      configure_sha224_log_with_sha224,
   72:      configure_sha256_log_with_sha256,
   73:      configure_sha384_log_with_sha384,
   74:      configure_sha512_log_with_sha512,
   75:      configure_sha1_log_with_sha1_plus,
   76:      configure_sha224_log_with_sha224_plus,
   77:      configure_sha256_log_with_sha256_plus,
   78:      configure_sha384_log_with_sha384_plus,
   79:      configure_sha512_log_with_sha512_plus,
   80:      configure_sha1_fail_log_with_sha224,
   81:      configure_sha224_fail_log_with_sha256,
   82:      configure_sha256_fail_log_with_sha384,
   83:      configure_sha384_fail_log_with_sha512,
   84:      configure_sha512_fail_log_with_sha1,
   85:      configure_sha1_plus_fail_log_with_sha1,
   86:      configure_sha224_plus_fail_log_with_sha224,
   87:      configure_sha256_plus_fail_log_with_sha256,
   88:      configure_sha384_plus_fail_log_with_sha384,
   89:      configure_sha512_plus_fail_log_with_sha512].
   90: 
   91: all_tests() ->
   92:     [log_one,
   93:      log_non_existent_plain,
   94:      log_one_scram_sha1,
   95:      log_non_existent_scram,
   96:      log_bad_user_fails
   97:     ].
   98: 
   99: access_tests() ->
  100:     [blocked_user,
  101:      access_none_blocks_all_users,
  102:      access_none_for_other_listener_has_no_effect].
  103: 
  104: digest_tests() ->
  105:     [log_one_digest,
  106:      log_non_existent_digest].
  107: 
  108: suite() ->
  109:     require_rpc_nodes([mim]) ++ escalus:suite().
  110: 
  111: %%--------------------------------------------------------------------
  112: %% Init & teardown
  113: %%--------------------------------------------------------------------
  114: 
  115: init_per_suite(Config) ->
  116:     escalus:init_per_suite(Config).
  117: 
  118: end_per_suite(Config) ->
  119:     escalus_fresh:clean(),
  120:     escalus:end_per_suite(Config).
  121: 
  122: init_per_group(login_digest = GroupName, ConfigIn) ->
  123:     Config = backup_and_set_options(GroupName, ConfigIn),
  124:     case mongoose_helper:supports_sasl_module(cyrsasl_digest) of
  125:         false ->
  126:             mongoose_helper:restore_config(Config),
  127:             {skip, "digest password type not supported"};
  128:         true ->
  129:             escalus:create_users(Config, escalus:get_users([alice, bob]))
  130:     end;
  131: init_per_group(GroupName, ConfigIn)
  132:   when GroupName == login_scram;
  133:        GroupName == login_scram_store_plain ->
  134:     Config = backup_and_set_options(GroupName, ConfigIn),
  135:     case are_sasl_scram_modules_supported() of
  136:         false ->
  137:             mongoose_helper:restore_config(Config),
  138:             {skip, "scram password type not supported"};
  139:         true ->
  140:             Config2 = escalus:create_users(Config, escalus:get_users([alice, bob, neustradamus])),
  141:             assert_password_format(GroupName, Config2)
  142:     end;
  143: init_per_group(login_scram_tls = GroupName, ConfigIn) ->
  144:     Config = backup_and_set_options(GroupName, ConfigIn),
  145:     case are_sasl_scram_modules_supported() of
  146:         false ->
  147:             mongoose_helper:restore_config(Config),
  148:             {skip, "scram password type not supported"};
  149:         true ->
  150:             Config1 = configure_c2s_listener(Config),
  151:             Config2 = create_tls_users(Config1),
  152:             assert_password_format(scram, Config2)
  153:     end;
  154: init_per_group(login_specific_scram = GroupName, ConfigIn) ->
  155:     Config = backup_and_set_options(GroupName, ConfigIn),
  156:     case are_sasl_scram_modules_supported() of
  157:         false ->
  158:             mongoose_helper:restore_config(Config),
  159:             {skip, "scram password type not supported"};
  160:         true ->
  161:             escalus:create_users(Config, escalus:get_users([alice, bob, neustradamus]))
  162:     end;
  163: init_per_group(GroupName, ConfigIn) ->
  164:     Config = backup_and_set_options(GroupName, ConfigIn),
  165:     escalus:create_users(Config, escalus:get_users([alice, bob])).
  166: 
  167: backup_and_set_options(GroupName, Config) ->
  168:     mongoose_helper:backup_and_set_config_option(Config, {auth, host_type()}, auth_opts(GroupName)).
  169: 
  170: auth_opts(login_digest) ->
  171:     AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
  172:     AuthOpts#{sasl_mechanisms => [cyrsasl_digest]};
  173: auth_opts(login_scram_store_plain) ->
  174:     mongoose_helper:auth_opts_with_password_format(plain);
  175: auth_opts(_GroupName) ->
  176:     mongoose_helper:auth_opts_with_password_format(scram).
  177: 
  178: end_per_group(login_digest, Config) ->
  179:     mongoose_helper:restore_config(Config),
  180:     escalus:delete_users(Config, escalus:get_users([alice, bob]));
  181: end_per_group(GroupName, Config) when
  182:     GroupName == login_scram; GroupName == login_specific_scram ->
  183:     mongoose_helper:restore_config(Config),
  184:     escalus:delete_users(Config, escalus:get_users([alice, bob, neustradamus]));
  185: end_per_group(login_scram_tls, Config) ->
  186:     mongoose_helper:restore_config(Config),
  187:     restore_c2s(Config),
  188:     delete_tls_users(Config);
  189: end_per_group(_GroupName, Config) ->
  190:     mongoose_helper:restore_config(Config),
  191:     escalus:delete_users(Config, escalus:get_users([alice, bob])).
  192: 
  193: init_per_testcase(CaseName, Config) when
  194:       CaseName =:= log_one_scram_sha1; CaseName =:= log_non_existent_scram ->
  195:     case mongoose_helper:supports_sasl_module(cyrsasl_scram_sha1) of
  196:         false ->
  197:             {skip, "scram password type not supported"};
  198:         true ->
  199:             escalus:init_per_testcase(CaseName, Config)
  200:     end;
  201: init_per_testcase(blocked_user = CaseName, Config) ->
  202:     [{_, Spec}] = escalus_users:get_users([alice]),
  203:     Config1 = set_acl_for_blocking(Config, Spec),
  204:     escalus:init_per_testcase(CaseName, Config1);
  205: init_per_testcase(access_none_blocks_all_users = CaseName, Config) ->
  206:     Config1 = set_access_none(ct:get_config({hosts, mim, c2s_port}), Config),
  207:     escalus:init_per_testcase(CaseName, Config1);
  208: init_per_testcase(access_none_for_other_listener_has_no_effect = CaseName, Config) ->
  209:     Config1 = set_access_none(ct:get_config({hosts, mim, c2s_tls_port}), Config),
  210:     escalus:init_per_testcase(CaseName, Config1);
  211: init_per_testcase(CaseName, Config) ->
  212:     escalus:init_per_testcase(CaseName, Config).
  213: 
  214: end_per_testcase(blocked_user = CaseName, Config) ->
  215:     unset_acl_for_blocking(Config),
  216:     escalus:end_per_testcase(CaseName, Config);
  217: end_per_testcase(access_none_blocks_all_users = CaseName, Config) ->
  218:     restore_c2s(Config),
  219:     escalus:end_per_testcase(CaseName, Config);
  220: end_per_testcase(access_none_for_other_listener_has_no_effect = CaseName, Config) ->
  221:     restore_c2s(Config),
  222:     escalus:end_per_testcase(CaseName, Config);
  223: end_per_testcase(CaseName, Config) ->
  224:     escalus:end_per_testcase(CaseName, Config).
  225: 
  226: set_access_none(C2SPort, Config) ->
  227:     [C2SListener] =
  228:         mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}),
  229:     mongoose_helper:restart_listener(mim(), C2SListener#{access := none}),
  230:     [{c2s_listener, C2SListener} | Config].
  231: 
  232: %%--------------------------------------------------------------------
  233: %% Message tests
  234: %%--------------------------------------------------------------------
  235: 
  236: scram_failed_with_non_authorized(Config) ->
  237:     ConnectionSteps = [start_stream, stream_features],
  238:     UserSpec = escalus_fresh:create_fresh_user(Config, alice),
  239:     {ok, Alice, _Features} = escalus_connection:start(UserSpec, ConnectionSteps),
  240:     Username = escalus_utils:get_username(Alice),
  241:     BadPayload = <<"n,,n=", Username/binary, ",r=9ZdW+o71OwOrDUx4J5+M+A==">>,
  242:     AuthStanza = auth_stanza(<<"SCRAM-SHA-1">>, BadPayload),
  243:     escalus_client:send(Alice, AuthStanza),
  244:     _Challenge = escalus_client:wait_for_stanza(Alice),
  245:     WrongProof = <<"c=biws,r=invalid_nonce,p=wrong_proof">>,
  246:     Response = auth_response(WrongProof),
  247:     escalus_client:send(Alice, Response),
  248:     Failure = escalus_client:wait_for_stanza(Alice),
  249:     ?assertMatch(#xmlel{name = <<"failure">>}, Failure),
  250:     ?assertMatch(#xmlel{}, exml_query:subelement(Failure, <<"not-authorized">>)).
  251: 
  252: log_one(Config) ->
  253:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  254: 
  255:         escalus_client:send(Alice, escalus_stanza:chat_to(Alice, <<"Hi!">>)),
  256:         escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Alice))
  257: 
  258:         end).
  259: 
  260: log_one_scram_plus(Config) ->
  261:     escalus:fresh_story(Config, [{neustradamus, 1}], fun(Neustradamus) ->
  262: 
  263:         escalus_client:send(Neustradamus, escalus_stanza:chat_to(Neustradamus, <<"Hi!">>)),
  264:         escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Neustradamus))
  265: 
  266:         end).
  267: 
  268: log_one_digest(Config) ->
  269:     log_one([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]).
  270: 
  271: log_one_scram_sha1(Config) ->
  272:     log_one([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]).
  273: 
  274: log_one_scram_sha224(Config) ->
  275:     log_one([{escalus_auth_method, <<"SCRAM-SHA-224">>} | Config]).
  276: 
  277: log_one_scram_sha256(Config) ->
  278:     log_one([{escalus_auth_method, <<"SCRAM-SHA-256">>} | Config]).
  279: 
  280:  log_one_scram_sha384(Config) ->
  281:     log_one([{escalus_auth_method, <<"SCRAM-SHA-384">>} | Config]).
  282: 
  283: log_one_scram_sha512(Config) ->
  284:     log_one([{escalus_auth_method, <<"SCRAM-SHA-512">>} | Config]).
  285: 
  286: log_one_scram_sha1_plus(Config) ->
  287:     log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-1-PLUS">>} | Config]).
  288: 
  289: log_one_scram_sha224_plus(Config) ->
  290:     log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-224-PLUS">>} | Config]).
  291: 
  292: log_one_scram_sha256_plus(Config) ->
  293:     log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-256-PLUS">>} | Config]).
  294: 
  295: log_one_scram_sha384_plus(Config) ->
  296:     log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-384-PLUS">>} | Config]).
  297: 
  298: log_one_scram_sha512_plus(Config) ->
  299:     log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-512-PLUS">>} | Config]).
  300: 
  301: configure_sha1_log_with_sha1(Config) ->
  302:     configure_and_log_scram(Config, sha, <<"SCRAM-SHA-1">>).
  303: 
  304: configure_sha224_log_with_sha224(Config) ->
  305:     configure_and_log_scram(Config, sha224, <<"SCRAM-SHA-224">>).
  306: 
  307: configure_sha256_log_with_sha256(Config) ->
  308:     configure_and_log_scram(Config, sha256, <<"SCRAM-SHA-256">>).
  309: 
  310: configure_sha384_log_with_sha384(Config) ->
  311:     configure_and_log_scram(Config, sha384, <<"SCRAM-SHA-384">>).
  312: 
  313: configure_sha512_log_with_sha512(Config) ->
  314:     configure_and_log_scram(Config, sha512, <<"SCRAM-SHA-512">>).
  315: 
  316: configure_sha1_log_with_sha1_plus(Config) ->
  317:     configure_and_log_scram_plus(Config, sha, <<"SCRAM-SHA-1-PLUS">>).
  318: 
  319: configure_sha224_log_with_sha224_plus(Config) ->
  320:     configure_and_log_scram_plus(Config, sha224, <<"SCRAM-SHA-224-PLUS">>).
  321: 
  322: configure_sha256_log_with_sha256_plus(Config) ->
  323:     configure_and_log_scram_plus(Config, sha256, <<"SCRAM-SHA-256-PLUS">>).
  324: 
  325: configure_sha384_log_with_sha384_plus(Config) ->
  326:     configure_and_log_scram_plus(Config, sha384, <<"SCRAM-SHA-384-PLUS">>).
  327: 
  328: configure_sha512_log_with_sha512_plus(Config) ->
  329:     configure_and_log_scram_plus(Config, sha512, <<"SCRAM-SHA-512-PLUS">>).
  330: 
  331: configure_sha1_fail_log_with_sha224(Config) ->
  332:     configure_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-224">>).
  333: 
  334: configure_sha224_fail_log_with_sha256(Config) ->
  335:     configure_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-256">>).
  336: 
  337: configure_sha256_fail_log_with_sha384(Config) ->
  338:     configure_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-384">>).
  339: 
  340: configure_sha384_fail_log_with_sha512(Config) ->
  341:     configure_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-512">>).
  342: 
  343: configure_sha512_fail_log_with_sha1(Config) ->
  344:     configure_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-1">>).
  345: 
  346: %%
  347: %% configure_sha*_plus_fail_log_with_sha* tests are succeeding due to the fact that
  348: %% escalus, when configured with fast_tls and login with scram, sets channel binding
  349: %% flag to 'y'. This indicates that escalus supports channel binding but the server
  350: %% does not. The server did advertise the SCRAM PLUS mechanism, so this flag is
  351: %% incorrect and could be the result of the man-in-the-middle attack attempting to
  352: %% downgrade the authentication mechanism. Because of that, the authentication should fail.
  353: %%
  354: configure_sha1_plus_fail_log_with_sha1(Config) ->
  355:     configure_scram_plus_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-1">>).
  356: 
  357: configure_sha224_plus_fail_log_with_sha224(Config) ->
  358:     configure_scram_plus_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-224">>).
  359: 
  360: configure_sha256_plus_fail_log_with_sha256(Config) ->
  361:     configure_scram_plus_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-256">>).
  362: 
  363: configure_sha384_plus_fail_log_with_sha384(Config) ->
  364:     configure_scram_plus_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-384">>).
  365: 
  366: configure_sha512_plus_fail_log_with_sha512(Config) ->
  367:     configure_scram_plus_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-512">>).
  368: 
  369: log_non_existent_plain(Config) ->
  370:     {auth_failed, _, Xmlel} = log_non_existent(Config),
  371:     #xmlel{name = <<"failure">>} = Xmlel,
  372:     #xmlel{} = exml_query:subelement(Xmlel, <<"not-authorized">>).
  373: 
  374: log_non_existent_digest(Config) ->
  375:     R = log_non_existent([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]),
  376:     {expected_challenge, _, _} = R.
  377: 
  378: log_non_existent_scram(Config) ->
  379:     R = log_non_existent([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]),
  380:     {expected_challenge, _, _} = R.
  381: 
  382: log_bad_user_fails(Config) ->
  383:     Config1 = [{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config],
  384:     [{kate, UserSpec}] = escalus_users:get_users([kate]),
  385:     UserSpec1 = lists:keyreplace(username, 1, UserSpec, {username, <<" kate">>}),
  386:     {error, {connection_step_failed, _, R}} = escalus_client:start(Config1, UserSpec1, <<"res">>),
  387:     {expected_challenge, got, Xmlel} = R,
  388:     #xmlel{name = <<"failure">>} = Xmlel.
  389: 
  390: log_non_existent(Config) ->
  391:     [{kate, UserSpec}] = escalus_users:get_users([kate]),
  392:     {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>),
  393:     R.
  394: 
  395: blocked_user(_Config) ->
  396:     [{_, Spec}] = escalus_users:get_users([alice]),
  397:     try
  398:         {ok, _Alice, _Spec2, _Features} = escalus_connection:start(Spec),
  399:         ct:fail("Alice authenticated but shouldn't")
  400:     catch
  401:         error:{assertion_failed, assert, is_iq_result, Stanza, _Bin} ->
  402:             <<"cancel">> = exml_query:path(Stanza, [{element, <<"error">>}, {attr, <<"type">>}])
  403:     end.
  404: 
  405: access_none_blocks_all_users(Config) ->
  406:     blocked_user(Config).
  407: 
  408: access_none_for_other_listener_has_no_effect(Config) ->
  409:     log_one(Config).
  410: 
  411: messages_story(Config) ->
  412:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  413: 
  414:         % Alice sends a message to Bob
  415:         escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)),
  416: 
  417:         % Bob gets the message
  418:         escalus_assert:is_chat_message(<<"Hi!">>, escalus_client:wait_for_stanza(Bob))
  419: 
  420:     end).
  421: 
  422: %%--------------------------------------------------------------------
  423: %% Helpers
  424: %%--------------------------------------------------------------------
  425: 
  426: configure_c2s_listener(Config) ->
  427:     C2SPort = ct:get_config({hosts, mim, c2s_port}),
  428:     [C2SListener = #{tls := TLSOpts}] =
  429:         mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}),
  430:     %% replace starttls with tls
  431:     NewTLSOpts = TLSOpts#{mode := tls},
  432:     mongoose_helper:restart_listener(mim(), C2SListener#{tls := NewTLSOpts}),
  433:     [{c2s_listener, C2SListener} | Config].
  434: 
  435: create_tls_users(Config) ->
  436:    Config1 = escalus:create_users(Config, escalus:get_users([alice, neustradamus])),
  437:    Users = proplists:get_value(escalus_users, Config1, []),
  438:    Users1 = prepare_user_for_ssl(Users, neustradamus),
  439:    Users2 = prepare_user_for_ssl(Users1, alice),
  440:    lists:keystore(escalus_users, 1, Config1, {escalus_users, Users2}).
  441: 
  442: prepare_user_for_ssl(Users, User) ->
  443:    UserSpec = proplists:get_value(User, Users),
  444:    UserSpec1 = lists:keydelete(starttls, 1, UserSpec),
  445:    UserSpec2 = lists:keystore(ssl, 1, UserSpec1, {ssl, true}),
  446:    UserSpec3 = lists:keystore(ssl_opts, 1, UserSpec2, {ssl_opts, [{verify, verify_none}]}),
  447:    lists:keystore(User, 1, Users, {User, UserSpec3}).
  448: 
  449: delete_tls_users(Config) ->
  450:     escalus:delete_users(Config, escalus:get_users([alice, neustradamus])).
  451: 
  452: assert_password_format(GroupName, Config) ->
  453:     Users = proplists:get_value(escalus_users, Config),
  454:     [verify_format(GroupName, User) || User <- Users],
  455:     Config.
  456: 
  457: verify_format(GroupName, {_User, Props}) ->
  458:     Username = escalus_utils:jid_to_lower(proplists:get_value(username, Props)),
  459:     Server = proplists:get_value(server, Props),
  460:     Password = proplists:get_value(password, Props),
  461:     JID = mongoose_helper:make_jid(Username, Server),
  462:     {SPassword, _} = rpc(mim(), ejabberd_auth, get_passterm_with_authmodule, [host_type(), JID]),
  463:     do_verify_format(GroupName, Password, SPassword).
  464: 
  465: 
  466: do_verify_format(GroupName, _P, #{iteration_count := _IC,
  467:                                   sha    := #{salt := _, stored_key := _, server_key := _},
  468:                                   sha224 := #{salt := _, stored_key := _, server_key := _},
  469:                                   sha256 := #{salt := _, stored_key := _, server_key := _},
  470:                                   sha384 := #{salt := _, stored_key := _, server_key := _},
  471:                                   sha512 := #{salt := _, stored_key := _, server_key := _}}) when
  472:                  GroupName == login_scram orelse GroupName == scram ->
  473:     true;
  474: do_verify_format({scram, Sha}, _Password, ScramMap = #{iteration_count := _IC}) ->
  475:    maps:is_key(Sha, ScramMap);
  476: do_verify_format(login_scram, _Password, SPassword) ->
  477:     %% returned password is a tuple containing scram data
  478:     {_, _, _, _} = SPassword;
  479: do_verify_format(_, Password, SPassword) ->
  480:     Password = SPassword.
  481: 
  482: set_acl_for_blocking(Config, Spec) ->
  483:     User = proplists:get_value(username, Spec),
  484:     LUser = jid:nodeprep(User),
  485:     mongoose_helper:backup_and_set_config_option(Config, [{acl, host_type()}, blocked],
  486:                                                  [#{user => LUser, match => current_domain}]).
  487: 
  488: unset_acl_for_blocking(Config) ->
  489:     mongoose_helper:restore_config_option(Config, [{acl, host_type()}, blocked]).
  490: 
  491: configure_and_log_scram(Config, Sha, Mech) ->
  492:     set_scram_sha(Config, Sha),
  493:     log_one([{escalus_auth_method, Mech} | Config]).
  494: 
  495: configure_and_log_scram_plus(Config, Sha, Mech) ->
  496:     set_scram_sha(Config, Sha),
  497:     log_one_scram_plus([{escalus_auth_method, Mech} | Config]).
  498: 
  499: configure_and_fail_log_scram(Config, Sha, Mech) ->
  500:     set_scram_sha(Config, Sha),
  501:     {expected_challenge, _, _} = fail_log_one([{escalus_auth_method, Mech} | Config]).
  502: 
  503: configure_scram_plus_and_fail_log_scram(Config, Sha, Mech) ->
  504:     set_scram_sha(Config, Sha),
  505:     {expected_challenge, _, _} = fail_log_one_scram_plus([{escalus_auth_method, Mech} | Config]).
  506: 
  507: set_scram_sha(Config, Sha) ->
  508:     NewAuthOpts = mongoose_helper:auth_opts_with_password_format({scram, [Sha]}),
  509:     mongoose_helper:change_config_option(Config, {auth, host_type()}, NewAuthOpts),
  510:     assert_password_format({scram, Sha}, Config).
  511: 
  512: fail_log_one(Config) ->
  513:     [{alice, UserSpec}] = escalus_users:get_users([alice]),
  514:     {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>),
  515:     R.
  516: 
  517: fail_log_one_scram_plus(Config) ->
  518:     [{neustradamus, UserSpec}] = escalus_users:get_users([neustradamus]),
  519:     {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>),
  520:     R.
  521: 
  522: are_sasl_scram_modules_supported() ->
  523:     ScramModules = [cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256,
  524:                     cyrsasl_scram_sha384, cyrsasl_scram_sha512],
  525:     IsSupported = [mongoose_helper:supports_sasl_module(Module) || Module <- ScramModules],
  526:     [true, true, true, true, true] == IsSupported.
  527: 
  528: restore_c2s(Config) ->
  529:    C2SListener = proplists:get_value(c2s_listener, Config),
  530:    mongoose_helper:restart_listener(mim(), C2SListener).
  531: 
  532: -define(NS_SASL, <<"urn:ietf:params:xml:ns:xmpp-sasl">>).
  533: auth_stanza(Mech, Payload) ->
  534:     #xmlel{name = <<"auth">>,
  535:            attrs = [{<<"xmlns">>, ?NS_SASL},
  536:                     {<<"mechanism">>, Mech}],
  537:            children = [#xmlcdata{content = base64:encode(Payload)}]}.
  538: 
  539: auth_response(Payload) ->
  540:     #xmlel{name = <<"response">>,
  541:            attrs = [{<<"xmlns">>, ?NS_SASL}],
  542:            children = [#xmlcdata{content = base64:encode(Payload)}]}.