1: -module(auth_internal_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include("scram.hrl").
    6: 
    7: all() ->
    8:     [passwords_as_records_are_still_supported,
    9:      passwords_in_plain_can_be_converted_to_scram].
   10: 
   11: init_per_suite(C) ->
   12:     application:ensure_all_started(jid),
   13:     ok = mnesia:create_schema([node()]),
   14:     ok = mnesia:start(),
   15:     ejabberd_auth_internal:start(<<"server">>),
   16:     C.
   17: 
   18: end_per_suite(_C) ->
   19:     mnesia:stop(),
   20:     mnesia:delete_schema([node()]).
   21: 
   22: passwords_as_records_are_still_supported(_C) ->
   23:     %% given in mnesia there is a user with password in old scram format
   24:     {U, S, P} = gen_user(),
   25:     OldScramFormat = old_password_to_scram(P, 340),
   26:     UserRecord = {passwd, {U, S}, OldScramFormat},
   27:     mnesia:dirty_write(UserRecord),
   28:     %% when we read the password via ejabberd_auth_internal:get_password/3
   29:     NewScramMap = ejabberd_auth_internal:get_password(host_type(), U, S),
   30:     %% then new map with sha key is returned
   31:     ?assertMatch(#{iteration_count := _,
   32:                    sha := #{salt       := _,
   33:                             server_key := _,
   34:                             stored_key := _}}, NewScramMap),
   35:     %% even though in db there is old record stored
   36:     OldScram = mnesia:dirty_read({passwd, {U, S}}),
   37:     ?assertMatch([{passwd, _, {scram, _, _, _, _}}], OldScram).
   38: 
   39: passwords_in_plain_can_be_converted_to_scram(_C) ->
   40:     %% Given there in mnesia there are users
   41:     %% with plain text password
   42:     {U, S, P} = gen_user(),
   43:     UserRecord = {passwd, {U, S}, P},
   44:     mnesia:dirty_write(UserRecord),
   45:     %% and password in the old record format
   46:     {U2, S2, P2} = gen_user(),
   47:     OldScramFormat = old_password_to_scram(P2, 340),
   48:     UserRecord2 = {passwd, {U2, S2}, OldScramFormat},
   49:     mnesia:dirty_write(UserRecord2),
   50:     %% when the migration function is run
   51:     ejabberd_auth_internal:scram_passwords(),
   52:     AfterMigrationPlain = mnesia:dirty_read({passwd, {U, S}}),
   53:     %% then plain text passwords are converted to new map with sha and sha256
   54:     ?assertMatch([{passwd, _,
   55:                    #{iteration_count := _,
   56:                      sha    := #{salt       := _,
   57:                                  server_key := _,
   58:                                  stored_key := _},
   59:                      sha224 := #{salt       := _,
   60:                                  server_key := _,
   61:                                  stored_key := _},
   62:                      sha256 := #{salt       := _,
   63:                                  server_key := _,
   64:                                  stored_key := _},
   65:                      sha384 := #{salt       := _,
   66:                                  server_key := _,
   67:                                  stored_key := _},
   68:                      sha512 := #{salt       := _,
   69:                                  server_key := _,
   70:                                  stored_key := _}}}], AfterMigrationPlain),
   71:     %% and the old scram format remains the same
   72:     AfterMigrationScram = mnesia:dirty_read({passwd, {U2, S2}}),
   73:     ?assertMatch([{passwd, _,
   74:                  #{iteration_count := _,
   75:                    sha := #{salt       := _,
   76:                             server_key := _,
   77:                             stored_key := _}}}], AfterMigrationScram).
   78: 
   79: gen_user() ->
   80:     {base64:encode(crypto:strong_rand_bytes(5)),
   81:      <<"server">>,
   82:      base64:encode(crypto:strong_rand_bytes(6))}.
   83: 
   84: old_password_to_scram(Password, IterationCount) ->
   85:     Salt = crypto:strong_rand_bytes(16),
   86:     SaltedPassword = fast_scram:salted_password(sha, jid:resourceprep(Password), Salt, IterationCount),
   87:     ClientKey = fast_scram:client_key(sha, SaltedPassword),
   88:     StoredKey = fast_scram:stored_key(sha, ClientKey),
   89:     ServerKey = fast_scram:server_key(sha, SaltedPassword),
   90:     #scram{storedkey = base64:encode(StoredKey),
   91:            serverkey = base64:encode(ServerKey),
   92:            salt = base64:encode(Salt),
   93:            iterationcount = IterationCount}.
   94: 
   95: host_type() ->
   96:     <<"test host type">>.