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: 22: -import(distributed_helper, [mim/0, 23: require_rpc_nodes/1, 24: rpc/4]). 25: 26: -import(domain_helper, [host_type/0, domain/0]). 27: 28: %%-------------------------------------------------------------------- 29: %% Suite configuration 30: %%-------------------------------------------------------------------- 31: 32: all() -> 33: [ 34: {group, login}, 35: {group, login_digest}, 36: {group, login_scram}, 37: {group, login_scram_store_plain}, 38: {group, login_specific_scram}, 39: {group, login_scram_tls}, 40: {group, messages} 41: ]. 42: 43: groups() -> 44: [{login, [parallel], all_tests()}, 45: {login_digest, [sequence], digest_tests()}, 46: {login_scram, [parallel], scram_tests()}, 47: {login_scram_store_plain, [parallel], scram_tests()}, 48: {login_scram_tls, [parallel], scram_tests()}, 49: {login_specific_scram, [sequence], configure_specific_scram_test()}, 50: {messages, [sequence], [messages_story, message_zlib_limit]}]. 51: 52: scram_tests() -> 53: [log_one, 54: log_one_scram_sha1, 55: log_one_scram_sha224, 56: log_one_scram_sha256, 57: log_one_scram_sha384, 58: log_one_scram_sha512, 59: log_one_scram_sha1_plus, 60: log_one_scram_sha224_plus, 61: log_one_scram_sha256_plus, 62: log_one_scram_sha384_plus, 63: log_one_scram_sha512_plus]. 64: 65: configure_specific_scram_test() -> 66: [configure_sha1_log_with_sha1, 67: configure_sha224_log_with_sha224, 68: configure_sha256_log_with_sha256, 69: configure_sha384_log_with_sha384, 70: configure_sha512_log_with_sha512, 71: configure_sha1_log_with_sha1_plus, 72: configure_sha224_log_with_sha224_plus, 73: configure_sha256_log_with_sha256_plus, 74: configure_sha384_log_with_sha384_plus, 75: configure_sha512_log_with_sha512_plus, 76: configure_sha1_fail_log_with_sha224, 77: configure_sha224_fail_log_with_sha256, 78: configure_sha256_fail_log_with_sha384, 79: configure_sha384_fail_log_with_sha512, 80: configure_sha512_fail_log_with_sha1, 81: configure_sha1_plus_fail_log_with_sha1, 82: configure_sha224_plus_fail_log_with_sha224, 83: configure_sha256_plus_fail_log_with_sha256, 84: configure_sha384_plus_fail_log_with_sha384, 85: configure_sha512_plus_fail_log_with_sha512]. 86: 87: all_tests() -> 88: [log_one, 89: log_non_existent_plain, 90: log_one_scram_sha1, 91: log_non_existent_scram, 92: blocked_user 93: ]. 94: 95: digest_tests() -> 96: [log_one_digest, 97: log_non_existent_digest]. 98: 99: suite() -> 100: require_rpc_nodes([mim]) ++ escalus:suite(). 101: 102: %%-------------------------------------------------------------------- 103: %% Init & teardown 104: %%-------------------------------------------------------------------- 105: 106: init_per_suite(Config) -> 107: escalus:init_per_suite(Config). 108: 109: end_per_suite(Config) -> 110: escalus_fresh:clean(), 111: escalus:end_per_suite(Config). 112: 113: init_per_group(login_digest = GroupName, ConfigIn) -> 114: Config = backup_and_set_options(GroupName, ConfigIn), 115: case mongoose_helper:supports_sasl_module(cyrsasl_digest) of 116: false -> 117: mongoose_helper:restore_config(Config), 118: {skip, "digest password type not supported"}; 119: true -> 120: escalus:create_users(Config, escalus:get_users([alice, bob])) 121: end; 122: init_per_group(GroupName, ConfigIn) 123: when GroupName == login_scram; 124: GroupName == login_scram_store_plain -> 125: Config = backup_and_set_options(GroupName, ConfigIn), 126: case are_sasl_scram_modules_supported() of 127: false -> 128: mongoose_helper:restore_config(Config), 129: {skip, "scram password type not supported"}; 130: true -> 131: Config2 = escalus:create_users(Config, escalus:get_users([alice, bob, neustradamus])), 132: assert_password_format(GroupName, Config2) 133: end; 134: init_per_group(login_scram_tls = GroupName, ConfigIn) -> 135: Config = backup_and_set_options(GroupName, ConfigIn), 136: case are_sasl_scram_modules_supported() of 137: false -> 138: mongoose_helper:restore_config(Config), 139: {skip, "scram password type not supported"}; 140: true -> 141: Config1 = configure_c2s_listener(Config), 142: Config2 = create_tls_users(Config1), 143: assert_password_format(scram, Config2) 144: end; 145: init_per_group(login_specific_scram = GroupName, ConfigIn) -> 146: Config = backup_and_set_options(GroupName, ConfigIn), 147: case are_sasl_scram_modules_supported() of 148: false -> 149: mongoose_helper:restore_config(Config), 150: {skip, "scram password type not supported"}; 151: true -> 152: escalus:create_users(Config, escalus:get_users([alice, bob, neustradamus])) 153: end; 154: init_per_group(GroupName, ConfigIn) -> 155: Config = backup_and_set_options(GroupName, ConfigIn), 156: escalus:create_users(Config, escalus:get_users([alice, bob])). 157: 158: backup_and_set_options(GroupName, Config) -> 159: mongoose_helper:backup_and_set_config_option(Config, {auth, host_type()}, auth_opts(GroupName)). 160: 161: auth_opts(login_digest) -> 162: AuthOpts = mongoose_helper:auth_opts_with_password_format(plain), 163: AuthOpts#{sasl_mechanisms => [cyrsasl_digest]}; 164: auth_opts(login_scram_store_plain) -> 165: mongoose_helper:auth_opts_with_password_format(plain); 166: auth_opts(_GroupName) -> 167: mongoose_helper:auth_opts_with_password_format(scram). 168: 169: end_per_group(login_digest, Config) -> 170: mongoose_helper:restore_config(Config), 171: escalus:delete_users(Config, escalus:get_users([alice, bob])); 172: end_per_group(GroupName, Config) when 173: GroupName == login_scram; GroupName == login_specific_scram -> 174: mongoose_helper:restore_config(Config), 175: escalus:delete_users(Config, escalus:get_users([alice, bob, neustradamus])); 176: end_per_group(login_scram_tls, Config) -> 177: mongoose_helper:restore_config(Config), 178: restore_c2s(Config), 179: delete_tls_users(Config); 180: end_per_group(_GroupName, Config) -> 181: mongoose_helper:restore_config(Config), 182: escalus:delete_users(Config, escalus:get_users([alice, bob])). 183: 184: init_per_testcase(CaseName, Config) when 185: CaseName =:= log_one_scram_sha1; CaseName =:= log_non_existent_scram -> 186: case mongoose_helper:supports_sasl_module(cyrsasl_scram_sha1) of 187: false -> 188: {skip, "scram password type not supported"}; 189: true -> 190: escalus:init_per_testcase(CaseName, Config) 191: end; 192: init_per_testcase(message_zlib_limit, Config) -> 193: [{_U, Props}] = escalus_users:get_users([hacker]), 194: Port = proplists:get_value(port, Props), 195: case mongoose_helper:get_listeners(mim(), #{port => Port}) of 196: [_Listener] -> 197: escalus:create_users(Config, escalus:get_users([hacker])), 198: escalus:init_per_testcase(message_zlib_limit, Config); 199: [] -> 200: {skip, port_not_configured_on_server} 201: end; 202: init_per_testcase(CaseName, Config) -> 203: escalus:init_per_testcase(CaseName, Config). 204: 205: end_per_testcase(message_zlib_limit, Config) -> 206: escalus:delete_users(Config, escalus:get_users([hacker])); 207: end_per_testcase(CaseName, Config) -> 208: escalus:end_per_testcase(CaseName, Config). 209: 210: %%-------------------------------------------------------------------- 211: %% Message tests 212: %%-------------------------------------------------------------------- 213: 214: log_one(Config) -> 215: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 216: 217: escalus_client:send(Alice, escalus_stanza:chat_to(Alice, <<"Hi!">>)), 218: escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Alice)) 219: 220: end). 221: 222: log_one_scram_plus(Config) -> 223: escalus:fresh_story(Config, [{neustradamus, 1}], fun(Neustradamus) -> 224: 225: escalus_client:send(Neustradamus, escalus_stanza:chat_to(Neustradamus, <<"Hi!">>)), 226: escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Neustradamus)) 227: 228: end). 229: 230: log_one_digest(Config) -> 231: log_one([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]). 232: 233: log_one_scram_sha1(Config) -> 234: log_one([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]). 235: 236: log_one_scram_sha224(Config) -> 237: log_one([{escalus_auth_method, <<"SCRAM-SHA-224">>} | Config]). 238: 239: log_one_scram_sha256(Config) -> 240: log_one([{escalus_auth_method, <<"SCRAM-SHA-256">>} | Config]). 241: 242: log_one_scram_sha384(Config) -> 243: log_one([{escalus_auth_method, <<"SCRAM-SHA-384">>} | Config]). 244: 245: log_one_scram_sha512(Config) -> 246: log_one([{escalus_auth_method, <<"SCRAM-SHA-512">>} | Config]). 247: 248: log_one_scram_sha1_plus(Config) -> 249: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-1-PLUS">>} | Config]). 250: 251: log_one_scram_sha224_plus(Config) -> 252: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-224-PLUS">>} | Config]). 253: 254: log_one_scram_sha256_plus(Config) -> 255: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-256-PLUS">>} | Config]). 256: 257: log_one_scram_sha384_plus(Config) -> 258: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-384-PLUS">>} | Config]). 259: 260: log_one_scram_sha512_plus(Config) -> 261: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-512-PLUS">>} | Config]). 262: 263: configure_sha1_log_with_sha1(Config) -> 264: configure_and_log_scram(Config, sha, <<"SCRAM-SHA-1">>). 265: 266: configure_sha224_log_with_sha224(Config) -> 267: configure_and_log_scram(Config, sha224, <<"SCRAM-SHA-224">>). 268: 269: configure_sha256_log_with_sha256(Config) -> 270: configure_and_log_scram(Config, sha256, <<"SCRAM-SHA-256">>). 271: 272: configure_sha384_log_with_sha384(Config) -> 273: configure_and_log_scram(Config, sha384, <<"SCRAM-SHA-384">>). 274: 275: configure_sha512_log_with_sha512(Config) -> 276: configure_and_log_scram(Config, sha512, <<"SCRAM-SHA-512">>). 277: 278: configure_sha1_log_with_sha1_plus(Config) -> 279: configure_and_log_scram_plus(Config, sha, <<"SCRAM-SHA-1-PLUS">>). 280: 281: configure_sha224_log_with_sha224_plus(Config) -> 282: configure_and_log_scram_plus(Config, sha224, <<"SCRAM-SHA-224-PLUS">>). 283: 284: configure_sha256_log_with_sha256_plus(Config) -> 285: configure_and_log_scram_plus(Config, sha256, <<"SCRAM-SHA-256-PLUS">>). 286: 287: configure_sha384_log_with_sha384_plus(Config) -> 288: configure_and_log_scram_plus(Config, sha384, <<"SCRAM-SHA-384-PLUS">>). 289: 290: configure_sha512_log_with_sha512_plus(Config) -> 291: configure_and_log_scram_plus(Config, sha512, <<"SCRAM-SHA-512-PLUS">>). 292: 293: configure_sha1_fail_log_with_sha224(Config) -> 294: configure_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-224">>). 295: 296: configure_sha224_fail_log_with_sha256(Config) -> 297: configure_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-256">>). 298: 299: configure_sha256_fail_log_with_sha384(Config) -> 300: configure_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-384">>). 301: 302: configure_sha384_fail_log_with_sha512(Config) -> 303: configure_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-512">>). 304: 305: configure_sha512_fail_log_with_sha1(Config) -> 306: configure_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-1">>). 307: 308: %% 309: %% configure_sha*_plus_fail_log_with_sha* tests are succeeding due to the fact that 310: %% escalus, when configured with fast_tls and login with scram, sets channel binding 311: %% flag to 'y'. This indicates that escalus supports channel binding but the server 312: %% does not. The server did advertise the SCRAM PLUS mechanism, so this flag is 313: %% incorrect and could be the result of the man-in-the-middle attack attempting to 314: %% downgrade the authentication mechanism. Because of that, the authentication should fail. 315: %% 316: configure_sha1_plus_fail_log_with_sha1(Config) -> 317: configure_scram_plus_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-1">>). 318: 319: configure_sha224_plus_fail_log_with_sha224(Config) -> 320: configure_scram_plus_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-224">>). 321: 322: configure_sha256_plus_fail_log_with_sha256(Config) -> 323: configure_scram_plus_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-256">>). 324: 325: configure_sha384_plus_fail_log_with_sha384(Config) -> 326: configure_scram_plus_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-384">>). 327: 328: configure_sha512_plus_fail_log_with_sha512(Config) -> 329: configure_scram_plus_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-512">>). 330: 331: log_non_existent_plain(Config) -> 332: {auth_failed, _, Xmlel} = log_non_existent(Config), 333: #xmlel{name = <<"failure">>} = Xmlel, 334: #xmlel{} = exml_query:subelement(Xmlel, <<"not-authorized">>). 335: 336: log_non_existent_digest(Config) -> 337: R = log_non_existent([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]), 338: {expected_challenge, _, _} = R. 339: 340: log_non_existent_scram(Config) -> 341: R = log_non_existent([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]), 342: {expected_challenge, _, _} = R. 343: 344: log_non_existent(Config) -> 345: [{kate, UserSpec}] = escalus_users:get_users([kate]), 346: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 347: R. 348: 349: blocked_user(Config) -> 350: [{_, Spec}] = escalus_users:get_users([alice]), 351: Config1 = set_acl_for_blocking(Config, Spec), 352: try 353: {ok, _Alice, _Spec2, _Features} = escalus_connection:start(Spec), 354: ct:fail("Alice authenticated but shouldn't") 355: catch 356: error:{assertion_failed, assert, is_iq_result, Stanza, _Bin} -> 357: <<"cancel">> = exml_query:path(Stanza, [{element, <<"error">>}, {attr, <<"type">>}]) 358: after 359: unset_acl_for_blocking(Config1) 360: end, 361: ok. 362: 363: messages_story(Config) -> 364: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 365: 366: % Alice sends a message to Bob 367: escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)), 368: 369: % Bob gets the message 370: escalus_assert:is_chat_message(<<"Hi!">>, escalus_client:wait_for_stanza(Bob)) 371: 372: end). 373: 374: message_zlib_limit(Config) -> 375: escalus:story(Config, [{alice, 1}], fun(Alice) -> 376: [{_, Spec}] = escalus_users:get_users([hacker]), 377: {ok, Hacker, _Features} = escalus_connection:start(Spec), 378: 379: ManySpaces = [ 32 || _N <- lists:seq(1, 10*1024) ], 380: 381: escalus:send(Hacker, escalus_stanza:chat_to(Alice, ManySpaces)), 382: 383: escalus:assert(is_stream_error, [<<"policy-violation">>, <<"child element too big">>], 384: escalus:wait_for_stanza(Hacker)), 385: escalus:assert(is_stream_end, escalus:wait_for_stanza(Hacker)) 386: 387: end). 388: 389: %%-------------------------------------------------------------------- 390: %% Helpers 391: %%-------------------------------------------------------------------- 392: 393: configure_c2s_listener(Config) -> 394: C2SPort = ct:get_config({hosts, mim, c2s_port}), 395: [C2SListener = #{tls := TLSOpts}] = 396: mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => ejabberd_c2s}), 397: %% replace starttls with tls 398: NewTLSOpts = TLSOpts#{mode := tls}, 399: mongoose_helper:restart_listener(mim(), C2SListener#{tls := NewTLSOpts}), 400: [{c2s_listener, C2SListener} | Config]. 401: 402: create_tls_users(Config) -> 403: Config1 = escalus:create_users(Config, escalus:get_users([alice, neustradamus])), 404: Users = proplists:get_value(escalus_users, Config1, []), 405: NSpec = lists:keydelete(starttls, 1, proplists:get_value(neustradamus, Users)), 406: NSpec2 = {neustradamus, lists:keystore(ssl, 1, NSpec, {ssl, true})}, 407: NewUsers = lists:keystore(neustradamus, 1, Users, NSpec2), 408: AliceSpec = proplists:get_value(alice, Users), 409: AliceSpec2 = {alice, lists:keystore(ssl, 1, AliceSpec, {ssl, true})}, 410: NewUsers2 = lists:keystore(alice, 1, NewUsers, AliceSpec2), 411: lists:keystore(escalus_users, 1, Config1, {escalus_users, NewUsers2}). 412: 413: delete_tls_users(Config) -> 414: escalus:delete_users(Config, escalus:get_users([alice, neustradamus])). 415: 416: assert_password_format(GroupName, Config) -> 417: Users = proplists:get_value(escalus_users, Config), 418: [verify_format(GroupName, User) || User <- Users], 419: Config. 420: 421: verify_format(GroupName, {_User, Props}) -> 422: Username = escalus_utils:jid_to_lower(proplists:get_value(username, Props)), 423: Server = proplists:get_value(server, Props), 424: Password = proplists:get_value(password, Props), 425: JID = mongoose_helper:make_jid(Username, Server), 426: {SPassword, _} = rpc(mim(), ejabberd_auth, get_passterm_with_authmodule, [host_type(), JID]), 427: do_verify_format(GroupName, Password, SPassword). 428: 429: 430: do_verify_format(GroupName, _P, #{iteration_count := _IC, 431: sha := #{salt := _, stored_key := _, server_key := _}, 432: sha224 := #{salt := _, stored_key := _, server_key := _}, 433: sha256 := #{salt := _, stored_key := _, server_key := _}, 434: sha384 := #{salt := _, stored_key := _, server_key := _}, 435: sha512 := #{salt := _, stored_key := _, server_key := _}}) when 436: GroupName == login_scram orelse GroupName == scram -> 437: true; 438: do_verify_format({scram, Sha}, _Password, ScramMap = #{iteration_count := _IC}) -> 439: maps:is_key(Sha, ScramMap); 440: do_verify_format(login_scram, _Password, SPassword) -> 441: %% returned password is a tuple containing scram data 442: {_, _, _, _} = SPassword; 443: do_verify_format(_, Password, SPassword) -> 444: Password = SPassword. 445: 446: set_acl_for_blocking(Config, Spec) -> 447: User = proplists:get_value(username, Spec), 448: LUser = jid:nodeprep(User), 449: mongoose_helper:backup_and_set_config_option(Config, [{acl, host_type()}, blocked], 450: [#{user => LUser, match => current_domain}]). 451: 452: unset_acl_for_blocking(Config) -> 453: mongoose_helper:restore_config_option(Config, [{acl, host_type()}, blocked]). 454: 455: configure_and_log_scram(Config, Sha, Mech) -> 456: set_scram_sha(Config, Sha), 457: log_one([{escalus_auth_method, Mech} | Config]). 458: 459: configure_and_log_scram_plus(Config, Sha, Mech) -> 460: set_scram_sha(Config, Sha), 461: log_one_scram_plus([{escalus_auth_method, Mech} | Config]). 462: 463: configure_and_fail_log_scram(Config, Sha, Mech) -> 464: set_scram_sha(Config, Sha), 465: {expected_challenge, _, _} = fail_log_one([{escalus_auth_method, Mech} | Config]). 466: 467: configure_scram_plus_and_fail_log_scram(Config, Sha, Mech) -> 468: set_scram_sha(Config, Sha), 469: {expected_challenge, _, _} = fail_log_one_scram_plus([{escalus_auth_method, Mech} | Config]). 470: 471: set_scram_sha(Config, Sha) -> 472: NewAuthOpts = mongoose_helper:auth_opts_with_password_format({scram, [Sha]}), 473: mongoose_helper:change_config_option(Config, {auth, host_type()}, NewAuthOpts), 474: assert_password_format({scram, Sha}, Config). 475: 476: fail_log_one(Config) -> 477: [{alice, UserSpec}] = escalus_users:get_users([alice]), 478: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 479: R. 480: 481: fail_log_one_scram_plus(Config) -> 482: [{neustradamus, UserSpec}] = escalus_users:get_users([neustradamus]), 483: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 484: R. 485: 486: are_sasl_scram_modules_supported() -> 487: ScramModules = [cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256, 488: cyrsasl_scram_sha384, cyrsasl_scram_sha512], 489: IsSupported = [mongoose_helper:supports_sasl_module(Module) || Module <- ScramModules], 490: [true, true, true, true, true] == IsSupported. 491: 492: restore_c2s(Config) -> 493: C2SListener = proplists:get_value(c2s_listener, Config), 494: mongoose_helper:restart_listener(mim(), C2SListener).