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: Listeners = [Listener 194: || {Listener, _, _} <- rpc(mim(), mongoose_config, get_opt, [listen])], 195: [{_U, Props}] = escalus_users:get_users([hacker]), 196: Port = proplists:get_value(port, Props), 197: case lists:keymember(Port, 1, Listeners) of 198: true -> 199: escalus:create_users(Config, escalus:get_users([hacker])), 200: escalus:init_per_testcase(message_zlib_limit, Config); 201: false -> 202: {skip, port_not_configured_on_server} 203: end; 204: init_per_testcase(CaseName, Config) -> 205: escalus:init_per_testcase(CaseName, Config). 206: 207: end_per_testcase(message_zlib_limit, Config) -> 208: escalus:delete_users(Config, escalus:get_users([hacker])); 209: end_per_testcase(CaseName, Config) -> 210: escalus:end_per_testcase(CaseName, Config). 211: 212: %%-------------------------------------------------------------------- 213: %% Message tests 214: %%-------------------------------------------------------------------- 215: 216: log_one(Config) -> 217: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 218: 219: escalus_client:send(Alice, escalus_stanza:chat_to(Alice, <<"Hi!">>)), 220: escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Alice)) 221: 222: end). 223: 224: log_one_scram_plus(Config) -> 225: escalus:fresh_story(Config, [{neustradamus, 1}], fun(Neustradamus) -> 226: 227: escalus_client:send(Neustradamus, escalus_stanza:chat_to(Neustradamus, <<"Hi!">>)), 228: escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Neustradamus)) 229: 230: end). 231: 232: log_one_digest(Config) -> 233: log_one([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]). 234: 235: log_one_scram_sha1(Config) -> 236: log_one([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]). 237: 238: log_one_scram_sha224(Config) -> 239: log_one([{escalus_auth_method, <<"SCRAM-SHA-224">>} | Config]). 240: 241: log_one_scram_sha256(Config) -> 242: log_one([{escalus_auth_method, <<"SCRAM-SHA-256">>} | Config]). 243: 244: log_one_scram_sha384(Config) -> 245: log_one([{escalus_auth_method, <<"SCRAM-SHA-384">>} | Config]). 246: 247: log_one_scram_sha512(Config) -> 248: log_one([{escalus_auth_method, <<"SCRAM-SHA-512">>} | Config]). 249: 250: log_one_scram_sha1_plus(Config) -> 251: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-1-PLUS">>} | Config]). 252: 253: log_one_scram_sha224_plus(Config) -> 254: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-224-PLUS">>} | Config]). 255: 256: log_one_scram_sha256_plus(Config) -> 257: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-256-PLUS">>} | Config]). 258: 259: log_one_scram_sha384_plus(Config) -> 260: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-384-PLUS">>} | Config]). 261: 262: log_one_scram_sha512_plus(Config) -> 263: log_one_scram_plus([{escalus_auth_method, <<"SCRAM-SHA-512-PLUS">>} | Config]). 264: 265: configure_sha1_log_with_sha1(Config) -> 266: configure_and_log_scram(Config, sha, <<"SCRAM-SHA-1">>). 267: 268: configure_sha224_log_with_sha224(Config) -> 269: configure_and_log_scram(Config, sha224, <<"SCRAM-SHA-224">>). 270: 271: configure_sha256_log_with_sha256(Config) -> 272: configure_and_log_scram(Config, sha256, <<"SCRAM-SHA-256">>). 273: 274: configure_sha384_log_with_sha384(Config) -> 275: configure_and_log_scram(Config, sha384, <<"SCRAM-SHA-384">>). 276: 277: configure_sha512_log_with_sha512(Config) -> 278: configure_and_log_scram(Config, sha512, <<"SCRAM-SHA-512">>). 279: 280: configure_sha1_log_with_sha1_plus(Config) -> 281: configure_and_log_scram_plus(Config, sha, <<"SCRAM-SHA-1-PLUS">>). 282: 283: configure_sha224_log_with_sha224_plus(Config) -> 284: configure_and_log_scram_plus(Config, sha224, <<"SCRAM-SHA-224-PLUS">>). 285: 286: configure_sha256_log_with_sha256_plus(Config) -> 287: configure_and_log_scram_plus(Config, sha256, <<"SCRAM-SHA-256-PLUS">>). 288: 289: configure_sha384_log_with_sha384_plus(Config) -> 290: configure_and_log_scram_plus(Config, sha384, <<"SCRAM-SHA-384-PLUS">>). 291: 292: configure_sha512_log_with_sha512_plus(Config) -> 293: configure_and_log_scram_plus(Config, sha512, <<"SCRAM-SHA-512-PLUS">>). 294: 295: configure_sha1_fail_log_with_sha224(Config) -> 296: configure_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-224">>). 297: 298: configure_sha224_fail_log_with_sha256(Config) -> 299: configure_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-256">>). 300: 301: configure_sha256_fail_log_with_sha384(Config) -> 302: configure_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-384">>). 303: 304: configure_sha384_fail_log_with_sha512(Config) -> 305: configure_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-512">>). 306: 307: configure_sha512_fail_log_with_sha1(Config) -> 308: configure_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-1">>). 309: 310: %% 311: %% configure_sha*_plus_fail_log_with_sha* tests are succeeding due to the fact that 312: %% escalus, when configured with fast_tls and login with scram, sets channel binding 313: %% flag to 'y'. This indicates that escalus supports channel binding but the server 314: %% does not. The server did advertise the SCRAM PLUS mechanism, so this flag is 315: %% incorrect and could be the result of the man-in-the-middle attack attempting to 316: %% downgrade the authentication mechanism. Because of that, the authentication should fail. 317: %% 318: configure_sha1_plus_fail_log_with_sha1(Config) -> 319: configure_scram_plus_and_fail_log_scram(Config, sha, <<"SCRAM-SHA-1">>). 320: 321: configure_sha224_plus_fail_log_with_sha224(Config) -> 322: configure_scram_plus_and_fail_log_scram(Config, sha224, <<"SCRAM-SHA-224">>). 323: 324: configure_sha256_plus_fail_log_with_sha256(Config) -> 325: configure_scram_plus_and_fail_log_scram(Config, sha256, <<"SCRAM-SHA-256">>). 326: 327: configure_sha384_plus_fail_log_with_sha384(Config) -> 328: configure_scram_plus_and_fail_log_scram(Config, sha384, <<"SCRAM-SHA-384">>). 329: 330: configure_sha512_plus_fail_log_with_sha512(Config) -> 331: configure_scram_plus_and_fail_log_scram(Config, sha512, <<"SCRAM-SHA-512">>). 332: 333: log_non_existent_plain(Config) -> 334: {auth_failed, _, Xmlel} = log_non_existent(Config), 335: #xmlel{name = <<"failure">>} = Xmlel, 336: #xmlel{} = exml_query:subelement(Xmlel, <<"not-authorized">>). 337: 338: log_non_existent_digest(Config) -> 339: R = log_non_existent([{escalus_auth_method, <<"DIGEST-MD5">>} | Config]), 340: {expected_challenge, _, _} = R. 341: 342: log_non_existent_scram(Config) -> 343: R = log_non_existent([{escalus_auth_method, <<"SCRAM-SHA-1">>} | Config]), 344: {expected_challenge, _, _} = R. 345: 346: log_non_existent(Config) -> 347: [{kate, UserSpec}] = escalus_users:get_users([kate]), 348: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 349: R. 350: 351: blocked_user(Config) -> 352: [{_, Spec}] = escalus_users:get_users([alice]), 353: Config1 = set_acl_for_blocking(Config, Spec), 354: try 355: {ok, _Alice, _Spec2, _Features} = escalus_connection:start(Spec), 356: ct:fail("Alice authenticated but shouldn't") 357: catch 358: error:{assertion_failed, assert, is_iq_result, Stanza, _Bin} -> 359: <<"cancel">> = exml_query:path(Stanza, [{element, <<"error">>}, {attr, <<"type">>}]) 360: after 361: unset_acl_for_blocking(Config1) 362: end, 363: ok. 364: 365: messages_story(Config) -> 366: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 367: 368: % Alice sends a message to Bob 369: escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)), 370: 371: % Bob gets the message 372: escalus_assert:is_chat_message(<<"Hi!">>, escalus_client:wait_for_stanza(Bob)) 373: 374: end). 375: 376: message_zlib_limit(Config) -> 377: escalus:story(Config, [{alice, 1}], fun(Alice) -> 378: [{_, Spec}] = escalus_users:get_users([hacker]), 379: {ok, Hacker, _Features} = escalus_connection:start(Spec), 380: 381: ManySpaces = [ 32 || _N <- lists:seq(1, 10*1024) ], 382: 383: escalus:send(Hacker, escalus_stanza:chat_to(Alice, ManySpaces)), 384: 385: escalus:assert(is_stream_error, [<<"policy-violation">>, <<"child element too big">>], 386: escalus:wait_for_stanza(Hacker)), 387: escalus:assert(is_stream_end, escalus:wait_for_stanza(Hacker)) 388: 389: end). 390: 391: %%-------------------------------------------------------------------- 392: %% Helpers 393: %%-------------------------------------------------------------------- 394: 395: configure_c2s_listener(Config) -> 396: C2SPort = ct:get_config({hosts, mim, c2s_port}), 397: [C2SListener = #{tls := TLSOpts}] = 398: mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => ejabberd_c2s}), 399: %% replace starttls with tls 400: NewTLSOpts = [tls | TLSOpts -- [starttls]], 401: mongoose_helper:restart_listener(mim(), C2SListener#{tls := NewTLSOpts}), 402: [{c2s_listener, C2SListener} | Config]. 403: 404: create_tls_users(Config) -> 405: Config1 = escalus:create_users(Config, escalus:get_users([alice, neustradamus])), 406: Users = proplists:get_value(escalus_users, Config1, []), 407: NSpec = lists:keydelete(starttls, 1, proplists:get_value(neustradamus, Users)), 408: NSpec2 = {neustradamus, lists:keystore(ssl, 1, NSpec, {ssl, true})}, 409: NewUsers = lists:keystore(neustradamus, 1, Users, NSpec2), 410: AliceSpec = proplists:get_value(alice, Users), 411: AliceSpec2 = {alice, lists:keystore(ssl, 1, AliceSpec, {ssl, true})}, 412: NewUsers2 = lists:keystore(alice, 1, NewUsers, AliceSpec2), 413: lists:keystore(escalus_users, 1, Config1, {escalus_users, NewUsers2}). 414: 415: delete_tls_users(Config) -> 416: escalus:delete_users(Config, escalus:get_users([alice, neustradamus])). 417: 418: assert_password_format(GroupName, Config) -> 419: Users = proplists:get_value(escalus_users, Config), 420: [verify_format(GroupName, User) || User <- Users], 421: Config. 422: 423: verify_format(GroupName, {_User, Props}) -> 424: Username = escalus_utils:jid_to_lower(proplists:get_value(username, Props)), 425: Server = proplists:get_value(server, Props), 426: Password = proplists:get_value(password, Props), 427: JID = mongoose_helper:make_jid(Username, Server), 428: {SPassword, _} = rpc(mim(), ejabberd_auth, get_passterm_with_authmodule, [host_type(), JID]), 429: do_verify_format(GroupName, Password, SPassword). 430: 431: 432: do_verify_format(GroupName, _P, #{iteration_count := _IC, 433: sha := #{salt := _, stored_key := _, server_key := _}, 434: sha224 := #{salt := _, stored_key := _, server_key := _}, 435: sha256 := #{salt := _, stored_key := _, server_key := _}, 436: sha384 := #{salt := _, stored_key := _, server_key := _}, 437: sha512 := #{salt := _, stored_key := _, server_key := _}}) when 438: GroupName == login_scram orelse GroupName == scram -> 439: true; 440: do_verify_format({scram, Sha}, _Password, ScramMap = #{iteration_count := _IC}) -> 441: maps:is_key(Sha, ScramMap); 442: do_verify_format(login_scram, _Password, SPassword) -> 443: %% returned password is a tuple containing scram data 444: {_, _, _, _} = SPassword; 445: do_verify_format(_, Password, SPassword) -> 446: Password = SPassword. 447: 448: set_acl_for_blocking(Config, Spec) -> 449: User = proplists:get_value(username, Spec), 450: LUser = jid:nodeprep(User), 451: mongoose_helper:backup_and_set_config_option(Config, [{acl, host_type()}, blocked], 452: [#{user => LUser, match => current_domain}]). 453: 454: unset_acl_for_blocking(Config) -> 455: mongoose_helper:restore_config_option(Config, [{acl, host_type()}, blocked]). 456: 457: configure_and_log_scram(Config, Sha, Mech) -> 458: set_scram_sha(Config, Sha), 459: log_one([{escalus_auth_method, Mech} | Config]). 460: 461: configure_and_log_scram_plus(Config, Sha, Mech) -> 462: set_scram_sha(Config, Sha), 463: log_one_scram_plus([{escalus_auth_method, Mech} | Config]). 464: 465: configure_and_fail_log_scram(Config, Sha, Mech) -> 466: set_scram_sha(Config, Sha), 467: {expected_challenge, _, _} = fail_log_one([{escalus_auth_method, Mech} | Config]). 468: 469: configure_scram_plus_and_fail_log_scram(Config, Sha, Mech) -> 470: set_scram_sha(Config, Sha), 471: {expected_challenge, _, _} = fail_log_one_scram_plus([{escalus_auth_method, Mech} | Config]). 472: 473: set_scram_sha(Config, Sha) -> 474: NewAuthOpts = mongoose_helper:auth_opts_with_password_format({scram, [Sha]}), 475: mongoose_helper:change_config_option(Config, {auth, host_type()}, NewAuthOpts), 476: assert_password_format({scram, Sha}, Config). 477: 478: fail_log_one(Config) -> 479: [{alice, UserSpec}] = escalus_users:get_users([alice]), 480: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 481: R. 482: 483: fail_log_one_scram_plus(Config) -> 484: [{neustradamus, UserSpec}] = escalus_users:get_users([neustradamus]), 485: {error, {connection_step_failed, _, R}} = escalus_client:start(Config, UserSpec, <<"res">>), 486: R. 487: 488: are_sasl_scram_modules_supported() -> 489: ScramModules = [cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256, 490: cyrsasl_scram_sha384, cyrsasl_scram_sha512], 491: IsSupported = [mongoose_helper:supports_sasl_module(Module) || Module <- ScramModules], 492: [true, true, true, true, true] == IsSupported. 493: 494: restore_c2s(Config) -> 495: C2SListener = proplists:get_value(c2s_listener, Config), 496: mongoose_helper:restart_listener(mim(), C2SListener).